Re-translate the Japanese version (#1871)

* Retranslate Japanese docs with GPT-5.4

* Retranslate Japanese code with GPT-5.4
This commit is contained in:
Yudong Jin
2026-03-30 07:30:15 +08:00
committed by GitHub
parent fe6443235b
commit d7b2277d2b
1444 changed files with 83312 additions and 8363 deletions
+88
View File
@@ -0,0 +1,88 @@
# CSharp formatting rules
[*.cs]
csharp_new_line_before_open_brace = none
csharp_new_line_before_else = false
csharp_new_line_before_catch = false
csharp_new_line_before_finally = false
csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
# CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
dotnet_diagnostic.CS8981.severity = silent
# IDE1006: Naming Styles
dotnet_diagnostic.IDE1006.severity = silent
# CA1822: Mark members as static
dotnet_diagnostic.CA1822.severity = silent
[*.{cs,vb}]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
# IDE0040: Add accessibility modifiers
dotnet_diagnostic.IDE0040.severity = silent
# IDE0044: Add readonly modifier
dotnet_diagnostic.IDE0044.severity = silent
+5
View File
@@ -0,0 +1,5 @@
.idea/
.vs/
obj/
.Debug
bin/
+3
View File
@@ -0,0 +1,3 @@
global using NUnit.Framework;
global using hello_algo.utils;
global using System.Text;
@@ -0,0 +1,107 @@
// File: array.cs
// Created Time: 2022-12-14
// Author: mingXta (1195669834@qq.com)
namespace hello_algo.chapter_array_and_linkedlist;
public class array {
/* 要素へランダムアクセス */
int RandomAccess(int[] nums) {
Random random = new();
// 区間 [0, nums.Length) からランダムに数字を 1 つ選ぶ
int randomIndex = random.Next(nums.Length);
// ランダムな要素を取得して返す
int randomNum = nums[randomIndex];
return randomNum;
}
/* 配列長を拡張する */
int[] Extend(int[] nums, int enlarge) {
// 拡張後の長さを持つ配列を初期化する
int[] res = new int[nums.Length + enlarge];
// 元の配列の全要素を新しい配列にコピー
for (int i = 0; i < nums.Length; i++) {
res[i] = nums[i];
}
// 拡張後の新しい配列を返す
return res;
}
/* 配列の index 番目に要素 num を挿入 */
void Insert(int[] nums, int num, int index) {
// インデックス index 以降の全要素を 1 つ後ろへ移動する
for (int i = nums.Length - 1; i > index; i--) {
nums[i] = nums[i - 1];
}
// index の要素に num を代入する
nums[index] = num;
}
/* index の要素を削除する */
void Remove(int[] nums, int index) {
// インデックス index より後ろの全要素を 1 つ前へ移動する
for (int i = index; i < nums.Length - 1; i++) {
nums[i] = nums[i + 1];
}
}
/* 配列を走査 */
void Traverse(int[] nums) {
int count = 0;
// インデックスで配列を走査
for (int i = 0; i < nums.Length; i++) {
count += nums[i];
}
// 配列要素を直接走査
foreach (int num in nums) {
count += num;
}
}
/* 配列内で指定要素を探す */
int Find(int[] nums, int target) {
for (int i = 0; i < nums.Length; i++) {
if (nums[i] == target)
return i;
}
return -1;
}
/* 補助関数:配列を文字列に変換 */
string ToString(int[] nums) {
return string.Join(",", nums);
}
[Test]
public void Test() {
// 配列を初期化
int[] arr = new int[5];
Console.WriteLine("配列 arr = " + ToString(arr));
int[] nums = [1, 3, 2, 5, 4];
Console.WriteLine("配列 nums = " + ToString(nums));
// ランダムアクセス
int randomNum = RandomAccess(nums);
Console.WriteLine("nums からランダムな要素を取得 " + randomNum);
// 長さを拡張
nums = Extend(nums, 3);
Console.WriteLine("配列の長さを 8 まで拡張すると nums = " + ToString(nums));
// 要素を挿入する
Insert(nums, 6, 3);
Console.WriteLine("インデックス 3 に数値 6 を挿入すると nums = " + ToString(nums));
// 要素を削除
Remove(nums, 2);
Console.WriteLine("インデックス 2 の要素を削除すると nums = " + ToString(nums));
// 配列を走査
Traverse(nums);
// 要素を探索する
int index = Find(nums, 3);
Console.WriteLine("nums 内で要素 3 を検索するとインデックス = " + index);
}
}
@@ -0,0 +1,80 @@
// File: linked_list.cs
// Created Time: 2022-12-16
// Author: mingXta (1195669834@qq.com)
namespace hello_algo.chapter_array_and_linkedlist;
public class linked_list {
/* 連結リストでノード n0 の後ろにノード P を挿入する */
void Insert(ListNode n0, ListNode P) {
ListNode? n1 = n0.next;
P.next = n1;
n0.next = P;
}
/* 連結リストでノード n0 の直後のノードを削除する */
void Remove(ListNode n0) {
if (n0.next == null)
return;
// n0 -> P -> n1
ListNode P = n0.next;
ListNode? n1 = P.next;
n0.next = n1;
}
/* 連結リスト内で index 番目のノードにアクセス */
ListNode? Access(ListNode? head, int index) {
for (int i = 0; i < index; i++) {
if (head == null)
return null;
head = head.next;
}
return head;
}
/* 連結リストで値が target の最初のノードを探す */
int Find(ListNode? head, int target) {
int index = 0;
while (head != null) {
if (head.val == target)
return index;
head = head.next;
index++;
}
return -1;
}
[Test]
public void Test() {
// 連結リストを初期化する
// 各ノードを初期化する
ListNode n0 = new(1);
ListNode n1 = new(3);
ListNode n2 = new(2);
ListNode n3 = new(5);
ListNode n4 = new(4);
// ノード間の参照を構築する
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
Console.WriteLine($"初期化した連結リストは{n0}");
// ノードを挿入
Insert(n0, new ListNode(0));
Console.WriteLine($"ノード挿入後の連結リストは{n0}");
// ノードを削除
Remove(n0);
Console.WriteLine($"ノード削除後の連結リストは{n0}");
// ノードにアクセス
ListNode? node = Access(n0, 3);
Console.WriteLine($"連結リストのインデックス 3 にあるノードの値 = {node?.val}");
// ノードを探索
int index = Find(n0, 2);
Console.WriteLine($"連結リスト内で値が 2 のノードのインデックス = {index}");
}
}
@@ -0,0 +1,66 @@
/**
* File: list.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_array_and_linkedlist;
public class list {
[Test]
public void Test() {
/* リストを初期化 */
int[] numbers = [1, 3, 2, 5, 4];
List<int> 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<int> 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));
}
}
@@ -0,0 +1,144 @@
/**
* File: my_list.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_array_and_linkedlist;
/* リストクラス */
class MyList {
private int[] arr; // 配列(リスト要素を格納)
private int arrCapacity = 10; // リスト容量
private int arrSize = 0; // リストの長さ(現在の要素数)
private readonly int extendRatio = 2; // リスト拡張時の増加倍率
/* コンストラクタ */
public MyList() {
arr = new int[arrCapacity];
}
/* リストの長さを取得(現在の要素数) */
public int Size() {
return arrSize;
}
/* リスト容量を取得する */
public int Capacity() {
return arrCapacity;
}
/* 要素にアクセス */
public int Get(int index) {
// インデックスが範囲外なら例外を送出する。以下同様
if (index < 0 || index >= arrSize)
throw new IndexOutOfRangeException("インデックスが範囲外です");
return arr[index];
}
/* 要素を更新 */
public void Set(int index, int num) {
if (index < 0 || index >= arrSize)
throw new IndexOutOfRangeException("インデックスが範囲外です");
arr[index] = num;
}
/* 末尾に要素を追加 */
public void Add(int num) {
// 要素数が容量を超えると、拡張機構が発動する
if (arrSize == arrCapacity)
ExtendCapacity();
arr[arrSize] = num;
// 要素数を更新
arrSize++;
}
/* 中間に要素を挿入 */
public void Insert(int index, int num) {
if (index < 0 || index >= arrSize)
throw new IndexOutOfRangeException("インデックスが範囲外です");
// 要素数が容量を超えると、拡張機構が発動する
if (arrSize == arrCapacity)
ExtendCapacity();
// index 以降の要素をすべて 1 つ後ろへずらす
for (int j = arrSize - 1; j >= index; j--) {
arr[j + 1] = arr[j];
}
arr[index] = num;
// 要素数を更新
arrSize++;
}
/* 要素を削除 */
public int Remove(int index) {
if (index < 0 || index >= arrSize)
throw new IndexOutOfRangeException("インデックスが範囲外です");
int num = arr[index];
// インデックス index より後の要素をすべて 1 つ前に移動する
for (int j = index; j < arrSize - 1; j++) {
arr[j] = arr[j + 1];
}
// 要素数を更新
arrSize--;
// 削除された要素を返す
return num;
}
/* リストの拡張 */
public void ExtendCapacity() {
// `arrCapacity * extendRatio` の長さを持つ配列を新規作成し、元の配列を新しい配列にコピーする
Array.Resize(ref arr, arrCapacity * extendRatio);
// リストの容量を更新
arrCapacity = arr.Length;
}
/* リストを配列に変換する */
public int[] ToArray() {
// 有効長の範囲内のリスト要素のみを変換
int[] arr = new int[arrSize];
for (int i = 0; i < arrSize; i++) {
arr[i] = Get(i);
}
return arr;
}
}
public class my_list {
[Test]
public void Test() {
/* リストを初期化 */
MyList nums = new();
/* 末尾に要素を追加 */
nums.Add(1);
nums.Add(3);
nums.Add(2);
nums.Add(5);
nums.Add(4);
Console.WriteLine("リスト nums = " + string.Join(",", nums.ToArray()) +
" ,容量 = " + nums.Capacity() + " ,長さ = " + nums.Size());
/* 中間に要素を挿入 */
nums.Insert(3, 6);
Console.WriteLine("インデックス 3 に数値 6 を挿入すると nums = " + string.Join(",", nums.ToArray()));
/* 要素を削除 */
nums.Remove(3);
Console.WriteLine("インデックス 3 の要素を削除すると nums = " + string.Join(",", nums.ToArray()));
/* 要素にアクセス */
int num = nums.Get(1);
Console.WriteLine("インデックス 1 の要素にアクセスすると num = " + num);
/* 要素を更新 */
nums.Set(1, 0);
Console.WriteLine("インデックス 1 の要素を 0 に更新すると nums = " + string.Join(",", nums.ToArray()));
/* 拡張機構をテストする */
for (int i = 0; i < 10; i++) {
// i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する
nums.Add(i);
}
Console.WriteLine("拡張後のリスト nums = " + string.Join(",", nums.ToArray()) +
" ,容量 = " + nums.Capacity() + " ,長さ = " + nums.Size());
}
}
@@ -0,0 +1,76 @@
/**
* File: n_queens.cs
* Created Time: 2023-05-04
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_backtracking;
public class n_queens {
/* バックトラッキング:N クイーン */
void Backtrack(int row, int n, List<List<string>> state, List<List<List<string>>> res,
bool[] cols, bool[] diags1, bool[] diags2) {
// すべての行への配置が完了したら、解を記録する
if (row == n) {
List<List<string>> copyState = [];
foreach (List<string> sRow in state) {
copyState.Add(new List<string>(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<List<List<string>>> NQueens(int n) {
// n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す
List<List<string>> state = [];
for (int i = 0; i < n; i++) {
List<string> 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<List<List<string>>> res = [];
Backtrack(0, n, state, res, cols, diags1, diags2);
return res;
}
[Test]
public void Test() {
int n = 4;
List<List<List<string>>> res = NQueens(n);
Console.WriteLine("盤面の縦横サイズの入力値は " + n);
Console.WriteLine("クイーンの配置パターンは全部で " + res.Count + " 通り");
foreach (List<List<string>> state in res) {
Console.WriteLine("--------------------");
foreach (List<string> row in state) {
PrintUtil.PrintList(row);
}
}
}
}
@@ -0,0 +1,53 @@
/**
* File: permutations_i.cs
* Created Time: 2023-04-24
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_backtracking;
public class permutations_i {
/* バックトラッキング:順列 I */
void Backtrack(List<int> state, int[] choices, bool[] selected, List<List<int>> res) {
// 状態の長さが要素数に等しければ、解を記録
if (state.Count == choices.Length) {
res.Add(new List<int>(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<List<int>> PermutationsI(int[] nums) {
List<List<int>> res = [];
Backtrack([], nums, new bool[nums.Length], res);
return res;
}
[Test]
public void Test() {
int[] nums = [1, 2, 3];
List<List<int>> res = PermutationsI(nums);
Console.WriteLine("入力配列 nums = " + string.Join(", ", nums));
Console.WriteLine("すべての順列 res = ");
foreach (List<int> permutation in res) {
PrintUtil.PrintList(permutation);
}
}
}
@@ -0,0 +1,55 @@
/**
* File: permutations_ii.cs
* Created Time: 2023-04-24
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_backtracking;
public class permutations_ii {
/* バックトラッキング:順列 II */
void Backtrack(List<int> state, int[] choices, bool[] selected, List<List<int>> res) {
// 状態の長さが要素数に等しければ、解を記録
if (state.Count == choices.Length) {
res.Add(new List<int>(state));
return;
}
// すべての選択肢を走査
HashSet<int> 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<List<int>> PermutationsII(int[] nums) {
List<List<int>> res = [];
Backtrack([], nums, new bool[nums.Length], res);
return res;
}
[Test]
public void Test() {
int[] nums = [1, 2, 2];
List<List<int>> res = PermutationsII(nums);
Console.WriteLine("入力配列 nums = " + string.Join(", ", nums));
Console.WriteLine("すべての順列 res = ");
foreach (List<int> permutation in res) {
PrintUtil.PrintList(permutation);
}
}
}
@@ -0,0 +1,37 @@
/**
* File: preorder_traversal_i_compact.cs
* Created Time: 2023-04-17
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_backtracking;
public class preorder_traversal_i_compact {
List<TreeNode> 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());
}
}
@@ -0,0 +1,44 @@
/**
* File: preorder_traversal_ii_compact.cs
* Created Time: 2023-04-17
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_backtracking;
public class preorder_traversal_ii_compact {
List<TreeNode> path = [];
List<List<TreeNode>> res = [];
/* 前順走査:例題 2 */
void PreOrder(TreeNode? root) {
if (root == null) {
return;
}
// 試す
path.Add(root);
if (root.val == 7) {
// 解を記録
res.Add(new List<TreeNode>(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<TreeNode> path in res) {
PrintUtil.PrintList(path.Select(p => p.val).ToList());
}
}
}
@@ -0,0 +1,45 @@
/**
* File: preorder_traversal_iii_compact.cs
* Created Time: 2023-04-17
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_backtracking;
public class preorder_traversal_iii_compact {
List<TreeNode> path = [];
List<List<TreeNode>> res = [];
/* 前順走査:例題 3 */
void PreOrder(TreeNode? root) {
// 枝刈り
if (root == null || root.val == 3) {
return;
}
// 試す
path.Add(root);
if (root.val == 7) {
// 解を記録
res.Add(new List<TreeNode>(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<TreeNode> path in res) {
PrintUtil.PrintList(path.Select(p => p.val).ToList());
}
}
}
@@ -0,0 +1,72 @@
/**
* File: preorder_traversal_iii_template.cs
* Created Time: 2023-04-17
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_backtracking;
public class preorder_traversal_iii_template {
/* 現在の状態が解かどうかを判定 */
bool IsSolution(List<TreeNode> state) {
return state.Count != 0 && state[^1].val == 7;
}
/* 解を記録 */
void RecordSolution(List<TreeNode> state, List<List<TreeNode>> res) {
res.Add(new List<TreeNode>(state));
}
/* 現在の状態で、この選択が有効かどうかを判定 */
bool IsValid(List<TreeNode> state, TreeNode choice) {
return choice != null && choice.val != 3;
}
/* 状態を更新 */
void MakeChoice(List<TreeNode> state, TreeNode choice) {
state.Add(choice);
}
/* 状態を元に戻す */
void UndoChoice(List<TreeNode> state, TreeNode choice) {
state.RemoveAt(state.Count - 1);
}
/* バックトラッキング:例題 3 */
void Backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> 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<List<TreeNode>> res = [];
List<TreeNode> choices = [root!];
Backtrack([], choices, res);
Console.WriteLine("\nルートノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含まないことを条件とする");
foreach (List<TreeNode> path in res) {
PrintUtil.PrintList(path.Select(p => p.val).ToList());
}
}
}
@@ -0,0 +1,55 @@
/**
* File: subset_sum_i.cs
* Created Time: 2023-06-25
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_backtracking;
public class subset_sum_i {
/* バックトラッキング:部分和 I */
void Backtrack(List<int> state, int target, int[] choices, int start, List<List<int>> res) {
// 部分集合の和が target に等しければ、解を記録
if (target == 0) {
res.Add(new List<int>(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<List<int>> SubsetSumI(int[] nums, int target) {
List<int> state = []; // 状態(部分集合)
Array.Sort(nums); // nums をソート
int start = 0; // 開始点を走査
List<List<int>> res = []; // 結果リスト(部分集合のリスト)
Backtrack(state, target, nums, start, res);
return res;
}
[Test]
public void Test() {
int[] nums = [3, 4, 5];
int target = 9;
List<List<int>> res = SubsetSumI(nums, target);
Console.WriteLine("入力配列 nums = " + string.Join(", ", nums) + ", target = " + target);
Console.WriteLine("和が " + target + " に等しいすべての部分集合 res = ");
foreach (var subset in res) {
PrintUtil.PrintList(subset);
}
}
}
@@ -0,0 +1,53 @@
/**
* File: subset_sum_i_naive.cs
* Created Time: 2023-06-25
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_backtracking;
public class subset_sum_i_naive {
/* バックトラッキング:部分和 I */
void Backtrack(List<int> state, int target, int total, int[] choices, List<List<int>> res) {
// 部分集合の和が target に等しければ、解を記録
if (total == target) {
res.Add(new List<int>(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<List<int>> SubsetSumINaive(int[] nums, int target) {
List<int> state = []; // 状態(部分集合)
int total = 0; // 部分和
List<List<int>> res = []; // 結果リスト(部分集合のリスト)
Backtrack(state, target, total, nums, res);
return res;
}
[Test]
public void Test() {
int[] nums = [3, 4, 5];
int target = 9;
List<List<int>> 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("この方法の出力結果には重複した集合が含まれることに注意してください");
}
}
@@ -0,0 +1,60 @@
/**
* File: subset_sum_ii.cs
* Created Time: 2023-06-25
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_backtracking;
public class subset_sum_ii {
/* バックトラッキング:部分和 II */
void Backtrack(List<int> state, int target, int[] choices, int start, List<List<int>> res) {
// 部分集合の和が target に等しければ、解を記録
if (target == 0) {
res.Add(new List<int>(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<List<int>> SubsetSumII(int[] nums, int target) {
List<int> state = []; // 状態(部分集合)
Array.Sort(nums); // nums をソート
int start = 0; // 開始点を走査
List<List<int>> res = []; // 結果リスト(部分集合のリスト)
Backtrack(state, target, nums, start, res);
return res;
}
[Test]
public void Test() {
int[] nums = [4, 4, 5];
int target = 9;
List<List<int>> res = SubsetSumII(nums, target);
Console.WriteLine("入力配列 nums = " + string.Join(", ", nums) + ", target = " + target);
Console.WriteLine("和が " + target + " に等しいすべての部分集合 res = ");
foreach (var subset in res) {
PrintUtil.PrintList(subset);
}
}
}
@@ -0,0 +1,77 @@
/**
* File: iteration.cs
* Created Time: 2023-08-28
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_computational_complexity;
public class iteration {
/* for ループ */
int ForLoop(int n) {
int res = 0;
// 1, 2, ..., n-1, n を順に加算する
for (int i = 1; i <= n; i++) {
res += i;
}
return res;
}
/* while ループ */
int WhileLoop(int n) {
int res = 0;
int i = 1; // 条件変数を初期化する
// 1, 2, ..., n-1, n を順に加算する
while (i <= n) {
res += i;
i += 1; // 条件変数を更新する
}
return res;
}
/* while ループ(2回更新) */
int WhileLoopII(int n) {
int res = 0;
int i = 1; // 条件変数を初期化する
// 1, 4, 10, ... を順に加算する
while (i <= n) {
res += i;
// 条件変数を更新する
i += 1;
i *= 2;
}
return res;
}
/* 二重 for ループ */
string NestedForLoop(int n) {
StringBuilder res = new();
// i = 1, 2, ..., n-1, n とループする
for (int i = 1; i <= n; i++) {
// j = 1, 2, ..., n-1, n とループする
for (int j = 1; j <= n; j++) {
res.Append($"({i}, {j}), ");
}
}
return res.ToString();
}
/* Driver Code */
[Test]
public void Test() {
int n = 5;
int res;
res = ForLoop(n);
Console.WriteLine("\nfor ループの合計結果 res = " + res);
res = WhileLoop(n);
Console.WriteLine("\nwhile ループの合計結果 res = " + res);
res = WhileLoopII(n);
Console.WriteLine("\nwhile ループ(2回更新)の合計結果 res = " + res);
string resStr = NestedForLoop(n);
Console.WriteLine("\n二重 for ループの走査結果 " + resStr);
}
}
@@ -0,0 +1,78 @@
/**
* File: recursion.cs
* Created Time: 2023-08-28
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_computational_complexity;
public class recursion {
/* 再帰 */
int Recur(int n) {
// 終了条件
if (n == 1)
return 1;
// 再帰:再帰呼び出し
int res = Recur(n - 1);
// 帰りがけ:結果を返す
return n + res;
}
/* 反復で再帰を模擬する */
int ForLoopRecur(int n) {
// 明示的なスタックを使ってシステムコールスタックを模擬する
Stack<int> 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);
}
}
@@ -0,0 +1,104 @@
/**
* File: space_complexity.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_computational_complexity;
public class space_complexity {
/* 関数 */
int Function() {
// 何らかの処理を行う
return 0;
}
/* 定数階 */
void Constant(int n) {
// 定数、変数、オブジェクトは O(1) の空間を占める
int a = 0;
int b = 0;
int[] nums = new int[10000];
ListNode node = new(0);
// ループ内の変数は O(1) の空間を占める
for (int i = 0; i < n; i++) {
int c = 0;
}
// ループ内の関数は O(1) の空間を占める
for (int i = 0; i < n; i++) {
Function();
}
}
/* 線形階 */
void Linear(int n) {
// 長さ n の配列は O(n) の空間を使用
int[] nums = new int[n];
// 長さ n のリストは O(n) の空間を使用
List<ListNode> nodes = [];
for (int i = 0; i < n; i++) {
nodes.Add(new ListNode(i));
}
// 長さ n のハッシュテーブルは O(n) の空間を使用
Dictionary<int, string> 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<List<int>> numList = [];
for (int i = 0; i < n; i++) {
List<int> 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);
}
}
@@ -0,0 +1,195 @@
/**
* File: time_complexity.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_computational_complexity;
public class time_complexity {
void Algorithm(int n) {
int a = 1; // +0(テクニック 1
a += n; // +0(テクニック 1
// +n(テクニック 2
for (int i = 0; i < 5 * n + 1; i++) {
Console.WriteLine(0);
}
// +n*n(テクニック 3
for (int i = 0; i < 2 * n; i++) {
for (int j = 0; j < n + 1; j++) {
Console.WriteLine(0);
}
}
}
// アルゴリズム A の時間計算量: 定数時間
void AlgorithmA(int n) {
Console.WriteLine(0);
}
// アルゴリズム B の時間計算量: 線形時間
void AlgorithmB(int n) {
for (int i = 0; i < n; i++) {
Console.WriteLine(0);
}
}
// アルゴリズム C の時間計算量: 定数時間
void AlgorithmC(int n) {
for (int i = 0; i < 1000000; i++) {
Console.WriteLine(0);
}
}
/* 定数階 */
int Constant(int n) {
int count = 0;
int size = 100000;
for (int i = 0; i < size; i++)
count++;
return count;
}
/* 線形階 */
int Linear(int n) {
int count = 0;
for (int i = 0; i < n; i++)
count++;
return count;
}
/* 線形時間(配列を走査) */
int ArrayTraversal(int[] nums) {
int count = 0;
// ループ回数は配列長に比例する
foreach (int num in nums) {
count++;
}
return count;
}
/* 二乗階 */
int Quadratic(int n) {
int count = 0;
// ループ回数はデータサイズ n の二乗に比例する
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
count++;
}
}
return count;
}
/* 二次時間(バブルソート) */
int BubbleSort(int[] nums) {
int count = 0; // カウンタ
// 外側のループ:未ソート区間は [0, i]
for (int i = nums.Length - 1; i > 0; i--) {
// 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// nums[j] と nums[j + 1] を交換
(nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);
count += 3; // 要素交換には 3 回の単位操作が含まれる
}
}
}
return count;
}
/* 指数時間(ループ実装) */
int Exponential(int n) {
int count = 0, bas = 1;
// 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する
for (int i = 0; i < n; i++) {
for (int j = 0; j < bas; j++) {
count++;
}
bas *= 2;
}
// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
return count;
}
/* 指数時間(再帰実装) */
int ExpRecur(int n) {
if (n == 1) return 1;
return ExpRecur(n - 1) + ExpRecur(n - 1) + 1;
}
/* 対数時間(ループ実装) */
int Logarithmic(int n) {
int count = 0;
while (n > 1) {
n /= 2;
count++;
}
return count;
}
/* 対数時間(再帰実装) */
int LogRecur(int n) {
if (n <= 1) return 0;
return LogRecur(n / 2) + 1;
}
/* 線形対数時間 */
int LinearLogRecur(int n) {
if (n <= 1) return 1;
int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2);
for (int i = 0; i < n; i++) {
count++;
}
return count;
}
/* 階乗時間(再帰実装) */
int FactorialRecur(int n) {
if (n == 0) return 1;
int count = 0;
// 1個から n 個に分裂
for (int i = 0; i < n; i++) {
count += FactorialRecur(n - 1);
}
return count;
}
[Test]
public void Test() {
// n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる
int n = 8;
Console.WriteLine("入力データサイズ n = " + n);
int count = Constant(n);
Console.WriteLine("定数時間の操作回数 = " + count);
count = Linear(n);
Console.WriteLine("線形時間の操作回数 = " + count);
count = ArrayTraversal(new int[n]);
Console.WriteLine("線形時間(配列の走査)の操作回数 = " + count);
count = Quadratic(n);
Console.WriteLine("二乗時間の操作回数 = " + count);
int[] nums = new int[n];
for (int i = 0; i < n; i++)
nums[i] = n - i; // [n,n-1,...,2,1]
count = BubbleSort(nums);
Console.WriteLine("二乗時間(バブルソート)の操作回数 = " + count);
count = Exponential(n);
Console.WriteLine("指数時間(ループ実装)の操作回数 = " + count);
count = ExpRecur(n);
Console.WriteLine("指数時間(再帰実装)の操作回数 = " + count);
count = Logarithmic(n);
Console.WriteLine("対数時間(ループ実装)の操作回数 = " + count);
count = LogRecur(n);
Console.WriteLine("対数時間(再帰実装)の操作回数 = " + count);
count = LinearLogRecur(n);
Console.WriteLine("線形対数時間(再帰実装)の操作回数 = " + count);
count = FactorialRecur(n);
Console.WriteLine("階乗時間(再帰実装)の操作回数 = " + count);
}
}
@@ -0,0 +1,49 @@
/**
* File: worst_best_time_complexity.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_computational_complexity;
public class worst_best_time_complexity {
/* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */
int[] RandomNumbers(int n) {
int[] nums = new int[n];
// 配列 nums = { 1, 2, 3, ..., n } を生成
for (int i = 0; i < n; i++) {
nums[i] = i + 1;
}
// 配列要素をランダムにシャッフル
for (int i = 0; i < nums.Length; i++) {
int index = new Random().Next(i, nums.Length);
(nums[i], nums[index]) = (nums[index], nums[i]);
}
return nums;
}
/* 配列 nums 内で数値 1 のインデックスを探す */
int FindOne(int[] nums) {
for (int i = 0; i < nums.Length; i++) {
// 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる
// 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる
if (nums[i] == 1)
return i;
}
return -1;
}
/* Driver Code */
[Test]
public void Test() {
for (int i = 0; i < 10; i++) {
int n = 100;
int[] nums = RandomNumbers(n);
int index = FindOne(nums);
Console.WriteLine("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = " + string.Join(",", nums));
Console.WriteLine("数字 1 のインデックスは " + index);
}
}
}
@@ -0,0 +1,46 @@
/**
* File: binary_search_recur.cs
* Created Time: 2023-07-18
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_divide_and_conquer;
public class binary_search_recur {
/* 二分探索:問題 f(i, j) */
int DFS(int[] nums, int target, int i, int j) {
// 区間が空なら対象要素は存在しないので -1 を返す
if (i > j) {
return -1;
}
// 中点インデックス m を計算
int m = (i + j) / 2;
if (nums[m] < target) {
// 部分問題 f(m+1, j) を再帰的に解く
return DFS(nums, target, m + 1, j);
} else if (nums[m] > target) {
// 部分問題 f(i, m-1) を再帰的に解く
return DFS(nums, target, i, m - 1);
} else {
// 目標要素が見つかったらそのインデックスを返す
return m;
}
}
/* 二分探索 */
int BinarySearch(int[] nums, int target) {
int n = nums.Length;
// 問題 f(0, n-1) を解く
return DFS(nums, target, 0, n - 1);
}
[Test]
public void Test() {
int target = 6;
int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35];
// 二分探索(両閉区間)
int index = BinarySearch(nums, target);
Console.WriteLine("対象要素 6 のインデックス = " + index);
}
}
@@ -0,0 +1,49 @@
/**
* File: build_tree.cs
* Created Time: 2023-07-18
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_divide_and_conquer;
public class build_tree {
/* 二分木を構築:分割統治 */
TreeNode? DFS(int[] preorder, Dictionary<int, int> 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<int, int> 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);
}
}
@@ -0,0 +1,59 @@
/**
* File: hanota.cs
* Created Time: 2023-07-18
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_divide_and_conquer;
public class hanota {
/* 円盤を 1 枚移動 */
void Move(List<int> src, List<int> tar) {
// src の上から円盤を1枚取り出す
int pan = src[^1];
src.RemoveAt(src.Count - 1);
// 円盤を tar の上に置く
tar.Add(pan);
}
/* ハノイの塔の問題 f(i) を解く */
void DFS(int i, List<int> src, List<int> buf, List<int> 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<int> A, List<int> B, List<int> C) {
int n = A.Count;
// A の上から n 枚の円盤を B を介して C へ移す
DFS(n, A, B, C);
}
[Test]
public void Test() {
// リスト末尾が柱の頂上
List<int> A = [5, 4, 3, 2, 1];
List<int> B = [];
List<int> 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));
}
}
@@ -0,0 +1,41 @@
/**
* File: climbing_stairs_backtrack.cs
* Created Time: 2023-06-30
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class climbing_stairs_backtrack {
/* バックトラッキング */
void Backtrack(List<int> choices, int state, int n, List<int> 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<int> choices = [1, 2]; // 1 段または 2 段上ることを選べる
int state = 0; // 第 0 段から上り始める
List<int> 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} 通り");
}
}
@@ -0,0 +1,36 @@
/**
* File: climbing_stairs_constraint_dp.cs
* Created Time: 2023-07-03
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class climbing_stairs_constraint_dp {
/* 制約付き階段登り:動的計画法 */
int ClimbingStairsConstraintDP(int n) {
if (n == 1 || n == 2) {
return 1;
}
// 部分問題の解を保存するために dp テーブルを初期化
int[,] dp = new int[n + 1, 3];
// 初期状態:最小部分問題の解をあらかじめ設定
dp[1, 1] = 1;
dp[1, 2] = 0;
dp[2, 1] = 0;
dp[2, 2] = 1;
// 状態遷移:小さい部分問題から大きい部分問題へ順に解く
for (int i = 3; i <= n; i++) {
dp[i, 1] = dp[i - 1, 2];
dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2];
}
return dp[n, 1] + dp[n, 2];
}
[Test]
public void Test() {
int n = 9;
int res = ClimbingStairsConstraintDP(n);
Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り");
}
}
@@ -0,0 +1,31 @@
/**
* File: climbing_stairs_dfs.cs
* Created Time: 2023-06-30
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class climbing_stairs_dfs {
/* 検索 */
int DFS(int i) {
// dp[1] と dp[2] は既知なので返す
if (i == 1 || i == 2)
return i;
// dp[i] = dp[i-1] + dp[i-2]
int count = DFS(i - 1) + DFS(i - 2);
return count;
}
/* 階段登り:探索 */
int ClimbingStairsDFS(int n) {
return DFS(n);
}
[Test]
public void Test() {
int n = 9;
int res = ClimbingStairsDFS(n);
Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り");
}
}
@@ -0,0 +1,39 @@
/**
* File: climbing_stairs_dfs_mem.cs
* Created Time: 2023-06-30
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class climbing_stairs_dfs_mem {
/* メモ化探索 */
int DFS(int i, int[] mem) {
// dp[1] と dp[2] は既知なので返す
if (i == 1 || i == 2)
return i;
// dp[i] の記録があれば、それをそのまま返す
if (mem[i] != -1)
return mem[i];
// dp[i] = dp[i-1] + dp[i-2]
int count = DFS(i - 1, mem) + DFS(i - 2, mem);
// dp[i] を記録する
mem[i] = count;
return count;
}
/* 階段登り:メモ化探索 */
int ClimbingStairsDFSMem(int n) {
// mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す
int[] mem = new int[n + 1];
Array.Fill(mem, -1);
return DFS(n, mem);
}
[Test]
public void Test() {
int n = 9;
int res = ClimbingStairsDFSMem(n);
Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り");
}
}
@@ -0,0 +1,49 @@
/**
* File: climbing_stairs_dp.cs
* Created Time: 2023-06-30
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class climbing_stairs_dp {
/* 階段登り:動的計画法 */
int ClimbingStairsDP(int n) {
if (n == 1 || n == 2)
return n;
// 部分問題の解を保存するために dp テーブルを初期化
int[] dp = new int[n + 1];
// 初期状態:最小部分問題の解をあらかじめ設定
dp[1] = 1;
dp[2] = 2;
// 状態遷移:小さい部分問題から大きい部分問題へ順に解く
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
/* 階段登り:空間最適化した動的計画法 */
int ClimbingStairsDPComp(int n) {
if (n == 1 || n == 2)
return n;
int a = 1, b = 2;
for (int i = 3; i <= n; i++) {
int tmp = b;
b = a + b;
a = tmp;
}
return b;
}
[Test]
public void Test() {
int n = 9;
int res = ClimbingStairsDP(n);
Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り");
res = ClimbingStairsDPComp(n);
Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り");
}
}
@@ -0,0 +1,71 @@
/**
* File: coin_change.cs
* Created Time: 2023-07-12
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class coin_change {
/* コイン両替:動的計画法 */
int CoinChangeDP(int[] coins, int amt) {
int n = coins.Length;
int MAX = amt + 1;
// dp テーブルを初期化
int[,] dp = new int[n + 1, amt + 1];
// 状態遷移:先頭行と先頭列
for (int a = 1; a <= amt; a++) {
dp[0, a] = MAX;
}
// 状態遷移: 残りの行と列
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 目標金額を超えるなら硬貨 i は選ばない
dp[i, a] = dp[i - 1, a];
} else {
// 硬貨 i を選ばない場合と選ぶ場合の小さい方
dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1);
}
}
}
return dp[n, amt] != MAX ? dp[n, amt] : -1;
}
/* コイン交換:空間最適化後の動的計画法 */
int CoinChangeDPComp(int[] coins, int amt) {
int n = coins.Length;
int MAX = amt + 1;
// dp テーブルを初期化
int[] dp = new int[amt + 1];
Array.Fill(dp, MAX);
dp[0] = 0;
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 目標金額を超えるなら硬貨 i は選ばない
dp[a] = dp[a];
} else {
// 硬貨 i を選ばない場合と選ぶ場合の小さい方
dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1);
}
}
}
return dp[amt] != MAX ? dp[amt] : -1;
}
[Test]
public void Test() {
int[] coins = [1, 2, 5];
int amt = 4;
// 動的計画法
int res = CoinChangeDP(coins, amt);
Console.WriteLine("目標金額にするために必要な最小の硬貨枚数は " + res);
// 空間最適化後の動的計画法
res = CoinChangeDPComp(coins, amt);
Console.WriteLine("目標金額にするために必要な最小の硬貨枚数は " + res);
}
}
@@ -0,0 +1,68 @@
/**
* File: coin_change_ii.cs
* Created Time: 2023-07-12
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class coin_change_ii {
/* コイン両替 II:動的計画法 */
int CoinChangeIIDP(int[] coins, int amt) {
int n = coins.Length;
// dp テーブルを初期化
int[,] dp = new int[n + 1, amt + 1];
// 先頭列を初期化する
for (int i = 0; i <= n; i++) {
dp[i, 0] = 1;
}
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 目標金額を超えるなら硬貨 i は選ばない
dp[i, a] = dp[i - 1, a];
} else {
// コイン i を選ばない場合と選ぶ場合の和
dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]];
}
}
}
return dp[n, amt];
}
/* コイン両替 II:空間最適化した動的計画法 */
int CoinChangeIIDPComp(int[] coins, int amt) {
int n = coins.Length;
// dp テーブルを初期化
int[] dp = new int[amt + 1];
dp[0] = 1;
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 目標金額を超えるなら硬貨 i は選ばない
dp[a] = dp[a];
} else {
// コイン i を選ばない場合と選ぶ場合の和
dp[a] = dp[a] + dp[a - coins[i - 1]];
}
}
}
return dp[amt];
}
[Test]
public void Test() {
int[] coins = [1, 2, 5];
int amt = 5;
// 動的計画法
int res = CoinChangeIIDP(coins, amt);
Console.WriteLine("目標金額を作る硬貨の組み合わせ数は " + res);
// 空間最適化後の動的計画法
res = CoinChangeIIDPComp(coins, amt);
Console.WriteLine("目標金額を作る硬貨の組み合わせ数は " + res);
}
}
@@ -0,0 +1,141 @@
/**
* File: edit_distance.cs
* Created Time: 2023-07-14
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class edit_distance {
/* 編集距離:総当たり探索 */
int EditDistanceDFS(string s, string t, int i, int j) {
// s と t がともに空なら 0 を返す
if (i == 0 && j == 0)
return 0;
// s が空なら t の長さを返す
if (i == 0)
return j;
// t が空なら s の長さを返す
if (j == 0)
return i;
// 2 つの文字が等しければ、その 2 文字をそのままスキップする
if (s[i - 1] == t[j - 1])
return EditDistanceDFS(s, t, i - 1, j - 1);
// 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1
int insert = EditDistanceDFS(s, t, i, j - 1);
int delete = EditDistanceDFS(s, t, i - 1, j);
int replace = EditDistanceDFS(s, t, i - 1, j - 1);
// 最小編集回数を返す
return Math.Min(Math.Min(insert, delete), replace) + 1;
}
/* 編集距離:メモ化探索 */
int EditDistanceDFSMem(string s, string t, int[][] mem, int i, int j) {
// s と t がともに空なら 0 を返す
if (i == 0 && j == 0)
return 0;
// s が空なら t の長さを返す
if (i == 0)
return j;
// t が空なら s の長さを返す
if (j == 0)
return i;
// 記録済みなら、それをそのまま返す
if (mem[i][j] != -1)
return mem[i][j];
// 2 つの文字が等しければ、その 2 文字をそのままスキップする
if (s[i - 1] == t[j - 1])
return EditDistanceDFSMem(s, t, mem, i - 1, j - 1);
// 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1
int insert = EditDistanceDFSMem(s, t, mem, i, j - 1);
int delete = EditDistanceDFSMem(s, t, mem, i - 1, j);
int replace = EditDistanceDFSMem(s, t, mem, i - 1, j - 1);
// 最小編集回数を記録して返す
mem[i][j] = Math.Min(Math.Min(insert, delete), replace) + 1;
return mem[i][j];
}
/* 編集距離:動的計画法 */
int EditDistanceDP(string s, string t) {
int n = s.Length, m = t.Length;
int[,] dp = new int[n + 1, m + 1];
// 状態遷移:先頭行と先頭列
for (int i = 1; i <= n; i++) {
dp[i, 0] = i;
}
for (int j = 1; j <= m; j++) {
dp[0, j] = j;
}
// 状態遷移: 残りの行と列
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s[i - 1] == t[j - 1]) {
// 2 つの文字が等しければ、その 2 文字をそのままスキップする
dp[i, j] = dp[i - 1, j - 1];
} else {
// 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1
dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1;
}
}
}
return dp[n, m];
}
/* 編集距離:空間最適化した動的計画法 */
int EditDistanceDPComp(string s, string t) {
int n = s.Length, m = t.Length;
int[] dp = new int[m + 1];
// 状態遷移:先頭行
for (int j = 1; j <= m; j++) {
dp[j] = j;
}
// 状態遷移:残りの行
for (int i = 1; i <= n; i++) {
// 状態遷移:先頭列
int leftup = dp[0]; // dp[i-1, j-1] を一時保存する
dp[0] = i;
// 状態遷移:残りの列
for (int j = 1; j <= m; j++) {
int temp = dp[j];
if (s[i - 1] == t[j - 1]) {
// 2 つの文字が等しければ、その 2 文字をそのままスキップする
dp[j] = leftup;
} else {
// 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1
dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1;
}
leftup = temp; // 次の反復の dp[i-1, j-1] に更新する
}
}
return dp[m];
}
[Test]
public void Test() {
string s = "bag";
string t = "pack";
int n = s.Length, m = t.Length;
// 全探索
int res = EditDistanceDFS(s, t, n, m);
Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要");
// メモ化探索
int[][] mem = new int[n + 1][];
for (int i = 0; i <= n; i++) {
mem[i] = new int[m + 1];
Array.Fill(mem[i], -1);
}
res = EditDistanceDFSMem(s, t, mem, n, m);
Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要");
// 動的計画法
res = EditDistanceDP(s, t);
Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要");
// 空間最適化後の動的計画法
res = EditDistanceDPComp(s, t);
Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要");
}
}
@@ -0,0 +1,118 @@
/**
* File: knapsack.cs
* Created Time: 2023-07-07
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class knapsack {
/* 0-1 ナップサック:総当たり探索 */
int KnapsackDFS(int[] weight, int[] val, int i, int c) {
// すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す
if (i == 0 || c == 0) {
return 0;
}
// ナップサック容量を超える場合は、入れない選択しかできない
if (weight[i - 1] > c) {
return KnapsackDFS(weight, val, i - 1, c);
}
// 品物 i を入れない場合と入れる場合の最大価値を計算する
int no = KnapsackDFS(weight, val, i - 1, c);
int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1];
// 2つの案のうち価値が大きいほうを返す
return Math.Max(no, yes);
}
/* 0-1 ナップサック:メモ化探索 */
int KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) {
// すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す
if (i == 0 || c == 0) {
return 0;
}
// 既に記録があればそのまま返す
if (mem[i][c] != -1) {
return mem[i][c];
}
// ナップサック容量を超える場合は、入れない選択しかできない
if (weight[i - 1] > c) {
return KnapsackDFSMem(weight, val, mem, i - 1, c);
}
// 品物 i を入れない場合と入れる場合の最大価値を計算する
int no = KnapsackDFSMem(weight, val, mem, i - 1, c);
int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1];
// 2 つの案のうち価値が大きい方を記録して返す
mem[i][c] = Math.Max(no, yes);
return mem[i][c];
}
/* 0-1 ナップサック:動的計画法 */
int KnapsackDP(int[] weight, int[] val, int cap) {
int n = weight.Length;
// dp テーブルを初期化
int[,] dp = new int[n + 1, cap + 1];
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int c = 1; c <= cap; c++) {
if (weight[i - 1] > c) {
// ナップサック容量を超えるなら品物 i は選ばない
dp[i, c] = dp[i - 1, c];
} else {
// 品物 i を選ばない場合と選ぶ場合の大きい方
dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]);
}
}
}
return dp[n, cap];
}
/* 0-1 ナップサック:空間最適化後の動的計画法 */
int KnapsackDPComp(int[] weight, int[] val, int cap) {
int n = weight.Length;
// dp テーブルを初期化
int[] dp = new int[cap + 1];
// 状態遷移
for (int i = 1; i <= n; i++) {
// 逆順に走査する
for (int c = cap; c > 0; c--) {
if (weight[i - 1] > c) {
// ナップサック容量を超えるなら品物 i は選ばない
dp[c] = dp[c];
} else {
// 品物 i を選ばない場合と選ぶ場合の大きい方
dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]);
}
}
}
return dp[cap];
}
[Test]
public void Test() {
int[] weight = [10, 20, 30, 40, 50];
int[] val = [50, 120, 150, 210, 240];
int cap = 50;
int n = weight.Length;
// 全探索
int res = KnapsackDFS(weight, val, n, cap);
Console.WriteLine("バックパック容量を超えない最大価値は " + res);
// メモ化探索
int[][] mem = new int[n + 1][];
for (int i = 0; i <= n; i++) {
mem[i] = new int[cap + 1];
Array.Fill(mem[i], -1);
}
res = KnapsackDFSMem(weight, val, mem, n, cap);
Console.WriteLine("バックパック容量を超えない最大価値は " + res);
// 動的計画法
res = KnapsackDP(weight, val, cap);
Console.WriteLine("バックパック容量を超えない最大価値は " + res);
// 空間最適化後の動的計画法
res = KnapsackDPComp(weight, val, cap);
Console.WriteLine("バックパック容量を超えない最大価値は " + res);
}
}
@@ -0,0 +1,53 @@
/**
* File: min_cost_climbing_stairs_dp.cs
* Created Time: 2023-06-30
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class min_cost_climbing_stairs_dp {
/* 階段登りの最小コスト:動的計画法 */
int MinCostClimbingStairsDP(int[] cost) {
int n = cost.Length - 1;
if (n == 1 || n == 2)
return cost[n];
// 部分問題の解を保存するために dp テーブルを初期化
int[] dp = new int[n + 1];
// 初期状態:最小部分問題の解をあらかじめ設定
dp[1] = cost[1];
dp[2] = cost[2];
// 状態遷移:小さい部分問題から大きい部分問題へ順に解く
for (int i = 3; i <= n; i++) {
dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i];
}
return dp[n];
}
/* 階段昇りの最小コスト:空間最適化後の動的計画法 */
int MinCostClimbingStairsDPComp(int[] cost) {
int n = cost.Length - 1;
if (n == 1 || n == 2)
return cost[n];
int a = cost[1], b = cost[2];
for (int i = 3; i <= n; i++) {
int tmp = b;
b = Math.Min(a, tmp) + cost[i];
a = tmp;
}
return b;
}
[Test]
public void Test() {
int[] cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1];
Console.WriteLine("入力された階段コストのリストは");
PrintUtil.PrintList(cost);
int res = MinCostClimbingStairsDP(cost);
Console.WriteLine($"階段を上り切る最小コストは {res}");
res = MinCostClimbingStairsDPComp(cost);
Console.WriteLine($"階段を上り切る最小コストは {res}");
}
}
@@ -0,0 +1,127 @@
/**
* File: min_path_sum.cs
* Created Time: 2023-07-10
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class min_path_sum {
/* 最小経路和:全探索 */
int MinPathSumDFS(int[][] grid, int i, int j) {
// 左上のセルなら探索を終了する
if (i == 0 && j == 0) {
return grid[0][0];
}
// 行または列のインデックスが範囲外なら、コスト +∞ を返す
if (i < 0 || j < 0) {
return int.MaxValue;
}
// 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する
int up = MinPathSumDFS(grid, i - 1, j);
int left = MinPathSumDFS(grid, i, j - 1);
// 左上隅から (i, j) までの最小経路コストを返す
return Math.Min(left, up) + grid[i][j];
}
/* 最小経路和:メモ化探索 */
int MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) {
// 左上のセルなら探索を終了する
if (i == 0 && j == 0) {
return grid[0][0];
}
// 行または列のインデックスが範囲外なら、コスト +∞ を返す
if (i < 0 || j < 0) {
return int.MaxValue;
}
// 既に記録があればそのまま返す
if (mem[i][j] != -1) {
return mem[i][j];
}
// 左と上のセルからの最小経路コスト
int up = MinPathSumDFSMem(grid, mem, i - 1, j);
int left = MinPathSumDFSMem(grid, mem, i, j - 1);
// 左上から (i, j) までの最小経路コストを記録して返す
mem[i][j] = Math.Min(left, up) + grid[i][j];
return mem[i][j];
}
/* 最小経路和:動的計画法 */
int MinPathSumDP(int[][] grid) {
int n = grid.Length, m = grid[0].Length;
// dp テーブルを初期化
int[,] dp = new int[n, m];
dp[0, 0] = grid[0][0];
// 状態遷移:先頭行
for (int j = 1; j < m; j++) {
dp[0, j] = dp[0, j - 1] + grid[0][j];
}
// 状態遷移:先頭列
for (int i = 1; i < n; i++) {
dp[i, 0] = dp[i - 1, 0] + grid[i][0];
}
// 状態遷移: 残りの行と列
for (int i = 1; i < n; i++) {
for (int j = 1; j < m; j++) {
dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j];
}
}
return dp[n - 1, m - 1];
}
/* 最小経路和:空間最適化後の動的計画法 */
int MinPathSumDPComp(int[][] grid) {
int n = grid.Length, m = grid[0].Length;
// dp テーブルを初期化
int[] dp = new int[m];
dp[0] = grid[0][0];
// 状態遷移:先頭行
for (int j = 1; j < m; j++) {
dp[j] = dp[j - 1] + grid[0][j];
}
// 状態遷移:残りの行
for (int i = 1; i < n; i++) {
// 状態遷移:先頭列
dp[0] = dp[0] + grid[i][0];
// 状態遷移:残りの列
for (int j = 1; j < m; j++) {
dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j];
}
}
return dp[m - 1];
}
[Test]
public void Test() {
int[][] grid =
[
[1, 3, 1, 5],
[2, 2, 4, 2],
[5, 3, 2, 1],
[4, 3, 5, 2]
];
int n = grid.Length, m = grid[0].Length;
// 全探索
int res = MinPathSumDFS(grid, n - 1, m - 1);
Console.WriteLine("左上から右下までの最小経路和は " + res);
// メモ化探索
int[][] mem = new int[n][];
for (int i = 0; i < n; i++) {
mem[i] = new int[m];
Array.Fill(mem[i], -1);
}
res = MinPathSumDFSMem(grid, mem, n - 1, m - 1);
Console.WriteLine("左上から右下までの最小経路和は " + res);
// 動的計画法
res = MinPathSumDP(grid);
Console.WriteLine("左上から右下までの最小経路和は " + res);
// 空間最適化後の動的計画法
res = MinPathSumDPComp(grid);
Console.WriteLine("左上から右下までの最小経路和は " + res);
}
}
@@ -0,0 +1,64 @@
/**
* File: unbounded_knapsack.cs
* Created Time: 2023-07-12
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_dynamic_programming;
public class unbounded_knapsack {
/* 完全ナップサック問題:動的計画法 */
int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) {
int n = wgt.Length;
// dp テーブルを初期化
int[,] dp = new int[n + 1, cap + 1];
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int c = 1; c <= cap; c++) {
if (wgt[i - 1] > c) {
// ナップサック容量を超えるなら品物 i は選ばない
dp[i, c] = dp[i - 1, c];
} else {
// 品物 i を選ばない場合と選ぶ場合の大きい方
dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]);
}
}
}
return dp[n, cap];
}
/* 完全ナップサック問題:空間最適化後の動的計画法 */
int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {
int n = wgt.Length;
// dp テーブルを初期化
int[] dp = new int[cap + 1];
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int c = 1; c <= cap; c++) {
if (wgt[i - 1] > c) {
// ナップサック容量を超えるなら品物 i は選ばない
dp[c] = dp[c];
} else {
// 品物 i を選ばない場合と選ぶ場合の大きい方
dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);
}
}
}
return dp[cap];
}
[Test]
public void Test() {
int[] wgt = [1, 2, 3];
int[] val = [5, 11, 15];
int cap = 4;
// 動的計画法
int res = UnboundedKnapsackDP(wgt, val, cap);
Console.WriteLine("バックパック容量を超えない最大価値は " + res);
// 空間最適化後の動的計画法
res = UnboundedKnapsackDPComp(wgt, val, cap);
Console.WriteLine("バックパック容量を超えない最大価値は " + res);
}
}
@@ -0,0 +1,122 @@
/**
* File: graph_adjacency_list.cs
* Created Time: 2023-02-06
* Author: zjkung1123 (zjkung1123@gmail.com)
*/
namespace hello_algo.chapter_graph;
/* 隣接リストに基づく無向グラフクラス */
public class GraphAdjList {
// 隣接リスト。key は頂点、value はその頂点に隣接する全頂点
public Dictionary<Vertex, List<Vertex>> 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<Vertex> list in adjList.Values) {
list.Remove(vet);
}
}
/* 隣接リストを出力 */
public void Print() {
Console.WriteLine("隣接リスト =");
foreach (KeyValuePair<Vertex, List<Vertex>> pair in adjList) {
List<int> 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();
}
}
@@ -0,0 +1,137 @@
/**
* File: graph_adjacency_matrix.cs
* Created Time: 2023-02-06
* Author: zjkung1123 (zjkung1123@gmail.com)
*/
namespace hello_algo.chapter_graph;
/* 隣接行列に基づく無向グラフクラス */
class GraphAdjMat {
List<int> vertices; // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す
List<List<int>> 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<int> newRow = new(n);
for (int j = 0; j < n; j++) {
newRow.Add(0);
}
adjMat.Add(newRow);
// 隣接行列に 1 列追加
foreach (List<int> 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<int> 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();
}
}
@@ -0,0 +1,58 @@
/**
* File: graph_bfs.cs
* Created Time: 2023-03-08
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_graph;
public class graph_bfs {
/* 幅優先探索 */
// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする
List<Vertex> GraphBFS(GraphAdjList graph, Vertex startVet) {
// 頂点の走査順序
List<Vertex> res = [];
// 訪問済み頂点を記録するためのハッシュ集合
HashSet<Vertex> visited = [startVet];
// BFS の実装にキューを用いる
Queue<Vertex> 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<Vertex> res = GraphBFS(graph, v[0]);
Console.WriteLine("\n幅優先探索(BFS)の頂点順序は");
Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res)));
}
}
@@ -0,0 +1,54 @@
/**
* File: graph_dfs.cs
* Created Time: 2023-03-08
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_graph;
public class graph_dfs {
/* 深さ優先走査の補助関数 */
void DFS(GraphAdjList graph, HashSet<Vertex> visited, List<Vertex> 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<Vertex> GraphDFS(GraphAdjList graph, Vertex startVet) {
// 頂点の走査順序
List<Vertex> res = [];
// 訪問済み頂点を記録するためのハッシュ集合
HashSet<Vertex> 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<Vertex> res = GraphDFS(graph, v[0]);
Console.WriteLine("\n深さ優先探索(DFS)の頂点順序は");
Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res)));
}
}
@@ -0,0 +1,54 @@
/**
* File: coin_change_greedy.cs
* Created Time: 2023-07-21
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_greedy;
public class coin_change_greedy {
/* コイン交換:貪欲法 */
int CoinChangeGreedy(int[] coins, int amt) {
// coins リストはソート済みと仮定する
int i = coins.Length - 1;
int count = 0;
// 残額がなくなるまで貪欲選択を繰り返す
while (amt > 0) {
// 残額以下で最も近い硬貨を見つける
while (i > 0 && coins[i] > amt) {
i--;
}
// coins[i] を選択する
amt -= coins[i];
count++;
}
// 実行可能な解が見つからなければ -1 を返す
return amt == 0 ? count : -1;
}
[Test]
public void Test() {
// 貪欲法:大域最適解を保証できる
int[] coins = [1, 5, 10, 20, 50, 100];
int amt = 186;
int res = CoinChangeGreedy(coins, amt);
Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt);
Console.WriteLine("金額 " + amt + " を作るのに必要な最小硬貨枚数は " + res);
// 貪欲法:大域最適解を保証できない
coins = [1, 20, 50];
amt = 60;
res = CoinChangeGreedy(coins, amt);
Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt);
Console.WriteLine("金額 " + amt + " を作るのに必要な最小硬貨枚数は " + res);
Console.WriteLine("実際に必要な最小枚数は 3、つまり 20 + 20 + 20");
// 貪欲法:大域最適解を保証できない
coins = [1, 49, 50];
amt = 98;
res = CoinChangeGreedy(coins, amt);
Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt);
Console.WriteLine("金額 " + amt + " を作るのに必要な最小硬貨枚数は " + res);
Console.WriteLine("実際に必要な最小枚数は 2、つまり 49 + 49");
}
}
@@ -0,0 +1,52 @@
/**
* File: fractional_knapsack.cs
* Created Time: 2023-07-21
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_greedy;
/* 品物 */
class Item(int w, int v) {
public int w = w; // 品物の重さ
public int v = v; // 品物の価値
}
public class fractional_knapsack {
/* 分数ナップサック:貪欲法 */
double FractionalKnapsack(int[] wgt, int[] val, int cap) {
// 重さと価値の 2 属性を持つ品物リストを作成
Item[] items = new Item[wgt.Length];
for (int i = 0; i < wgt.Length; i++) {
items[i] = new Item(wgt[i], val[i]);
}
// 単位価値 item.v / item.w の高い順にソートする
Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w));
// 貪欲選択を繰り返す
double res = 0;
foreach (Item item in items) {
if (item.w <= cap) {
// 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる
res += item.v;
cap -= item.w;
} else {
// 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる
res += (double)item.v / item.w * cap;
// 残り容量がないため、ループを抜ける
break;
}
}
return res;
}
[Test]
public void Test() {
int[] wgt = [10, 20, 30, 40, 50];
int[] val = [50, 120, 150, 210, 240];
int cap = 50;
// 貪欲法
double res = FractionalKnapsack(wgt, val, cap);
Console.WriteLine("バックパック容量を超えない最大価値は " + res);
}
}
@@ -0,0 +1,39 @@
/**
* File: max_capacity.cs
* Created Time: 2023-07-21
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_greedy;
public class max_capacity {
/* 最大容量:貪欲法 */
int MaxCapacity(int[] ht) {
// i, j を初期化し、それぞれ配列の両端に置く
int i = 0, j = ht.Length - 1;
// 初期の最大容量は 0
int res = 0;
// 2 枚の板が出会うまで貪欲選択を繰り返す
while (i < j) {
// 最大容量を更新する
int cap = Math.Min(ht[i], ht[j]) * (j - i);
res = Math.Max(res, cap);
// 短い方を内側へ動かす
if (ht[i] < ht[j]) {
i++;
} else {
j--;
}
}
return res;
}
[Test]
public void Test() {
int[] ht = [3, 8, 5, 2, 7, 7, 3, 4];
// 貪欲法
int res = MaxCapacity(ht);
Console.WriteLine("最大容量は " + res);
}
}
@@ -0,0 +1,39 @@
/**
* File: max_product_cutting.cs
* Created Time: 2023-07-21
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_greedy;
public class max_product_cutting {
/* 最大切断積:貪欲法 */
int MaxProductCutting(int n) {
// n <= 3 のときは、必ず 1 を切り出す
if (n <= 3) {
return 1 * (n - 1);
}
// 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする
int a = n / 3;
int b = n % 3;
if (b == 1) {
// 余りが 1 のときは、1 * 3 を 2 * 2 に変える
return (int)Math.Pow(3, a - 1) * 2 * 2;
}
if (b == 2) {
// 余りが 2 のときは、そのままにする
return (int)Math.Pow(3, a) * 2;
}
// 余りが 0 のときは、そのままにする
return (int)Math.Pow(3, a);
}
[Test]
public void Test() {
int n = 58;
// 貪欲法
int res = MaxProductCutting(n);
Console.WriteLine("最大分割積は" + res);
}
}
@@ -0,0 +1,134 @@
/**
* File: array_hash_map.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_hashing;
/* キーと値の組 int->string */
class Pair(int key, string val) {
public int key = key;
public string val = val;
}
/* 配列ベースのハッシュテーブル */
class ArrayHashMap {
List<Pair?> 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<Pair> PairSet() {
List<Pair> pairSet = [];
foreach (Pair? pair in buckets) {
if (pair != null)
pairSet.Add(pair);
}
return pairSet;
}
/* すべてのキーを取得 */
public List<int> KeySet() {
List<int> keySet = [];
foreach (Pair? pair in buckets) {
if (pair != null)
keySet.Add(pair.key);
}
return keySet;
}
/* すべての値を取得 */
public List<string> ValueSet() {
List<string> 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);
}
}
}
@@ -0,0 +1,36 @@
/**
* File: built_in_hash.cs
* Created Time: 2023-06-26
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_hashing;
public class built_in_hash {
[Test]
public void Test() {
int num = 3;
int hashNum = num.GetHashCode();
Console.WriteLine("整数 " + num + " のハッシュ値は " + hashNum);
bool bol = true;
int hashBol = bol.GetHashCode();
Console.WriteLine("真偽値 " + bol + " のハッシュ値は " + hashBol);
double dec = 3.14159;
int hashDec = dec.GetHashCode();
Console.WriteLine("小数 " + dec + " のハッシュ値は " + hashDec);
string str = "Hello アルゴリズム";
int hashStr = str.GetHashCode();
Console.WriteLine("文字列 " + str + " のハッシュ値は " + hashStr);
object[] arr = [12836, "シャオハー"];
int hashTup = arr.GetHashCode();
Console.WriteLine("配列 [" + string.Join(", ", arr) + "] のハッシュ値は " + hashTup);
ListNode obj = new(0);
int hashObj = obj.GetHashCode();
Console.WriteLine("ノードオブジェクト " + obj + " のハッシュ値は " + hashObj);
}
}
@@ -0,0 +1,51 @@
/**
* File: hash_map.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_hashing;
public class hash_map {
[Test]
public void Test() {
/* ハッシュテーブルを初期化 */
Dictionary<int, string> 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);
}
}
}
@@ -0,0 +1,144 @@
/**
* File: hash_map_chaining.cs
* Created Time: 2023-06-26
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_hashing;
/* チェイン法ハッシュテーブル */
class HashMapChaining {
int size; // キーと値のペア数
int capacity; // ハッシュテーブル容量
double loadThres; // リサイズを発動する負荷率のしきい値
int extendRatio; // 拡張倍率
List<List<Pair>> buckets; // バケット配列
/* コンストラクタ */
public HashMapChaining() {
size = 0;
capacity = 4;
loadThres = 2.0 / 3.0;
extendRatio = 2;
buckets = new List<List<Pair>>(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<List<Pair>> bucketsTmp = buckets;
// リサイズ後の新しいハッシュテーブルを初期化
capacity *= extendRatio;
buckets = new List<List<Pair>>(capacity);
for (int i = 0; i < capacity; i++) {
buckets.Add([]);
}
size = 0;
// キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す
foreach (List<Pair> bucket in bucketsTmp) {
foreach (Pair pair in bucket) {
Put(pair.key, pair.val);
}
}
}
/* ハッシュテーブルを出力 */
public void Print() {
foreach (List<Pair> bucket in buckets) {
List<string> 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();
}
}
@@ -0,0 +1,159 @@
/**
* File: hash_map_open_addressing.cs
* Created Time: 2023-06-26
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_hashing;
/* オープンアドレス法ハッシュテーブル */
class HashMapOpenAddressing {
int size; // キーと値のペア数
int capacity = 4; // ハッシュテーブル容量
double loadThres = 2.0 / 3.0; // リサイズを発動する負荷率のしきい値
int extendRatio = 2; // 拡張倍率
Pair[] buckets; // バケット配列
Pair TOMBSTONE = new(-1, "-1"); // 削除済みマーク
/* コンストラクタ */
public HashMapOpenAddressing() {
size = 0;
buckets = new Pair[capacity];
}
/* ハッシュ関数 */
int HashFunc(int key) {
return key % capacity;
}
/* 負荷率 */
double LoadFactor() {
return (double)size / capacity;
}
/* key に対応するバケットインデックスを探す */
int FindBucket(int key) {
int index = HashFunc(key);
int firstTombstone = -1;
// 線形プロービングを行い、空バケットに達したら終了
while (buckets[index] != null) {
// key が見つかったら、対応するバケットのインデックスを返す
if (buckets[index].key == key) {
// 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動
if (firstTombstone != -1) {
buckets[firstTombstone] = buckets[index];
buckets[index] = TOMBSTONE;
return firstTombstone; // 移動後のバケットインデックスを返す
}
return index; // バケットのインデックスを返す
}
// 最初に見つかった削除マークを記録
if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {
firstTombstone = index;
}
// バケットのインデックスを計算し、末尾を越えたら先頭に戻る
index = (index + 1) % capacity;
}
// key が存在しない場合は追加位置のインデックスを返す
return firstTombstone == -1 ? index : firstTombstone;
}
/* 検索操作 */
public string? Get(int key) {
// key に対応するバケットインデックスを探す
int index = FindBucket(key);
// キーと値の組が見つかったら、対応する val を返す
if (buckets[index] != null && buckets[index] != TOMBSTONE) {
return buckets[index].val;
}
// キーと値の組が存在しなければ null を返す
return null;
}
/* 追加操作 */
public void Put(int key, string val) {
// 負荷率がしきい値を超えたら、リサイズを実行
if (LoadFactor() > loadThres) {
Extend();
}
// key に対応するバケットインデックスを探す
int index = FindBucket(key);
// キーと値の組が見つかったら、val を上書きして返す
if (buckets[index] != null && buckets[index] != TOMBSTONE) {
buckets[index].val = val;
return;
}
// キーと値の組が存在しない場合は、その組を追加する
buckets[index] = new Pair(key, val);
size++;
}
/* 削除操作 */
public void Remove(int key) {
// key に対応するバケットインデックスを探す
int index = FindBucket(key);
// キーと値の組が見つかったら、削除マーカーで上書きする
if (buckets[index] != null && buckets[index] != TOMBSTONE) {
buckets[index] = TOMBSTONE;
size--;
}
}
/* ハッシュテーブルを拡張 */
void Extend() {
// 元のハッシュテーブルを一時保存
Pair[] bucketsTmp = buckets;
// リサイズ後の新しいハッシュテーブルを初期化
capacity *= extendRatio;
buckets = new Pair[capacity];
size = 0;
// キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す
foreach (Pair pair in bucketsTmp) {
if (pair != null && pair != TOMBSTONE) {
Put(pair.key, pair.val);
}
}
}
/* ハッシュテーブルを出力 */
public void Print() {
foreach (Pair pair in buckets) {
if (pair == null) {
Console.WriteLine("null");
} else if (pair == TOMBSTONE) {
Console.WriteLine("TOMBSTONE");
} else {
Console.WriteLine(pair.key + " -> " + pair.val);
}
}
}
}
public class hash_map_open_addressing {
[Test]
public void Test() {
/* ハッシュテーブルを初期化 */
HashMapOpenAddressing map = new();
/* 追加操作 */
// ハッシュテーブルにキーと値のペア (key, value) を追加
map.Put(12836, "シャオハー");
map.Put(15937, "シャオルオ");
map.Put(16750, "シャオスワン");
map.Put(13276, "シャオファー");
map.Put(10583, "シャオヤー");
Console.WriteLine("\n追加完了後、ハッシュテーブルは\nKey -> Value");
map.Print();
/* 検索操作 */
// キー key をハッシュテーブルに渡し、値 value を取得
string? name = map.Get(13276);
Console.WriteLine("\n学籍番号 13276 を入力すると、氏名 " + name);
/* 削除操作 */
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.Remove(16750);
Console.WriteLine("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value");
map.Print();
}
}
@@ -0,0 +1,66 @@
/**
* File: simple_hash.cs
* Created Time: 2023-06-26
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_hashing;
public class simple_hash {
/* 加算ハッシュ */
int AddHash(string key) {
long hash = 0;
const int MODULUS = 1000000007;
foreach (char c in key) {
hash = (hash + c) % MODULUS;
}
return (int)hash;
}
/* 乗算ハッシュ */
int MulHash(string key) {
long hash = 0;
const int MODULUS = 1000000007;
foreach (char c in key) {
hash = (31 * hash + c) % MODULUS;
}
return (int)hash;
}
/* XOR ハッシュ */
int XorHash(string key) {
int hash = 0;
const int MODULUS = 1000000007;
foreach (char c in key) {
hash ^= c;
}
return hash & MODULUS;
}
/* 回転ハッシュ */
int RotHash(string key) {
long hash = 0;
const int MODULUS = 1000000007;
foreach (char c in key) {
hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS;
}
return (int)hash;
}
[Test]
public void Test() {
string key = "Hello アルゴリズム";
int hash = AddHash(key);
Console.WriteLine("加算ハッシュ値は " + hash);
hash = MulHash(key);
Console.WriteLine("乗算ハッシュ値は " + hash);
hash = XorHash(key);
Console.WriteLine("XOR ハッシュ値は " + hash);
hash = RotHash(key);
Console.WriteLine("回転ハッシュ値は " + hash);
}
}
+64
View File
@@ -0,0 +1,64 @@
/**
* File: heap.cs
* Created Time: 2023-02-06
* Author: zjkung1123 (zjkung1123@gmail.com)
*/
namespace hello_algo.chapter_heap;
public class heap {
void TestPush(PriorityQueue<int, int> heap, int val) {
heap.Enqueue(val, val); // 要素をヒープに追加
Console.WriteLine($"\n要素 {val} をヒープに追加した後\n");
PrintUtil.PrintHeap(heap);
}
void TestPop(PriorityQueue<int, int> heap) {
int val = heap.Dequeue(); // ヒープ頂点の要素を取り出す
Console.WriteLine($"\nヒープの先頭要素 {val} を取り出した後\n");
PrintUtil.PrintHeap(heap);
}
[Test]
public void Test() {
/* ヒープを初期化 */
// 最小ヒープを初期化
PriorityQueue<int, int> minHeap = new();
// 最大ヒープを初期化する(Comparer はラムダ式で変更できる)
PriorityQueue<int, int> maxHeap = new(Comparer<int>.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<int, int>(list.Select(x => (x, x)));
Console.WriteLine("リストを入力して最小ヒープを構築した後");
PrintUtil.PrintHeap(minHeap);
}
}
+160
View File
@@ -0,0 +1,160 @@
/**
* File: my_heap.cs
* Created Time: 2023-02-06
* Author: zjkung1123 (zjkung1123@gmail.com)
*/
namespace hello_algo.chapter_heap;
/* 最大ヒープ */
class MaxHeap {
// 配列ではなくリストを使うことで、拡張を考慮する必要がない
List<int> maxHeap;
/* コンストラクタ。空のヒープを作成する */
public MaxHeap() {
maxHeap = [];
}
/* コンストラクタ。入力リストに基づいてヒープを構築 */
public MaxHeap(IEnumerable<int> nums) {
// リスト要素をそのままヒープに追加
maxHeap = new List<int>(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<int>(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}");
}
}
+37
View File
@@ -0,0 +1,37 @@
/**
* File: top_k.cs
* Created Time: 2023-06-14
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_heap;
public class top_k {
/* ヒープに基づいて配列中の最大の k 個の要素を探す */
PriorityQueue<int, int> TopKHeap(int[] nums, int k) {
// 最小ヒープを初期化
PriorityQueue<int, int> 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<int, int> res = TopKHeap(nums, k);
Console.WriteLine("最大の " + k + " 個の要素は");
PrintUtil.PrintHeap(res);
}
}
@@ -0,0 +1,59 @@
/**
* File: binary_search.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_searching;
public class binary_search {
/* 二分探索(両閉区間) */
int BinarySearch(int[] nums, int target) {
// 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
int i = 0, j = nums.Length - 1;
// ループし、探索区間が空になったら終了する(i > j で空)
while (i <= j) {
int m = i + (j - i) / 2; // 中点インデックス m を計算
if (nums[m] < target) // この場合、target は区間 [m+1, j] にある
i = m + 1;
else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある
j = m - 1;
else // 目標要素が見つかったらそのインデックスを返す
return m;
}
// 目標要素が見つからなければ -1 を返す
return -1;
}
/* 二分探索(左閉右開区間) */
int BinarySearchLCRO(int[] nums, int target) {
// 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
int i = 0, j = nums.Length;
// ループし、探索区間が空になったら終了する(i = j で空)
while (i < j) {
int m = i + (j - i) / 2; // 中点インデックス m を計算
if (nums[m] < target) // この場合、target は区間 [m+1, j) にある
i = m + 1;
else if (nums[m] > target) // この場合、target は区間 [i, m) にある
j = m;
else // 目標要素が見つかったらそのインデックスを返す
return m;
}
// 目標要素が見つからなければ -1 を返す
return -1;
}
[Test]
public void Test() {
int target = 6;
int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35];
/* 二分探索(両閉区間) */
int index = BinarySearch(nums, target);
Console.WriteLine("対象要素 6 のインデックス = " + index);
/* 二分探索(左閉右開区間) */
index = BinarySearchLCRO(nums, target);
Console.WriteLine("対象要素 6 のインデックス = " + index);
}
}
@@ -0,0 +1,50 @@
/**
* File: binary_search_edge.cs
* Created Time: 2023-08-06
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_searching;
public class binary_search_edge {
/* 最も左の target を二分探索 */
int BinarySearchLeftEdge(int[] nums, int target) {
// target の挿入位置を探すのと等価
int i = binary_search_insertion.BinarySearchInsertion(nums, target);
// target が見つからなければ、-1 を返す
if (i == nums.Length || nums[i] != target) {
return -1;
}
// target が見つかったら、インデックス i を返す
return i;
}
/* 最も右の target を二分探索 */
int BinarySearchRightEdge(int[] nums, int target) {
// 最左の target + 1 を探す問題に変換する
int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1);
// j は最も右の target を指し、i は target より大きい最初の要素を指す
int j = i - 1;
// target が見つからなければ、-1 を返す
if (j == -1 || nums[j] != target) {
return -1;
}
// target が見つかったら、インデックス j を返す
return j;
}
[Test]
public void Test() {
// 重複要素を含む配列
int[] nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15];
Console.WriteLine("\n配列 nums = " + nums.PrintList());
// 二分探索で左端と右端を探す
foreach (int target in new int[] { 6, 7 }) {
int index = BinarySearchLeftEdge(nums, target);
Console.WriteLine("一番左の要素 " + target + " のインデックスは " + index);
index = BinarySearchRightEdge(nums, target);
Console.WriteLine("一番右の要素 " + target + " のインデックスは " + index);
}
}
}
@@ -0,0 +1,64 @@
/**
* File: binary_search_insertion.cs
* Created Time: 2023-08-06
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_searching;
public class binary_search_insertion {
/* 二分探索で挿入位置を探す(重複要素なし) */
public static int BinarySearchInsertionSimple(int[] nums, int target) {
int i = 0, j = nums.Length - 1; // 両閉区間 [0, n-1] を初期化
while (i <= j) {
int m = i + (j - i) / 2; // 中点インデックス m を計算
if (nums[m] < target) {
i = m + 1; // target は区間 [m+1, j] にある
} else if (nums[m] > target) {
j = m - 1; // target は区間 [i, m-1] にある
} else {
return m; // target が見つかったら、挿入位置 m を返す
}
}
// target が見つからなければ、挿入位置 i を返す
return i;
}
/* 二分探索で挿入位置を探す(重複要素あり) */
public static int BinarySearchInsertion(int[] nums, int target) {
int i = 0, j = nums.Length - 1; // 両閉区間 [0, n-1] を初期化
while (i <= j) {
int m = i + (j - i) / 2; // 中点インデックス m を計算
if (nums[m] < target) {
i = m + 1; // target は区間 [m+1, j] にある
} else if (nums[m] > target) {
j = m - 1; // target は区間 [i, m-1] にある
} else {
j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある
}
}
// 挿入位置 i を返す
return i;
}
[Test]
public void Test() {
// 重複要素のない配列
int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35];
Console.WriteLine("\n配列 nums = " + nums.PrintList());
// 二分探索で挿入位置を探す
foreach (int target in new int[] { 6, 9 }) {
int index = BinarySearchInsertionSimple(nums, target);
Console.WriteLine("要素 " + target + " の挿入位置のインデックスは " + index);
}
// 重複要素を含む配列
nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15];
Console.WriteLine("\n配列 nums = " + nums.PrintList());
// 二分探索で挿入位置を探す
foreach (int target in new int[] { 2, 6, 20 }) {
int index = BinarySearchInsertion(nums, target);
Console.WriteLine("要素 " + target + " の挿入位置のインデックスは " + index);
}
}
}
@@ -0,0 +1,50 @@
/**
* File: hashing_search.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_searching;
public class hashing_search {
/* ハッシュ探索(配列) */
int HashingSearchArray(Dictionary<int, int> map, int target) {
// ハッシュテーブルの key: 目標要素、value: インデックス
// ハッシュテーブルにこの key がなければ -1 を返す
return map.GetValueOrDefault(target, -1);
}
/* ハッシュ探索(連結リスト) */
ListNode? HashingSearchLinkedList(Dictionary<int, ListNode> 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<int, int> 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<int, ListNode> map1 = [];
while (head != null) {
map1[head.val] = head; // key: ノード値、value: ノード
head = head.next;
}
ListNode? node = HashingSearchLinkedList(map1, target);
Console.WriteLine("目標ノード値 3 に対応するノードオブジェクトは " + node);
}
}
@@ -0,0 +1,49 @@
/**
* File: linear_search.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_searching;
public class linear_search {
/* 線形探索(配列) */
int LinearSearchArray(int[] nums, int target) {
// 配列を走査
for (int i = 0; i < nums.Length; i++) {
// 目標要素が見つかったらそのインデックスを返す
if (nums[i] == target)
return i;
}
// 目標要素が見つからなければ -1 を返す
return -1;
}
/* 線形探索(連結リスト) */
ListNode? LinearSearchLinkedList(ListNode? head, int target) {
// 連結リストを走査
while (head != null) {
// 対象ノードが見つかったら、それを返す
if (head.val == target)
return head;
head = head.next;
}
// 対象ノードが見つからない場合は null を返す
return null;
}
[Test]
public void Test() {
int target = 3;
/* 配列で線形探索を行う */
int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8];
int index = LinearSearchArray(nums, target);
Console.WriteLine("対象要素 3 のインデックス = " + index);
/* 連結リストで線形探索を行う */
ListNode? head = ListNode.ArrToLinkedList(nums);
ListNode? node = LinearSearchLinkedList(head, target);
Console.WriteLine("目標ノード値 3 に対応するノードオブジェクトは " + node);
}
}
@@ -0,0 +1,52 @@
/**
* File: two_sum.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_searching;
public class two_sum {
/* 方法 1:総当たり列挙 */
int[] TwoSumBruteForce(int[] nums, int target) {
int size = nums.Length;
// 2重ループのため、時間計算量は O(n^2)
for (int i = 0; i < size - 1; i++) {
for (int j = i + 1; j < size; j++) {
if (nums[i] + nums[j] == target)
return [i, j];
}
}
return [];
}
/* 方法 2:補助ハッシュテーブル */
int[] TwoSumHashTable(int[] nums, int target) {
int size = nums.Length;
// 補助ハッシュテーブルを使用し、空間計算量は O(n)
Dictionary<int, int> 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));
}
}
@@ -0,0 +1,51 @@
/**
* File: bubble_sort.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_sorting;
public class bubble_sort {
/* バブルソート */
void BubbleSort(int[] nums) {
// 外側のループ:未ソート区間は [0, i]
for (int i = nums.Length - 1; i > 0; i--) {
// 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// nums[j] と nums[j + 1] を交換
(nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);
}
}
}
}
/* バブルソート(フラグ最適化) */
void BubbleSortWithFlag(int[] nums) {
// 外側のループ:未ソート区間は [0, i]
for (int i = nums.Length - 1; i > 0; i--) {
bool flag = false; // フラグを初期化する
// 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// nums[j] と nums[j + 1] を交換
(nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);
flag = true; // 交換する要素を記録
}
}
if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了
}
}
[Test]
public void Test() {
int[] nums = [4, 1, 3, 1, 5, 2];
BubbleSort(nums);
Console.WriteLine("バブルソート完了後 nums = " + string.Join(",", nums));
int[] nums1 = [4, 1, 3, 1, 5, 2];
BubbleSortWithFlag(nums1);
Console.WriteLine("バブルソート完了後 nums1 = " + string.Join(",", nums1));
}
}
@@ -0,0 +1,46 @@
/**
* File: bucket_sort.cs
* Created Time: 2023-04-13
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_sorting;
public class bucket_sort {
/* バケットソート */
void BucketSort(float[] nums) {
// k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
int k = nums.Length / 2;
List<List<float>> 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<float> bucket in buckets) {
// 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
bucket.Sort();
}
// 3. バケットを走査して結果を結合
int j = 0;
foreach (List<float> 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));
}
}
@@ -0,0 +1,77 @@
/**
* File: counting_sort.cs
* Created Time: 2023-04-13
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_sorting;
public class counting_sort {
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使えない
void CountingSortNaive(int[] nums) {
// 1. 配列の最大要素 m を求める
int m = 0;
foreach (int num in nums) {
m = Math.Max(m, num);
}
// 2. 各数値の出現回数を数える
// counter[num] は num の出現回数を表す
int[] counter = new int[m + 1];
foreach (int num in nums) {
counter[num]++;
}
// 3. counter を走査し、各要素を元の配列 nums に書き戻す
int i = 0;
for (int num = 0; num < m + 1; num++) {
for (int j = 0; j < counter[num]; j++, i++) {
nums[i] = num;
}
}
}
/* 計数ソート */
// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
void CountingSort(int[] nums) {
// 1. 配列の最大要素 m を求める
int m = 0;
foreach (int num in nums) {
m = Math.Max(m, num);
}
// 2. 各数値の出現回数を数える
// counter[num] は num の出現回数を表す
int[] counter = new int[m + 1];
foreach (int num in nums) {
counter[num]++;
}
// 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
// つまり counter[num]-1 は、num が res に最後に現れるインデックス
for (int i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. nums を逆順に走査し、各要素を結果配列 res に格納する
// 結果を記録するための配列 res を初期化
int n = nums.Length;
int[] res = new int[n];
for (int i = n - 1; i >= 0; i--) {
int num = nums[i];
res[counter[num] - 1] = num; // num を対応するインデックスに配置
counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る
}
// 結果配列 res で元の配列 nums を上書きする
for (int i = 0; i < n; i++) {
nums[i] = res[i];
}
}
[Test]
public void Test() {
int[] nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4];
CountingSortNaive(nums);
Console.WriteLine("カウントソート(オブジェクトはソート不可)完了後 nums = " + string.Join(" ", nums));
int[] nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4];
CountingSort(nums1);
Console.WriteLine("カウントソート完了後 nums1 = " + string.Join(" ", nums));
}
}
@@ -0,0 +1,52 @@
/**
* File: heap_sort.cs
* Created Time: 2023-06-01
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_sorting;
public class heap_sort {
/* ヒープの長さは n。ノード i から下方向にヒープ化 */
void SiftDown(int[] nums, int n, int i) {
while (true) {
// ノード i, l, r のうち値が最大のノードを ma とする
int l = 2 * i + 1;
int r = 2 * i + 2;
int ma = i;
if (l < n && nums[l] > nums[ma])
ma = l;
if (r < n && nums[r] > nums[ma])
ma = r;
// ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
if (ma == i)
break;
// 2 つのノードを交換
(nums[ma], nums[i]) = (nums[i], nums[ma]);
// ループで上から下へヒープ化
i = ma;
}
}
/* ヒープソート */
void HeapSort(int[] nums) {
// ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
for (int i = nums.Length / 2 - 1; i >= 0; i--) {
SiftDown(nums, nums.Length, i);
}
// ヒープから最大要素を取り出し、n-1 回繰り返す
for (int i = nums.Length - 1; i > 0; i--) {
// 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
(nums[i], nums[0]) = (nums[0], nums[i]);
// 根ノードを起点に、上から下へヒープ化
SiftDown(nums, i, 0);
}
}
[Test]
public void Test() {
int[] nums = [4, 1, 3, 1, 5, 2];
HeapSort(nums);
Console.WriteLine("ヒープソート完了後 nums = " + string.Join(" ", nums));
}
}
@@ -0,0 +1,30 @@
/**
* File: insertion_sort.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_sorting;
public class insertion_sort {
/* 挿入ソート */
void InsertionSort(int[] nums) {
// 外側ループ:整列済み区間は [0, i-1]
for (int i = 1; i < nums.Length; i++) {
int bas = nums[i], j = i - 1;
// 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
while (j >= 0 && nums[j] > bas) {
nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する
j--;
}
nums[j + 1] = bas; // base を正しい位置に配置する
}
}
[Test]
public void Test() {
int[] nums = [4, 1, 3, 1, 5, 2];
InsertionSort(nums);
Console.WriteLine("挿入ソート完了後 nums = " + string.Join(",", nums));
}
}
@@ -0,0 +1,56 @@
/**
* File: merge_sort.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_sorting;
public class merge_sort {
/* 左部分配列と右部分配列をマージ */
void Merge(int[] nums, int left, int mid, int right) {
// 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
// マージ結果を格納する一時配列 tmp を作成
int[] tmp = new int[right - left + 1];
// 左右の部分配列の開始インデックスを初期化する
int i = left, j = mid + 1, k = 0;
// 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
while (i <= mid && j <= right) {
if (nums[i] <= nums[j])
tmp[k++] = nums[i++];
else
tmp[k++] = nums[j++];
}
// 左右の部分配列の残り要素を一時配列にコピーする
while (i <= mid) {
tmp[k++] = nums[i++];
}
while (j <= right) {
tmp[k++] = nums[j++];
}
// 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
for (k = 0; k < tmp.Length; ++k) {
nums[left + k] = tmp[k];
}
}
/* マージソート */
void MergeSort(int[] nums, int left, int right) {
// 終了条件
if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了
// 分割フェーズ
int mid = left + (right - left) / 2; // 中点を計算
MergeSort(nums, left, mid); // 左部分配列を再帰処理
MergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
// マージフェーズ
Merge(nums, left, mid, right);
}
[Test]
public void Test() {
/* マージソート */
int[] nums = [7, 3, 2, 6, 0, 1, 5, 4];
MergeSort(nums, 0, nums.Length - 1);
Console.WriteLine("マージソート完了後 nums = " + string.Join(",", nums));
}
}
@@ -0,0 +1,150 @@
/**
* File: quick_sort.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_sorting;
class quickSort {
/* 要素の交換 */
static void Swap(int[] nums, int i, int j) {
(nums[j], nums[i]) = (nums[i], nums[j]);
}
/* 番兵分割 */
static int Partition(int[] nums, int left, int right) {
// nums[left] を基準値とする
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--; // 右から左へ基準値未満の最初の要素を探す
while (i < j && nums[i] <= nums[left])
i++; // 左から右へ基準値より大きい最初の要素を探す
Swap(nums, i, j); // この 2 つの要素を交換
}
Swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する
return i; // 基準値のインデックスを返す
}
/* クイックソート */
public static void QuickSort(int[] nums, int left, int right) {
// 部分配列の長さが 1 なら再帰を終了する
if (left >= right)
return;
// 番兵分割
int pivot = Partition(nums, left, right);
// 左右の部分配列を再帰処理
QuickSort(nums, left, pivot - 1);
QuickSort(nums, pivot + 1, right);
}
}
/* クイックソートクラス(中央値ピボット最適化) */
class QuickSortMedian {
/* 要素の交換 */
static void Swap(int[] nums, int i, int j) {
(nums[j], nums[i]) = (nums[i], nums[j]);
}
/* 3つの候補要素の中央値を選ぶ */
static int MedianThree(int[] nums, int left, int mid, int right) {
int l = nums[left], m = nums[mid], r = nums[right];
if ((l <= m && m <= r) || (r <= m && m <= l))
return mid; // m は l と r の間
if ((m <= l && l <= r) || (r <= l && l <= m))
return left; // l は m と r の間
return right;
}
/* 番兵による分割処理(3 点中央値) */
static int Partition(int[] nums, int left, int right) {
// 3つの候補要素の中央値を選ぶ
int med = MedianThree(nums, left, (left + right) / 2, right);
// 中央値を配列の最左端に交換する
Swap(nums, left, med);
// nums[left] を基準値とする
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--; // 右から左へ基準値未満の最初の要素を探す
while (i < j && nums[i] <= nums[left])
i++; // 左から右へ基準値より大きい最初の要素を探す
Swap(nums, i, j); // この 2 つの要素を交換
}
Swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する
return i; // 基準値のインデックスを返す
}
/* クイックソート */
public static void QuickSort(int[] nums, int left, int right) {
// 部分配列の長さが 1 なら再帰を終了する
if (left >= right)
return;
// 番兵分割
int pivot = Partition(nums, left, right);
// 左右の部分配列を再帰処理
QuickSort(nums, left, pivot - 1);
QuickSort(nums, pivot + 1, right);
}
}
/* クイックソートクラス(再帰深度最適化) */
class QuickSortTailCall {
/* 要素の交換 */
static void Swap(int[] nums, int i, int j) {
(nums[j], nums[i]) = (nums[i], nums[j]);
}
/* 番兵分割 */
static int Partition(int[] nums, int left, int right) {
// nums[left] を基準値とする
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--; // 右から左へ基準値未満の最初の要素を探す
while (i < j && nums[i] <= nums[left])
i++; // 左から右へ基準値より大きい最初の要素を探す
Swap(nums, i, j); // この 2 つの要素を交換
}
Swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する
return i; // 基準値のインデックスを返す
}
/* クイックソート(再帰深度最適化) */
public static void QuickSort(int[] nums, int left, int right) {
// 部分配列の長さが 1 なら終了
while (left < right) {
// 番兵による分割処理
int pivot = Partition(nums, left, right);
// 2 つの部分配列のうち短いほうにクイックソートを適用する
if (pivot - left < right - pivot) {
QuickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート
left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right]
} else {
QuickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート
right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1]
}
}
}
}
public class quick_sort {
[Test]
public void Test() {
/* クイックソート */
int[] nums = [2, 4, 1, 0, 3, 5];
quickSort.QuickSort(nums, 0, nums.Length - 1);
Console.WriteLine("クイックソート完了後 nums = " + string.Join(",", nums));
/* クイックソート(中央値の基準値で最適化) */
int[] nums1 = [2, 4, 1, 0, 3, 5];
QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1);
Console.WriteLine("クイックソート(中央値ピボット最適化)完了後 nums1 = " + string.Join(",", nums1));
/* クイックソート(再帰深度最適化) */
int[] nums2 = [2, 4, 1, 0, 3, 5];
QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1);
Console.WriteLine("クイックソート(再帰深度最適化)完了後 nums2 = " + string.Join(",", nums2));
}
}
@@ -0,0 +1,69 @@
/**
* File: radix_sort.cs
* Created Time: 2023-04-13
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_sorting;
public class radix_sort {
/* 要素 num の下から k 桁目を取得(exp = 10^(k-1) */
int Digit(int num, int exp) {
// ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
return (num / exp) % 10;
}
/* 計数ソート(nums の k 桁目でソート) */
void CountingSortDigit(int[] nums, int exp) {
// 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
int[] counter = new int[10];
int n = nums.Length;
// 0~9 の各数字の出現回数を集計する
for (int i = 0; i < n; i++) {
int d = Digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする
counter[d]++; // 数字 d の出現回数を数える
}
// 累積和を求め、「出現回数」を「配列インデックス」に変換する
for (int i = 1; i < 10; i++) {
counter[i] += counter[i - 1];
}
// 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
int[] res = new int[n];
for (int i = n - 1; i >= 0; i--) {
int d = Digit(nums[i], exp);
int j = counter[d] - 1; // d の配列内インデックス j を取得する
res[j] = nums[i]; // 現在の要素をインデックス j に格納する
counter[d]--; // d の個数を 1 減らす
}
// 結果で元の配列 nums を上書きする
for (int i = 0; i < n; i++) {
nums[i] = res[i];
}
}
/* 基数ソート */
void RadixSort(int[] nums) {
// 最大桁数の判定用に配列の最大要素を取得
int m = int.MinValue;
foreach (int num in nums) {
if (num > m) m = num;
}
// 下位桁から上位桁の順に走査する
for (int exp = 1; exp <= m; exp *= 10) {
// 配列要素の k 桁目に対して計数ソートを行う
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// つまり exp = 10^(k-1)
CountingSortDigit(nums, exp);
}
}
[Test]
public void Test() {
// 基数ソート
int[] nums = [ 10546151, 35663510, 42865989, 34862445, 81883077,
88906420, 72429244, 30524779, 82060337, 63832996 ];
RadixSort(nums);
Console.WriteLine("基数ソート完了後 nums = " + string.Join(" ", nums));
}
}
@@ -0,0 +1,32 @@
/**
* File: selection_sort.cs
* Created Time: 2023-06-01
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_sorting;
public class selection_sort {
/* 選択ソート */
void SelectionSort(int[] nums) {
int n = nums.Length;
// 外側ループ:未整列区間は [i, n-1]
for (int i = 0; i < n - 1; i++) {
// 内側のループ:未ソート区間の最小要素を見つける
int k = i;
for (int j = i + 1; j < n; j++) {
if (nums[j] < nums[k])
k = j; // 最小要素のインデックスを記録
}
// その最小要素を未整列区間の先頭要素と交換する
(nums[k], nums[i]) = (nums[i], nums[k]);
}
}
[Test]
public void Test() {
int[] nums = [4, 1, 3, 1, 5, 2];
SelectionSort(nums);
Console.WriteLine("選択ソート完了後 nums = " + string.Join(" ", nums));
}
}
@@ -0,0 +1,152 @@
/**
* File: array_deque.cs
* Created Time: 2023-03-08
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_stack_and_queue;
/* 循環配列ベースの両端キュー */
public class ArrayDeque {
int[] nums; // 両端キューの要素を格納する配列
int front; // 先頭ポインタ。先頭要素を指す
int queSize; // 両端キューの長さ
/* コンストラクタ */
public ArrayDeque(int capacity) {
nums = new int[capacity];
front = queSize = 0;
}
/* 両端キューの容量を取得 */
int Capacity() {
return nums.Length;
}
/* 両端キューの長さを取得 */
public int Size() {
return queSize;
}
/* 両端キューが空かどうかを判定 */
public bool IsEmpty() {
return queSize == 0;
}
/* 循環配列のインデックスを計算 */
int Index(int i) {
// 剰余演算により配列の先頭と末尾をつなげる
// i が配列の末尾を越えたら先頭に戻る
// i が配列の先頭を越えて前に出たら末尾に戻る
return (i + Capacity()) % Capacity();
}
/* キュー先頭にエンキュー */
public void PushFirst(int num) {
if (queSize == Capacity()) {
Console.WriteLine("両端キューは満杯です");
return;
}
// 先頭ポインタを左に 1 つ移動する
// 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
front = Index(front - 1);
// num をキュー先頭に追加
nums[front] = num;
queSize++;
}
/* キュー末尾にエンキュー */
public void PushLast(int num) {
if (queSize == Capacity()) {
Console.WriteLine("両端キューは満杯です");
return;
}
// キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
int rear = Index(front + queSize);
// num をキュー末尾に追加
nums[rear] = num;
queSize++;
}
/* キュー先頭からデキュー */
public int PopFirst() {
int num = PeekFirst();
// 先頭ポインタを 1 つ後ろへ進める
front = Index(front + 1);
queSize--;
return num;
}
/* キュー末尾からデキュー */
public int PopLast() {
int num = PeekLast();
queSize--;
return num;
}
/* キュー先頭の要素にアクセス */
public int PeekFirst() {
if (IsEmpty()) {
throw new InvalidOperationException();
}
return nums[front];
}
/* キュー末尾の要素にアクセス */
public int PeekLast() {
if (IsEmpty()) {
throw new InvalidOperationException();
}
// 末尾要素のインデックスを計算
int last = Index(front + queSize - 1);
return nums[last];
}
/* 出力用の配列を返す */
public int[] ToArray() {
// 有効長の範囲内のリスト要素のみを変換
int[] res = new int[queSize];
for (int i = 0, j = front; i < queSize; i++, j++) {
res[i] = nums[Index(j)];
}
return res;
}
}
public class array_deque {
[Test]
public void Test() {
/* 両端キューを初期化 */
ArrayDeque deque = new(10);
deque.PushLast(3);
deque.PushLast(2);
deque.PushLast(5);
Console.WriteLine("双方向キュー deque = " + string.Join(" ", deque.ToArray()));
/* 要素にアクセス */
int peekFirst = deque.PeekFirst();
Console.WriteLine("先頭要素 peekFirst = " + peekFirst);
int peekLast = deque.PeekLast();
Console.WriteLine("末尾要素 peekLast = " + peekLast);
/* 要素をエンキュー */
deque.PushLast(4);
Console.WriteLine("要素 4 を末尾にエンキューした後 deque = " + string.Join(" ", deque.ToArray()));
deque.PushFirst(1);
Console.WriteLine("要素 1 を先頭にエンキューした後 deque = " + string.Join(" ", deque.ToArray()));
/* 要素をデキュー */
int popLast = deque.PopLast();
Console.WriteLine("末尾からデキューした要素 = " + popLast + "、末尾からデキューした後 deque = " + string.Join(" ", deque.ToArray()));
int popFirst = deque.PopFirst();
Console.WriteLine("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後 deque = " + string.Join(" ", deque.ToArray()));
/* 両端キューの長さを取得 */
int size = deque.Size();
Console.WriteLine("双方向キューの長さ size = " + size);
/* 両端キューが空かどうかを判定 */
bool isEmpty = deque.IsEmpty();
Console.WriteLine("双方向キューが空かどうか = " + isEmpty);
}
}
@@ -0,0 +1,114 @@
/**
* File: array_queue.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_stack_and_queue;
/* 循環配列ベースのキュー */
class ArrayQueue {
int[] nums; // キュー要素を格納する配列
int front; // 先頭ポインタ。先頭要素を指す
int queSize; // キューの長さ
public ArrayQueue(int capacity) {
nums = new int[capacity];
front = queSize = 0;
}
/* キューの容量を取得 */
int Capacity() {
return nums.Length;
}
/* キューの長さを取得 */
public int Size() {
return queSize;
}
/* キューが空かどうかを判定 */
public bool IsEmpty() {
return queSize == 0;
}
/* エンキュー */
public void Push(int num) {
if (queSize == Capacity()) {
Console.WriteLine("キューは満杯です");
return;
}
// 末尾ポインタを計算し、末尾インデックス + 1 を指す
// 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
int rear = (front + queSize) % Capacity();
// num をキュー末尾に追加
nums[rear] = num;
queSize++;
}
/* デキュー */
public int Pop() {
int num = Peek();
// 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
front = (front + 1) % Capacity();
queSize--;
return num;
}
/* キュー先頭の要素にアクセス */
public int Peek() {
if (IsEmpty())
throw new Exception();
return nums[front];
}
/* 配列を返す */
public int[] ToArray() {
// 有効長の範囲内のリスト要素のみを変換
int[] res = new int[queSize];
for (int i = 0, j = front; i < queSize; i++, j++) {
res[i] = nums[j % this.Capacity()];
}
return res;
}
}
public class array_queue {
[Test]
public void Test() {
/* キューを初期化 */
int capacity = 10;
ArrayQueue queue = new(capacity);
/* 要素をエンキュー */
queue.Push(1);
queue.Push(3);
queue.Push(2);
queue.Push(5);
queue.Push(4);
Console.WriteLine("キュー queue = " + string.Join(",", queue.ToArray()));
/* キュー先頭の要素にアクセス */
int peek = queue.Peek();
Console.WriteLine("先頭要素 peek = " + peek);
/* 要素をデキュー */
int pop = queue.Pop();
Console.WriteLine("デキューした要素 pop = " + pop + "、デキュー後の queue = " + string.Join(",", queue.ToArray()));
/* キューの長さを取得 */
int size = queue.Size();
Console.WriteLine("キューの長さ size = " + size);
/* キューが空かどうかを判定 */
bool isEmpty = queue.IsEmpty();
Console.WriteLine("キューが空かどうか = " + isEmpty);
/* 循環配列をテストする */
for (int i = 0; i < 10; i++) {
queue.Push(i);
queue.Pop();
Console.WriteLine("第 " + i + " 回のエンキュー + デキュー後 queue = " + string.Join(",", queue.ToArray()));
}
}
}
@@ -0,0 +1,84 @@
/**
* File: array_stack.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_stack_and_queue;
/* 配列ベースのスタック */
class ArrayStack {
List<int> 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);
}
}
@@ -0,0 +1,44 @@
/**
* File: deque.cs
* Created Time: 2022-12-30
* Author: moonache (microin1301@outlook.com)
*/
namespace hello_algo.chapter_stack_and_queue;
public class deque {
[Test]
public void Test() {
/* 両端キューを初期化 */
// C# では、LinkedList を両端キューとして使う
LinkedList<int> 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);
}
}
@@ -0,0 +1,177 @@
/**
* File: linkedlist_deque.cs
* Created Time: 2023-03-08
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_stack_and_queue;
/* 双方向連結リストノード */
public class ListNode(int val) {
public int val = val; // ノード値
public ListNode? next = null; // 後続ノードへの参照
public ListNode? prev = null; // 前駆ノードへの参照
}
/* 双方向連結リストベースの両端キュー */
public class LinkedListDeque {
ListNode? front, rear; // 先頭ノード front、末尾ノード rear
int queSize = 0; // 両端キューの長さ
public LinkedListDeque() {
front = null;
rear = null;
}
/* 両端キューの長さを取得 */
public int Size() {
return queSize;
}
/* 両端キューが空かどうかを判定 */
public bool IsEmpty() {
return Size() == 0;
}
/* エンキュー操作 */
void Push(int num, bool isFront) {
ListNode node = new(num);
// 連結リストが空なら、front と rear の両方を node に向ける
if (IsEmpty()) {
front = node;
rear = node;
}
// 先頭へのエンキュー操作
else if (isFront) {
// node を連結リストの先頭に追加
front!.prev = node;
node.next = front;
front = node; // 先頭ノードを更新する
}
// 末尾へのエンキュー操作
else {
// node を連結リストの末尾に追加
rear!.next = node;
node.prev = rear;
rear = node; // 末尾ノードを更新する
}
queSize++; // キューの長さを更新
}
/* キュー先頭にエンキュー */
public void PushFirst(int num) {
Push(num, true);
}
/* キュー末尾にエンキュー */
public void PushLast(int num) {
Push(num, false);
}
/* デキュー操作 */
int? Pop(bool isFront) {
if (IsEmpty())
throw new Exception();
int? val;
// キュー先頭からの取り出し
if (isFront) {
val = front?.val; // 先頭ノードの値を一時保存
// 先頭ノードを削除
ListNode? fNext = front?.next;
if (fNext != null) {
fNext.prev = null;
front!.next = null;
}
front = fNext; // 先頭ノードを更新する
}
// キュー末尾からの取り出し
else {
val = rear?.val; // 末尾ノードの値を一時保存
// 末尾ノードを削除
ListNode? rPrev = rear?.prev;
if (rPrev != null) {
rPrev.next = null;
rear!.prev = null;
}
rear = rPrev; // 末尾ノードを更新する
}
queSize--; // キューの長さを更新
return val;
}
/* キュー先頭からデキュー */
public int? PopFirst() {
return Pop(true);
}
/* キュー末尾からデキュー */
public int? PopLast() {
return Pop(false);
}
/* キュー先頭の要素にアクセス */
public int? PeekFirst() {
if (IsEmpty())
throw new Exception();
return front?.val;
}
/* キュー末尾の要素にアクセス */
public int? PeekLast() {
if (IsEmpty())
throw new Exception();
return rear?.val;
}
/* 出力用の配列を返す */
public int?[] ToArray() {
ListNode? node = front;
int?[] res = new int?[Size()];
for (int i = 0; i < res.Length; i++) {
res[i] = node?.val;
node = node?.next;
}
return res;
}
}
public class linkedlist_deque {
[Test]
public void Test() {
/* 両端キューを初期化 */
LinkedListDeque deque = new();
deque.PushLast(3);
deque.PushLast(2);
deque.PushLast(5);
Console.WriteLine("双方向キュー deque = " + string.Join(" ", deque.ToArray()));
/* 要素にアクセス */
int? peekFirst = deque.PeekFirst();
Console.WriteLine("先頭要素 peekFirst = " + peekFirst);
int? peekLast = deque.PeekLast();
Console.WriteLine("末尾要素 peekLast = " + peekLast);
/* 要素をエンキュー */
deque.PushLast(4);
Console.WriteLine("要素 4 を末尾にエンキューした後 deque = " + string.Join(" ", deque.ToArray()));
deque.PushFirst(1);
Console.WriteLine("要素 1 を先頭にエンキューした後 deque = " + string.Join(" ", deque.ToArray()));
/* 要素をデキュー */
int? popLast = deque.PopLast();
Console.WriteLine("末尾からデキューした要素 = " + popLast + "、末尾からデキューした後 deque = " + string.Join(" ", deque.ToArray()));
int? popFirst = deque.PopFirst();
Console.WriteLine("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後 deque = " + string.Join(" ", deque.ToArray()));
/* 両端キューの長さを取得 */
int size = deque.Size();
Console.WriteLine("双方向キューの長さ size = " + size);
/* 両端キューが空かどうかを判定 */
bool isEmpty = deque.IsEmpty();
Console.WriteLine("双方向キューが空かどうか = " + isEmpty);
}
}
@@ -0,0 +1,106 @@
/**
* File: linkedlist_queue.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_stack_and_queue;
/* 連結リストベースのキュー */
class LinkedListQueue {
ListNode? front, rear; // 先頭ノード front、末尾ノード rear
int queSize = 0;
public LinkedListQueue() {
front = null;
rear = null;
}
/* キューの長さを取得 */
public int Size() {
return queSize;
}
/* キューが空かどうかを判定 */
public bool IsEmpty() {
return Size() == 0;
}
/* エンキュー */
public void Push(int num) {
// 末尾ノードの後ろに num を追加
ListNode node = new(num);
// キューが空なら、先頭・末尾ノードをともにそのノードに設定
if (front == null) {
front = node;
rear = node;
// キューが空でなければ、そのノードを末尾ノードの後ろに追加
} else if (rear != null) {
rear.next = node;
rear = node;
}
queSize++;
}
/* デキュー */
public int Pop() {
int num = Peek();
// 先頭ノードを削除
front = front?.next;
queSize--;
return num;
}
/* キュー先頭の要素にアクセス */
public int Peek() {
if (IsEmpty())
throw new Exception();
return front!.val;
}
/* 連結リストを Array に変換して返す */
public int[] ToArray() {
if (front == null)
return [];
ListNode? node = front;
int[] res = new int[Size()];
for (int i = 0; i < res.Length; i++) {
res[i] = node!.val;
node = node.next;
}
return res;
}
}
public class linkedlist_queue {
[Test]
public void Test() {
/* キューを初期化 */
LinkedListQueue queue = new();
/* 要素をエンキュー */
queue.Push(1);
queue.Push(3);
queue.Push(2);
queue.Push(5);
queue.Push(4);
Console.WriteLine("キュー queue = " + string.Join(",", queue.ToArray()));
/* キュー先頭の要素にアクセス */
int peek = queue.Peek();
Console.WriteLine("先頭要素 peek = " + peek);
/* 要素をデキュー */
int pop = queue.Pop();
Console.WriteLine("デキューした要素 pop = " + pop + "、デキュー後の queue = " + string.Join(",", queue.ToArray()));
/* キューの長さを取得 */
int size = queue.Size();
Console.WriteLine("キューの長さ size = " + size);
/* キューが空かどうかを判定 */
bool isEmpty = queue.IsEmpty();
Console.WriteLine("キューが空かどうか = " + isEmpty);
}
}
@@ -0,0 +1,97 @@
/**
* File: linkedlist_stack.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_stack_and_queue;
/* 連結リストベースのスタック */
class LinkedListStack {
ListNode? stackPeek; // 先頭ノードをスタックトップとする
int stkSize = 0; // スタックの長さ
public LinkedListStack() {
stackPeek = null;
}
/* スタックの長さを取得 */
public int Size() {
return stkSize;
}
/* スタックが空かどうかを判定 */
public bool IsEmpty() {
return Size() == 0;
}
/* プッシュ */
public void Push(int num) {
ListNode node = new(num) {
next = stackPeek
};
stackPeek = node;
stkSize++;
}
/* ポップ */
public int Pop() {
int num = Peek();
stackPeek = stackPeek!.next;
stkSize--;
return num;
}
/* スタックトップの要素にアクセス */
public int Peek() {
if (IsEmpty())
throw new Exception();
return stackPeek!.val;
}
/* List を Array に変換して返す */
public int[] ToArray() {
if (stackPeek == null)
return [];
ListNode? node = stackPeek;
int[] res = new int[Size()];
for (int i = res.Length - 1; i >= 0; i--) {
res[i] = node!.val;
node = node.next;
}
return res;
}
}
public class linkedlist_stack {
[Test]
public void Test() {
/* スタックを初期化 */
LinkedListStack stack = new();
/* 要素をプッシュ */
stack.Push(1);
stack.Push(3);
stack.Push(2);
stack.Push(5);
stack.Push(4);
Console.WriteLine("スタック stack = " + string.Join(",", stack.ToArray()));
/* スタックトップの要素にアクセス */
int peek = stack.Peek();
Console.WriteLine("スタックトップ要素 peek = " + peek);
/* 要素をポップ */
int pop = stack.Pop();
Console.WriteLine("ポップした要素 pop = " + pop + "、ポップ後の stack = " + string.Join(",", stack.ToArray()));
/* スタックの長さを取得 */
int size = stack.Size();
Console.WriteLine("スタックの長さ size = " + size);
/* 空かどうかを判定 */
bool isEmpty = stack.IsEmpty();
Console.WriteLine("スタックが空かどうか = " + isEmpty);
}
}
@@ -0,0 +1,39 @@
/**
* File: queue.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_stack_and_queue;
public class queue {
[Test]
public void Test() {
/* キューを初期化 */
Queue<int> 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);
}
}
@@ -0,0 +1,40 @@
/**
* File: stack.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_stack_and_queue;
public class stack {
[Test]
public void Test() {
/* スタックを初期化 */
Stack<int> 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);
}
}
@@ -0,0 +1,129 @@
/**
* File: array_binary_tree.cs
* Created Time: 2023-07-20
* Author: hpstory (hpstory1024@163.com)
*/
namespace hello_algo.chapter_tree;
/* 配列表現による二分木クラス */
public class ArrayBinaryTree(List<int?> arr) {
List<int?> 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<int> LevelOrder() {
List<int> 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<int> 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<int> PreOrder() {
List<int> res = [];
DFS(0, "pre", res);
return res;
}
/* 中順走査 */
public List<int> InOrder() {
List<int> res = [];
DFS(0, "in", res);
return res;
}
/* 後順走査 */
public List<int> PostOrder() {
List<int> res = [];
DFS(0, "post", res);
return res;
}
}
public class array_binary_tree {
[Test]
public void Test() {
// 二分木を初期化
// ここでは、配列から直接二分木を生成する関数を利用する
List<int?> 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<int> 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());
}
}
+216
View File
@@ -0,0 +1,216 @@
/**
* File: avl_tree.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_tree;
/* AVL 木 */
class AVLTree {
public TreeNode? root; // 根ノード
/* ノードの高さを取得 */
int Height(TreeNode? node) {
// 空ノードの高さは -1、葉ノードの高さは 0
return node == null ? -1 : node.height;
}
/* ノードの高さを更新する */
void UpdateHeight(TreeNode node) {
// ノードの高さは最も高い部分木の高さ + 1 に等しい
node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
}
/* 平衡係数を取得 */
public int BalanceFactor(TreeNode? node) {
// 空ノードの平衡係数は 0
if (node == null) return 0;
// ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
return Height(node.left) - Height(node.right);
}
/* 右回転 */
TreeNode? RightRotate(TreeNode? node) {
TreeNode? child = node?.left;
TreeNode? grandChild = child?.right;
// child を支点として node を右回転させる
child.right = node;
node.left = grandChild;
// ノードの高さを更新する
UpdateHeight(node);
UpdateHeight(child);
// 回転後の部分木の根ノードを返す
return child;
}
/* 左回転 */
TreeNode? LeftRotate(TreeNode? node) {
TreeNode? child = node?.right;
TreeNode? grandChild = child?.left;
// child を支点として node を左回転させる
child.left = node;
node.right = grandChild;
// ノードの高さを更新する
UpdateHeight(node);
UpdateHeight(child);
// 回転後の部分木の根ノードを返す
return child;
}
/* 回転操作を行い、この部分木の平衡を回復する */
TreeNode? Rotate(TreeNode? node) {
// ノード node の平衡係数を取得
int balanceFactorInt = BalanceFactor(node);
// 左に偏った木
if (balanceFactorInt > 1) {
if (BalanceFactor(node?.left) >= 0) {
// 右回転
return RightRotate(node);
} else {
// 左回転してから右回転
node!.left = LeftRotate(node!.left);
return RightRotate(node);
}
}
// 右に偏った木
if (balanceFactorInt < -1) {
if (BalanceFactor(node?.right) <= 0) {
// 左回転
return LeftRotate(node);
} else {
// 右回転してから左回転
node!.right = RightRotate(node!.right);
return LeftRotate(node);
}
}
// 平衡木なので回転不要、そのまま返す
return node;
}
/* ノードを挿入 */
public void Insert(int val) {
root = InsertHelper(root, val);
}
/* ノードを再帰的に挿入する(補助メソッド) */
TreeNode? InsertHelper(TreeNode? node, int val) {
if (node == null) return new TreeNode(val);
/* 1. 挿入位置を探索してノードを挿入 */
if (val < node.val)
node.left = InsertHelper(node.left, val);
else if (val > node.val)
node.right = InsertHelper(node.right, val);
else
return node; // 重複ノードは挿入せず、そのまま返す
UpdateHeight(node); // ノードの高さを更新する
/* 2. 回転操作を行い、部分木の平衡を回復する */
node = Rotate(node);
// 部分木の根ノードを返す
return node;
}
/* ノードを削除 */
public void Remove(int val) {
root = RemoveHelper(root, val);
}
/* ノードを再帰的に削除する(補助メソッド) */
TreeNode? RemoveHelper(TreeNode? node, int val) {
if (node == null) return null;
/* 1. ノードを探索して削除 */
if (val < node.val)
node.left = RemoveHelper(node.left, val);
else if (val > node.val)
node.right = RemoveHelper(node.right, val);
else {
if (node.left == null || node.right == null) {
TreeNode? child = node.left ?? node.right;
// 子ノード数 = 0 の場合、node をそのまま削除して返す
if (child == null)
return null;
// 子ノード数 = 1 の場合、node をそのまま削除する
else
node = child;
} else {
// 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
TreeNode? temp = node.right;
while (temp.left != null) {
temp = temp.left;
}
node.right = RemoveHelper(node.right, temp.val!.Value);
node.val = temp.val;
}
}
UpdateHeight(node); // ノードの高さを更新する
/* 2. 回転操作を行い、部分木の平衡を回復する */
node = Rotate(node);
// 部分木の根ノードを返す
return node;
}
/* ノードを探索 */
public TreeNode? Search(int val) {
TreeNode? cur = root;
// ループで探索し、葉ノードを越えたら抜ける
while (cur != null) {
// 目標ノードは cur の右部分木にある
if (cur.val < val)
cur = cur.right;
// 目標ノードは cur の左部分木にある
else if (cur.val > val)
cur = cur.left;
// 目標ノードが見つかったらループを抜ける
else
break;
}
// 目標ノードを返す
return cur;
}
}
public class avl_tree {
static void TestInsert(AVLTree tree, int val) {
tree.Insert(val);
Console.WriteLine("\nノード " + val + " を挿入した後の AVL 木は");
PrintUtil.PrintTree(tree.root);
}
static void TestRemove(AVLTree tree, int val) {
tree.Remove(val);
Console.WriteLine("\nノード " + val + " を削除した後、AVL 木は");
PrintUtil.PrintTree(tree.root);
}
[Test]
public void Test() {
/* 空の AVL 木を初期化する */
AVLTree avlTree = new();
/* ノードを挿入 */
// ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい
TestInsert(avlTree, 1);
TestInsert(avlTree, 2);
TestInsert(avlTree, 3);
TestInsert(avlTree, 4);
TestInsert(avlTree, 5);
TestInsert(avlTree, 8);
TestInsert(avlTree, 7);
TestInsert(avlTree, 9);
TestInsert(avlTree, 10);
TestInsert(avlTree, 6);
/* 重複ノードを挿入する */
TestInsert(avlTree, 7);
/* ノードを削除 */
// ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい
TestRemove(avlTree, 8); // 次数 0 のノードを削除する
TestRemove(avlTree, 5); // 次数 1 のノードを削除する
TestRemove(avlTree, 4); // 次数 2 のノードを削除する
/* ノードを検索 */
TreeNode? node = avlTree.Search(7);
Console.WriteLine("\n見つかったノードオブジェクトは " + node + "、ノード値 = " + node?.val);
}
}
@@ -0,0 +1,160 @@
/**
* File: binary_search_tree.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_tree;
class BinarySearchTree {
TreeNode? root;
public BinarySearchTree() {
// 空の木を初期化する
root = null;
}
/* 二分木の根ノードを取得 */
public TreeNode? GetRoot() {
return root;
}
/* ノードを探索 */
public TreeNode? Search(int num) {
TreeNode? cur = root;
// ループで探索し、葉ノードを越えたら抜ける
while (cur != null) {
// 目標ノードは cur の右部分木にある
if (cur.val < num) cur =
cur.right;
// 目標ノードは cur の左部分木にある
else if (cur.val > num)
cur = cur.left;
// 目標ノードが見つかったらループを抜ける
else
break;
}
// 目標ノードを返す
return cur;
}
/* ノードを挿入 */
public void Insert(int num) {
// 木が空なら、根ノードを初期化する
if (root == null) {
root = new TreeNode(num);
return;
}
TreeNode? cur = root, pre = null;
// ループで探索し、葉ノードを越えたら抜ける
while (cur != null) {
// 重複ノードが見つかったら、直ちに返す
if (cur.val == num)
return;
pre = cur;
// 挿入位置は cur の右部分木にある
if (cur.val < num)
cur = cur.right;
// 挿入位置は cur の左部分木にある
else
cur = cur.left;
}
// ノードを挿入
TreeNode node = new(num);
if (pre != null) {
if (pre.val < num)
pre.right = node;
else
pre.left = node;
}
}
/* ノードを削除 */
public void Remove(int num) {
// 木が空なら、そのまま早期リターンする
if (root == null)
return;
TreeNode? cur = root, pre = null;
// ループで探索し、葉ノードを越えたら抜ける
while (cur != null) {
// 削除対象のノードが見つかったら、ループを抜ける
if (cur.val == num)
break;
pre = cur;
// 削除対象ノードは cur の右部分木にある
if (cur.val < num)
cur = cur.right;
// 削除対象ノードは cur の左部分木にある
else
cur = cur.left;
}
// 削除対象ノードがなければそのまま返す
if (cur == null)
return;
// 子ノード数 = 0 or 1
if (cur.left == null || cur.right == null) {
// 子ノード数が 0 / 1 のとき、child = null / その子ノード
TreeNode? child = cur.left ?? cur.right;
// ノード cur を削除する
if (cur != root) {
if (pre!.left == cur)
pre.left = child;
else
pre.right = child;
} else {
// 削除ノードが根ノードなら、根ノードを再設定
root = child;
}
}
// 子ノード数 = 2
else {
// 中順走査における cur の次ノードを取得
TreeNode? tmp = cur.right;
while (tmp.left != null) {
tmp = tmp.left;
}
// ノード tmp を再帰的に削除
Remove(tmp.val!.Value);
// tmp で cur を上書きする
cur.val = tmp.val;
}
}
}
public class binary_search_tree {
[Test]
public void Test() {
/* 二分探索木を初期化 */
BinarySearchTree bst = new();
// 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる
int[] nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15];
foreach (int num in nums) {
bst.Insert(num);
}
Console.WriteLine("\n初期化した二分木は\n");
PrintUtil.PrintTree(bst.GetRoot());
/* ノードを探索 */
TreeNode? node = bst.Search(7);
Console.WriteLine("\n見つかったノードオブジェクトは " + node + "、ノード値 = " + node?.val);
/* ノードを挿入 */
bst.Insert(16);
Console.WriteLine("\nノード 16 を挿入した後、二分木は\n");
PrintUtil.PrintTree(bst.GetRoot());
/* ノードを削除 */
bst.Remove(1);
Console.WriteLine("\nノード 1 を削除した後、二分木は\n");
PrintUtil.PrintTree(bst.GetRoot());
bst.Remove(2);
Console.WriteLine("\nノード 2 を削除した後、二分木は\n");
PrintUtil.PrintTree(bst.GetRoot());
bst.Remove(4);
Console.WriteLine("\nノード 4 を削除した後、二分木は\n");
PrintUtil.PrintTree(bst.GetRoot());
}
}
@@ -0,0 +1,39 @@
/**
* File: binary_tree.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_tree;
public class binary_tree {
[Test]
public void Test() {
/* 二分木を初期化 */
// ノードを初期化
TreeNode n1 = new(1);
TreeNode n2 = new(2);
TreeNode n3 = new(3);
TreeNode n4 = new(4);
TreeNode n5 = new(5);
// ノード間の参照(ポインタ)を構築する
n1.left = n2;
n1.right = n3;
n2.left = n4;
n2.right = n5;
Console.WriteLine("\n二分木を初期化\n");
PrintUtil.PrintTree(n1);
/* ノードの挿入と削除 */
TreeNode P = new(0);
// n1 -> n2 の間にノード P を挿入
n1.left = P;
P.left = n2;
Console.WriteLine("\nノード P を挿入した後\n");
PrintUtil.PrintTree(n1);
// ノード P を削除
n1.left = n2;
Console.WriteLine("\nノード P を削除した後\n");
PrintUtil.PrintTree(n1);
}
}
@@ -0,0 +1,40 @@
/**
* File: binary_tree_bfs.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_tree;
public class binary_tree_bfs {
/* レベル順走査 */
List<int> LevelOrder(TreeNode root) {
// キューを初期化し、ルートノードを追加する
Queue<TreeNode> queue = new();
queue.Enqueue(root);
// 走査順序を保存するためのリストを初期化する
List<int> 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<int> list = LevelOrder(root!);
Console.WriteLine("\nレベル順走査のノード出力シーケンス = " + string.Join(",", list));
}
}
@@ -0,0 +1,59 @@
/**
* File: binary_tree_dfs.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.chapter_tree;
public class binary_tree_dfs {
List<int> 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));
}
}
+25
View File
@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello-algo", "hello-algo.csproj", "{48B60439-EFDC-4C8F-AE8D-41979958C8AC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1E773F8A-FF66-4974-820B-FCE9032D19AE}
EndGlobalSection
EndGlobal
+21
View File
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>hello_algo</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
+32
View File
@@ -0,0 +1,32 @@
// File: ListNode.cs
// Created Time: 2022-12-16
// Author: mingXta (1195669834@qq.com)
namespace hello_algo.utils;
/* 連結リストノード */
public class ListNode(int x) {
public int val = x;
public ListNode? next;
/* 配列をデシリアライズして連結リストに変換する */
public static ListNode? ArrToLinkedList(int[] arr) {
ListNode dum = new(0);
ListNode head = dum;
foreach (int val in arr) {
head.next = new ListNode(val);
head = head.next;
}
return dum.next;
}
public override string? ToString() {
List<string> list = [];
var head = this;
while (head != null) {
list.Add(head.val.ToString());
head = head.next;
}
return string.Join("->", list);
}
}
+132
View File
@@ -0,0 +1,132 @@
/**
* File: PrintUtil.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com), krahets (krahets@163.com)
*/
namespace hello_algo.utils;
public class Trunk(Trunk? prev, string str) {
public Trunk? prev = prev;
public string str = str;
};
public static class PrintUtil {
/* リストを出力する */
public static void PrintList<T>(IList<T> list) {
Console.WriteLine("[" + string.Join(", ", list) + "]");
}
public static string PrintList<T>(this IEnumerable<T?> list) {
return $"[ {string.Join(", ", list.Select(x => x?.ToString() ?? "null"))} ]";
}
/* 行列を出力する (Array) */
public static void PrintMatrix<T>(T[][] matrix) {
Console.WriteLine("[");
foreach (T[] row in matrix) {
Console.WriteLine(" " + string.Join(", ", row) + ",");
}
Console.WriteLine("]");
}
/* 行列を出力 (List) */
public static void PrintMatrix<T>(List<List<T>> matrix) {
Console.WriteLine("[");
foreach (List<T> row in matrix) {
Console.WriteLine(" " + string.Join(", ", row) + ",");
}
Console.WriteLine("]");
}
/* 連結リストを出力 */
public static void PrintLinkedList(ListNode? head) {
List<string> 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<K, V>(Dictionary<K, V> map) where K : notnull {
foreach (var kv in map.Keys) {
Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString());
}
}
/* ヒープを出力 */
public static void PrintHeap(Queue<int> queue) {
Console.Write("ヒープの配列表現:");
List<int> list = [.. queue];
Console.WriteLine(string.Join(',', list));
Console.WriteLine("ヒープの木構造表示:");
TreeNode? tree = TreeNode.ListToTree(list.Cast<int?>().ToList());
PrintTree(tree);
}
/* 優先キューを出力 */
public static void PrintHeap(PriorityQueue<int, int> queue) {
var newQueue = new PriorityQueue<int, int>(queue.UnorderedItems, queue.Comparer);
Console.Write("ヒープの配列表現:");
List<int> 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<int?>().ToList());
PrintTree(tree);
}
}
+67
View File
@@ -0,0 +1,67 @@
/**
* File: TreeNode.cs
* Created Time: 2022-12-23
* Author: haptear (haptear@hotmail.com)
*/
namespace hello_algo.utils;
/* 二分木ノードクラス */
public class TreeNode(int? x) {
public int? val = x; // ノード値
public int height; // ノードの高さ
public TreeNode? left; // 左子ノードへの参照
public TreeNode? right; // 右子ノードへの参照
// シリアライズの符号化規則は以下を参照:
// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/
// 二分木の配列表現:
// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
// 二分木の連結リスト表現:
// /——— 15
// /——— 7
// /——— 3
// | \——— 6
// | \——— 12
// ——— 1
// \——— 2
// | /——— 9
// \——— 4
// \——— 8
/* リストを二分木にデシリアライズする: 再帰 */
static TreeNode? ListToTreeDFS(List<int?> 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<int?> arr) {
return ListToTreeDFS(arr, 0);
}
/* 二分木をリストにシリアライズする: 再帰 */
static void TreeToListDFS(TreeNode? root, int i, List<int?> 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<int?> TreeToList(TreeNode root) {
List<int?> res = [];
TreeToListDFS(root, 0, res);
return res;
}
}
+30
View File
@@ -0,0 +1,30 @@
/**
* File: Vertex.cs
* Created Time: 2023-02-06
* Author: zjkung1123 (zjkung1123@gmail.com), krahets (krahets@163.com)
*/
namespace hello_algo.utils;
/* 頂点クラス */
public class Vertex(int val) {
public int val = val;
/* 値リスト vals を入力し、頂点リスト vets を返す */
public static Vertex[] ValsToVets(int[] vals) {
Vertex[] vets = new Vertex[vals.Length];
for (int i = 0; i < vals.Length; i++) {
vets[i] = new Vertex(vals[i]);
}
return vets;
}
/* 頂点リスト vets を入力し、値リスト vals を返す */
public static List<int> VetsToVals(List<Vertex> vets) {
List<int> vals = [];
foreach (Vertex vet in vets) {
vals.Add(vet.val);
}
return vals;
}
}