docs: add Japanese translate documents (#1812)

* docs: add Japanese documents (`ja/docs`)

* docs: add Japanese documents (`ja/codes`)

* docs: add Japanese documents

* Remove pythontutor blocks in ja/

* Add an empty at the end of each markdown file.

* Add the missing figures (use the English version temporarily).

* Add index.md for Japanese version.

* Add index.html for Japanese version.

* Add missing index.assets

* Fix backtracking_algorithm.md for Japanese version.

* Add avatar_eltociear.jpg. Fix image links on the Japanese landing page.

* Add the Japanese banner.

---------

Co-authored-by: krahets <krahets@163.com>
This commit is contained in:
Ikko Eltociear Ashimine
2025-10-17 06:04:43 +09:00
committed by GitHub
parent 2487a27036
commit 954c45864b
886 changed files with 33569 additions and 0 deletions

View File

@@ -43,6 +43,8 @@
<a href="https://github.com/krahets/hello-algo/blob/main/zh-hant/README.md">繁體中文</a>
<a href="https://github.com/krahets/hello-algo/blob/main/en/README.md">English</a>
<a href="https://github.com/krahets/hello-algo/blob/main/ja/README.md">日本語</a>
</p>
## 关于本书

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -41,6 +41,8 @@
<a href="https://github.com/krahets/hello-algo/blob/main/zh-hant/README.md">繁體中文</a>
English
<a href="https://github.com/krahets/hello-algo/blob/main/ja/README.md">日本語</a>
</p>
## The book

134
ja/CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,134 @@
# 中国語から日本語への貢献ガイドライン
「Hello アルゴリズム」を中国語から日本語に翻訳するにあたり、以下のアプローチを採用しています:
1. **AI翻訳**: 大規模言語モデルを使用して初期翻訳を実施します。
2. **人による最適化**: 機械生成された出力を手動で改良し、正確性と自然さを確保します。
3. **プルリクエストレビュー**: 最適化された翻訳は、GitHubのプルリクエストワークフローを通じてレビュアーによって二重チェックされます。
4. さらなる改善のため、ステップ `2.``3.` を繰り返します。
<img width="650" alt="translation_pipeline" src="https://github.com/user-attachments/assets/201930ef-723e-4179-b670-e5a084a8211e">
## 参加方法
以下の基準を満たす貢献者を求めています:
- **技術的背景**: コンピュータサイエンス、特にデータ構造とアルゴリズムに関する強固な基礎知識
- **言語スキル**: 日本語ネイティブまたは日本語に精通した方、中国語の読解力
- **利用可能な時間**: オープンソースプロジェクトへの貢献に専念し、長期的な翻訳作業に参加する意欲
つまり、私たちの貢献者は、さまざまな言語背景を持つコンピュータサイエンティスト、エンジニア、学生であり、それぞれの目的には異なる焦点があります:
- **中国語読解力を持つ日本語ネイティブ**: 中国語版と日本語版の間の翻訳の正確性と一貫性を確保する
- **日本語に精通した中国語話者**: 日本語コンテンツの自然さと流暢さを向上させ、自然で読みやすいものにする
> [!note]
> 参加にご興味がある方は、お気軽に krahetx@gmail.com またはWeChat `krahets-jyd` までご連絡ください。
>
> 進捗管理とタスク割り当てには、この[Notionページ](https://hello-algo.notion.site/chinese-to-english)を使用しています。詳細はこちらをご覧ください。
## 翻訳プロセス
> [!important]
> 作業を開始する前に、GitHubのプルリクエストワークフローに慣れ、以下の「翻訳基準」と「翻訳のための疑似コード」を必ずお読みください。
1. **タスク割り当て**: Notionワークスペースでタスクを自己割り当てします。
2. **翻訳**: ローカルPCで翻訳を最適化します。詳細は以下の「翻訳疑似コード」セクションを参照してください。
3. **ピアレビュー**: プルリクエストPRを提出する前に、変更を慎重にレビューしてください。PRは2名のレビュアーの承認後にメインブランチにマージされます。
## 翻訳基準
> [!tip]
> **「正確性」と「自然さ」は、主に中国語を理解できる日本語ネイティブスピーカーによって扱われます。**
>
> 場合によっては、「正確性(一貫性)」と「自然さ」はトレードオフの関係にあり、一方を最適化すると他方に大きな影響を与える可能性があります。そのような場合は、プルリクエストにコメントを残して議論してください。
**正確性**:
- [用語集](https://www.hello-algo.com/chapter_appendix/terminology/)セクションを参照して、翻訳全体で用語の一貫性を保ちます。
- 技術的な正確性を優先し、中国語版のトーンとスタイルを維持します。
- 修正が正確で包括的であることを確保するため、常に中国語版の内容とコンテキストを考慮してください。
**自然さ**:
- 翻訳は日本語の表現慣習に従い、自然で流暢に読めるようにすべきです。
- 記事を調和させるために、常にコンテンツのコンテキストを考慮してください。
- 中国語と日本語の文化的な違いに注意してください。例えば、中国語の「拼音」は日本語には存在しません。
- 最適化された文が元の意味を変える可能性がある場合は、議論のためにコメントを追加してください。
**フォーマット**:
- 図表は展開時に自動的に番号付けされるため、手動で番号を付けないでください。
- バグ修正を除き、各PRは管理可能なレビューサイズを確保するため、少なくとも1つの完全なドキュメントをカバーすべきです。
**レビュー**:
- レビュー中は、変更の評価を優先し、必要に応じて周囲のコンテキストを参照してください。
- お互いの視点から学ぶことで、より良い翻訳とより一貫性のある結果につながります。
## 翻訳疑似コード
以下の疑似コードは、典型的な翻訳プロセスのステップをモデル化しています。
```python
def optimize_translation(markdown_texts, lang_skill):
"""翻訳を最適化する"""
for sentence in markdown_texts:
"""正確性は主に中国語を理解できる日本語ネイティブスピーカーによって処理される"""
if lang_skill is "日本語ネイティブ + 中国語読解力":
if is_accurate_Chinese_to_Japanese(sentence):
continue
# 正確性を最適化
result = refine_accuracy(sentence)
"""
自然さは主に日本語ネイティブスピーカーによって処理され、
副次的に中国語話者によって処理される
"""
if is_authentic_Japanese(sentence):
continue
# 自然さを最適化
result = refine_authenticity(sentence)
# 一貫性を損なう可能性がある場合はPRにコメントを追加
if break_consistency(result):
add_comment(description)
pull_request = submit_pull_request(markdown_texts)
# PRは2名以上のレビュアーによる承認後にマージされる
while count_approvals(pull_request) < 2:
continue
merge(pull_request)
```
以下はレビュアー向けの疑似コードです:
```python
def review_pull_requests(pull_request, lang_skill):
"""PRをレビューする"""
# PR内のすべての変更をループ
while is_anything_left_to_review(pull_request):
change = get_next_change(pull_request)
"""正確性は主に中国語を理解できる日本語ネイティブスピーカーによって処理される"""
if lang_skill is "日本語ネイティブ + 中国語読解力":
# 中国語版と日本語版の間の正確性(一貫性)をチェック
if is_accurate_Chinese_to_Japanese(change):
continue
# 正確性(一貫性)を最適化
result = refine_accuracy(change)
# PRにコメントを追加
add_comment(result)
"""
自然さは主に日本語ネイティブスピーカーによって処理され、
副次的に中国語話者によって処理される
"""
if is_authentic_Japanese(change):
continue
# 自然な日本語でない場合は自然さを最適化
result = refine_authenticity(change)
# PRにコメントを追加
add_comment(result)
approve(pull_request)
```

91
ja/README.md Normal file
View File

@@ -0,0 +1,91 @@
<p align="center">
<a href="https://www.hello-algo.com/ja/">
<img src="https://www.hello-algo.com/en/index.assets/hello_algo_header.png" width="450"></a>
</p>
<p align="center">
<img style="height: 60px;" src="https://readme-typing-svg.demolab.com?font=Roboto&weight=400&duration=3500&pause=2000&color=21C8B8&center=true&vCenter=true&random=false&width=200&lines=Hello%2C+Algo!" alt="hello-algo-typing-svg" />
</br>
アニメーションで図解、ワンクリック実行のデータ構造とアルゴリズム入門講座
</p>
<p align="center">
<a href="https://www.hello-algo.com/ja/">
<img src="https://www.hello-algo.com/en/index.assets/btn_read_online_dark.svg" width="120"></a>
</p>
<p align="center">
<img src="https://www.hello-algo.com/index.assets/animation.gif" width="395">
<img src="https://www.hello-algo.com/index.assets/running_code.gif" width="395">
</p>
<p align="center">
<img src="https://img.shields.io/badge/Python-snow?logo=python&logoColor=3776AB" alt="" />
<img src="https://img.shields.io/badge/Java-snow?logo=coffeescript&logoColor=FC4C02" alt="" />
<img src="https://img.shields.io/badge/C%2B%2B-snow?logo=c%2B%2B&logoColor=00599C" alt="" />
<img src="https://img.shields.io/badge/C-snow?logo=c&logoColor=A8B9CC" alt="" />
<img src="https://img.shields.io/badge/C%23-snow?logo=csharp&logoColor=512BD4" alt="" />
<img src="https://img.shields.io/badge/JavaScript-snow?logo=javascript&logoColor=E9CE30" alt="" />
<img src="https://img.shields.io/badge/Go-snow?logo=go&logoColor=00ADD8" alt="" />
<img src="https://img.shields.io/badge/Swift-snow?logo=swift&logoColor=F05138" alt="" />
<img src="https://img.shields.io/badge/Rust-snow?logo=rust&logoColor=000000" alt="" />
<img src="https://img.shields.io/badge/Ruby-snow?logo=ruby&logoColor=CC342D" alt="" />
<img src="https://img.shields.io/badge/Kotlin-snow?logo=kotlin&logoColor=7F52FF" alt="" />
<img src="https://img.shields.io/badge/TypeScript-snow?logo=typescript&logoColor=3178C6" alt="" />
<img src="https://img.shields.io/badge/Dart-snow?logo=dart&logoColor=0175C2" alt="" />
</p>
<p align="center">
<a href="https://github.com/krahets/hello-algo">简体中文</a>
<a href="https://github.com/krahets/hello-algo/blob/main/zh-hant/README.md">繁體中文</a>
<a href="https://github.com/krahets/hello-algo/blob/main/en/README.md">English</a>
日本語
</p>
## この本について
このオープンソースプロジェクトは、データ構造とアルゴリズムの無料で初心者向けの入門講座を作成することを目的としています。
- アニメーションによる図解、わかりやすい内容、なめらかな学習曲線により、初心者がデータ構造とアルゴリズムの「知識マップ」を探索できます。
- ワンクリックでコードを実行でき、読者のプログラミングスキルを向上させ、アルゴリズムの動作原理とデータ構造の基礎となる実装を理解できます。
- 教えることで学ぶことを促進し、質問や洞察を自由に共有してください。議論を通じて一緒に成長しましょう。
この本が役立つと思われた場合は、スター :star: を付けてサポートしてください。ありがとうございます!
## 推薦の言葉
> 「データ構造とアルゴリズムに関するわかりやすい本で、読者が頭と手を使って学ぶように導きます。アルゴリズム初心者に強くお勧めします!」
>
> **—— 鄧俊輝教授、清華大学コンピュータサイエンス技術学部**
> 「データ構造とアルゴリズムを学んでいたときに『Hello Algo』があったなら、10倍簡単だったでしょう
>
> **—— Mu Li、Amazon シニアプリンシパルサイエンティスト**
## 貢献
> [!Important]
>
> 英語から日本語への翻訳への貢献を歓迎します!詳細は[CONTRIBUTING.md](CONTRIBUTING.md)をご覧ください。
このオープンソースブックは継続的に更新されており、読者により良い学習コンテンツを提供するため、このプロジェクトへの参加を歓迎します。
- [内容の修正](https://www.hello-algo.com/ja/chapter_appendix/contribution/): 文法エラー、内容の欠落、曖昧さ、無効なリンク、コードのバグなど、コメントセクションで間違いを修正したり指摘したりしてください。
- [コードの移植](https://github.com/krahets/hello-algo/issues/15): さまざまなプログラミング言語でのご貢献をお待ちしています。現在、Python、Java、C++、Go、JavaScriptを含む12言語をサポートしています。
貴重なご提案とフィードバックを歓迎します。ご質問がある場合は、Issuesを提出するか、WeChat: `krahets-jyd`でお問い合わせください。
この本のすべての貢献者に感謝を捧げたいと思います。彼らの無私の献身により、この本がより良いものになりました。貢献者の皆様:
<p align="left">
<a href="https://github.com/krahets/hello-algo/graphs/contributors">
<img width="770" src="https://contrib.rocks/image?repo=krahets/hello-algo&max=300&columns=16" />
</a>
</p>
## ライセンス
このリポジトリ内のテキスト、コード、画像、写真、動画は[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)の下でライセンスされています。

View File

@@ -0,0 +1,113 @@
/**
* File: array.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 要素への乱数アクセス */
int randomAccess(int *nums, int size) {
// [0, size)の範囲で乱数を選択
int randomIndex = rand() % size;
// 乱数要素を取得して返却
int randomNum = nums[randomIndex];
return randomNum;
}
/* 配列長の拡張 */
int *extend(int *nums, int size, int enlarge) {
// 拡張された長さの配列を初期化
int *res = new int[size + enlarge];
// 元の配列の全要素を新しい配列にコピー
for (int i = 0; i < size; i++) {
res[i] = nums[i];
}
// メモリを解放
delete[] nums;
// 拡張後の新しい配列を返却
return res;
}
/* `index`に要素numを挿入 */
void insert(int *nums, int size, int num, int index) {
// `index`より後のすべての要素を1つ後ろに移動
for (int i = size - 1; i > index; i--) {
nums[i] = nums[i - 1];
}
// indexの位置にnumを代入
nums[index] = num;
}
/* `index`の要素を削除 */
void remove(int *nums, int size, int index) {
// `index`より後のすべての要素を1つ前に移動
for (int i = index; i < size - 1; i++) {
nums[i] = nums[i + 1];
}
}
/* 配列の走査 */
void traverse(int *nums, int size) {
int count = 0;
// インデックスによる配列の走査
for (int i = 0; i < size; i++) {
count += nums[i];
}
}
/* 配列内の指定要素を検索 */
int find(int *nums, int size, int target) {
for (int i = 0; i < size; i++) {
if (nums[i] == target)
return i;
}
return -1;
}
/* ドライバーコード */
int main() {
/* 配列を初期化 */
int size = 5;
int *arr = new int[size];
cout << "Array arr = ";
printArray(arr, size);
int *nums = new int[size]{1, 3, 2, 5, 4};
cout << "Array nums = ";
printArray(nums, size);
/* 乱数アクセス */
int randomNum = randomAccess(nums, size);
cout << "Get a random element from nums = " << randomNum << endl;
/* 長さの拡張 */
int enlarge = 3;
nums = extend(nums, size, enlarge);
size += enlarge;
cout << "Extend the array length to 8, resulting in nums = ";
printArray(nums, size);
/* 要素の挿入 */
insert(nums, size, 6, 3);
cout << "Insert the number 6 at index 3, resulting in nums = ";
printArray(nums, size);
/* 要素の削除 */
remove(nums, size, 2);
cout << "Remove the element at index 2, resulting in nums = ";
printArray(nums, size);
/* 配列の走査 */
traverse(nums, size);
/* 要素の検索 */
int index = find(nums, size, 3);
cout << "Find element 3 in nums, index = " << index << endl;
// メモリを解放
delete[] arr;
delete[] nums;
return 0;
}

View File

@@ -0,0 +1,89 @@
/**
* File: linked_list.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 連結リストのードn0の後にードPを挿入 */
void insert(ListNode *n0, ListNode *P) {
ListNode *n1 = n0->next;
P->next = n1;
n0->next = P;
}
/* 連結リストのードn0の後の最初のードを削除 */
void remove(ListNode *n0) {
if (n0->next == nullptr)
return;
// n0 -> P -> n1
ListNode *P = n0->next;
ListNode *n1 = P->next;
n0->next = n1;
// メモリを解放
delete P;
}
/* 連結リストの`index`番目のノードにアクセス */
ListNode *access(ListNode *head, int index) {
for (int i = 0; i < index; i++) {
if (head == nullptr)
return nullptr;
head = head->next;
}
return head;
}
/* 連結リストで値がtargetの最初のードを検索 */
int find(ListNode *head, int target) {
int index = 0;
while (head != nullptr) {
if (head->val == target)
return index;
head = head->next;
index++;
}
return -1;
}
/* ドライバーコード */
int main() {
/* 連結リストを初期化 */
// 各ノードを初期化
ListNode *n0 = new ListNode(1);
ListNode *n1 = new ListNode(3);
ListNode *n2 = new ListNode(2);
ListNode *n3 = new ListNode(5);
ListNode *n4 = new ListNode(4);
// ノード間の参照を構築
n0->next = n1;
n1->next = n2;
n2->next = n3;
n3->next = n4;
cout << "The initialized linked list is" << endl;
printLinkedList(n0);
/* ノードを挿入 */
insert(n0, new ListNode(0));
cout << "Linked list after inserting the node is" << endl;
printLinkedList(n0);
/* ノードを削除 */
remove(n0);
cout << "Linked list after removing the node is" << endl;
printLinkedList(n0);
/* ノードにアクセス */
ListNode *node = access(n0, 3);
cout << "The value of the node at index 3 in the linked list = " << node->val << endl;
/* ノードを検索 */
int index = find(n0, 2);
cout << "The index of the node with value 2 in the linked list = " << index << endl;
// メモリを解放
freeMemoryLinkedList(n0);
return 0;
}

View File

@@ -0,0 +1,72 @@
/**
* File: list.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* ドライバーコード */
int main() {
/* リストを初期化 */
vector<int> nums = {1, 3, 2, 5, 4};
cout << "List nums = ";
printVector(nums);
/* 要素にアクセス */
int num = nums[1];
cout << "Access the element at index 1, obtained num = " << num << endl;
/* 要素を更新 */
nums[1] = 0;
cout << "Update the element at index 1 to 0, resulting in nums = ";
printVector(nums);
/* リストをクリア */
nums.clear();
cout << "After clearing the list, nums = ";
printVector(nums);
/* 末尾に要素を追加 */
nums.push_back(1);
nums.push_back(3);
nums.push_back(2);
nums.push_back(5);
nums.push_back(4);
cout << "After adding elements, nums = ";
printVector(nums);
/* 中間に要素を挿入 */
nums.insert(nums.begin() + 3, 6);
cout << "Insert the number 6 at index 3, resulting in nums = ";
printVector(nums);
/* 要素を削除 */
nums.erase(nums.begin() + 3);
cout << "Remove the element at index 3, resulting in nums = ";
printVector(nums);
/* インデックスによるリストの走査 */
int count = 0;
for (int i = 0; i < nums.size(); i++) {
count += nums[i];
}
/* リスト要素の走査 */
count = 0;
for (int x : nums) {
count += x;
}
/* 2つのリストを連結 */
vector<int> nums1 = {6, 8, 7, 10, 9};
nums.insert(nums.end(), nums1.begin(), nums1.end());
cout << "Concatenate list nums1 to nums, resulting in nums = ";
printVector(nums);
/* リストをソート */
sort(nums.begin(), nums.end());
cout << "After sorting the list, nums = ";
printVector(nums);
return 0;
}

View File

@@ -0,0 +1,171 @@
/**
* File: my_list.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* リストクラス */
class MyList {
private:
int *arr; // 配列(リスト要素を格納)
int arrCapacity = 10; // リストの容量
int arrSize = 0; // リストの長さ(現在の要素数)
int extendRatio = 2; // リスト拡張時の倍率
public:
/* コンストラクタ */
MyList() {
arr = new int[arrCapacity];
}
/* デストラクタ */
~MyList() {
delete[] arr;
}
/* リストの長さを取得(現在の要素数)*/
int size() {
return arrSize;
}
/* リストの容量を取得 */
int capacity() {
return arrCapacity;
}
/* 要素にアクセス */
int get(int index) {
// インデックスが範囲外の場合、例外をスロー(以下同様)
if (index < 0 || index >= size())
throw out_of_range("Index out of bounds");
return arr[index];
}
/* 要素を更新 */
void set(int index, int num) {
if (index < 0 || index >= size())
throw out_of_range("Index out of bounds");
arr[index] = num;
}
/* 末尾に要素を追加 */
void add(int num) {
// 要素数が容量を超えた場合、拡張メカニズムをトリガー
if (size() == capacity())
extendCapacity();
arr[size()] = num;
// 要素数を更新
arrSize++;
}
/* 中間に要素を挿入 */
void insert(int index, int num) {
if (index < 0 || index >= size())
throw out_of_range("Index out of bounds");
// 要素数が容量を超えた場合、拡張メカニズムをトリガー
if (size() == capacity())
extendCapacity();
// `index`より後のすべての要素を1つ後ろに移動
for (int j = size() - 1; j >= index; j--) {
arr[j + 1] = arr[j];
}
arr[index] = num;
// 要素数を更新
arrSize++;
}
/* 要素を削除 */
int remove(int index) {
if (index < 0 || index >= size())
throw out_of_range("Index out of bounds");
int num = arr[index];
// `index`より後のすべての要素を1つ前に移動
for (int j = index; j < size() - 1; j++) {
arr[j] = arr[j + 1];
}
// 要素数を更新
arrSize--;
// 削除された要素を返却
return num;
}
/* リストを拡張 */
void extendCapacity() {
// 元の配列のextendRatio倍の長さで新しい配列を作成
int newCapacity = capacity() * extendRatio;
int *tmp = arr;
arr = new int[newCapacity];
// 元の配列のすべての要素を新しい配列にコピー
for (int i = 0; i < size(); i++) {
arr[i] = tmp[i];
}
// メモリを解放
delete[] tmp;
arrCapacity = newCapacity;
}
/* リストをVectorに変換して印刷用に使用 */
vector<int> toVector() {
// 有効な長さ範囲内の要素のみを変換
vector<int> vec(size());
for (int i = 0; i < size(); i++) {
vec[i] = arr[i];
}
return vec;
}
};
/* ドライバーコード */
int main() {
/* リストを初期化 */
MyList *nums = new MyList();
/* 末尾に要素を追加 */
nums->add(1);
nums->add(3);
nums->add(2);
nums->add(5);
nums->add(4);
cout << "List nums = ";
vector<int> vec = nums->toVector();
printVector(vec);
cout << "Capacity = " << nums->capacity() << ", length = " << nums->size() << endl;
/* 中間に要素を挿入 */
nums->insert(3, 6);
cout << "Insert the number 6 at index 3, resulting in nums = ";
vec = nums->toVector();
printVector(vec);
/* 要素を削除 */
nums->remove(3);
cout << "Remove the element at index 3, resulting in nums = ";
vec = nums->toVector();
printVector(vec);
/* 要素にアクセス */
int num = nums->get(1);
cout << "Access the element at index 1, obtained num = " << num << endl;
/* 要素を更新 */
nums->set(1, 0);
cout << "Update the element at index 1 to 0, resulting in nums = ";
vec = nums->toVector();
printVector(vec);
/* 拡張メカニズムをテスト */
for (int i = 0; i < 10; i++) {
// i = 5の時、リストの長さがリストの容量を超え、この時点で拡張メカニズムがトリガーされる
nums->add(i);
}
cout << "After extending, list nums = ";
vec = nums->toVector();
printVector(vec);
cout << "Capacity = " << nums->capacity() << ", length = " << nums->size() << endl;
// メモリを解放
delete nums;
return 0;
}

View File

@@ -0,0 +1,65 @@
/**
* File: n_queens.cpp
* Created Time: 2023-05-04
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* バックトラッキングアルゴリズムn クイーン */
void backtrack(int row, int n, vector<vector<string>> &state, vector<vector<vector<string>>> &res, vector<bool> &cols,
vector<bool> &diags1, vector<bool> &diags2) {
// すべての行が配置されたら、解を記録
if (row == n) {
res.push_back(state);
return;
}
// すべての列を走査
for (int col = 0; col < n; col++) {
// セルに対応する主対角線と副対角線を計算
int diag1 = row - col + n - 1;
int diag2 = row + col;
// 剪定:セルの列、主対角線、副対角線にクイーンを配置することを許可しない
if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
// 試行:セルにクイーンを配置
state[row][col] = "Q";
cols[col] = diags1[diag1] = diags2[diag2] = true;
// 次の行を配置
backtrack(row + 1, n, state, res, cols, diags1, diags2);
// 回退:セルを空のスポットに復元
state[row][col] = "#";
cols[col] = diags1[diag1] = diags2[diag2] = false;
}
}
}
/* n クイーンを解く */
vector<vector<vector<string>>> nQueens(int n) {
// n*n サイズのチェスボードを初期化、'Q' はクイーンを表し、'#' は空のスポットを表す
vector<vector<string>> state(n, vector<string>(n, "#"));
vector<bool> cols(n, false); // クイーンのある列を記録
vector<bool> diags1(2 * n - 1, false); // クイーンのある主対角線を記録
vector<bool> diags2(2 * n - 1, false); // クイーンのある副対角線を記録
vector<vector<vector<string>>> res;
backtrack(0, n, state, res, cols, diags1, diags2);
return res;
}
/* ドライバーコード */
int main() {
int n = 4;
vector<vector<vector<string>>> res = nQueens(n);
cout << "チェスボードの次元を " << n << " として入力" << endl;
cout << "クイーン配置解の総数 = " << res.size() << endl;
for (const vector<vector<string>> &state : res) {
cout << "--------------------" << endl;
for (const vector<string> &row : state) {
printVector(row);
}
}
return 0;
}

View File

@@ -0,0 +1,54 @@
/**
* File: permutations_i.cpp
* Created Time: 2023-04-24
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* バックトラッキングアルゴリズム:順列 I */
void backtrack(vector<int> &state, const vector<int> &choices, vector<bool> &selected, vector<vector<int>> &res) {
// 状態の長さが要素数と等しくなったら、解を記録
if (state.size() == choices.size()) {
res.push_back(state);
return;
}
// すべての選択肢を走査
for (int i = 0; i < choices.size(); i++) {
int choice = choices[i];
// 剪定:要素の重複選択を許可しない
if (!selected[i]) {
// 試行:選択を行い、状態を更新
selected[i] = true;
state.push_back(choice);
// 次のラウンドの選択に進む
backtrack(state, choices, selected, res);
// 回退:選択を取り消し、前の状態に復元
selected[i] = false;
state.pop_back();
}
}
}
/* 順列 I */
vector<vector<int>> permutationsI(vector<int> nums) {
vector<int> state;
vector<bool> selected(nums.size(), false);
vector<vector<int>> res;
backtrack(state, nums, selected, res);
return res;
}
/* ドライバーコード */
int main() {
vector<int> nums = {1, 2, 3};
vector<vector<int>> res = permutationsI(nums);
cout << "入力配列 nums = ";
printVector(nums);
cout << "すべての順列 res = ";
printVectorMatrix(res);
return 0;
}

View File

@@ -0,0 +1,56 @@
/**
* File: permutations_ii.cpp
* Created Time: 2023-04-24
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* バックトラッキングアルゴリズム:順列 II */
void backtrack(vector<int> &state, const vector<int> &choices, vector<bool> &selected, vector<vector<int>> &res) {
// 状態の長さが要素数と等しくなったら、解を記録
if (state.size() == choices.size()) {
res.push_back(state);
return;
}
// すべての選択肢を走査
unordered_set<int> duplicated;
for (int i = 0; i < choices.size(); i++) {
int choice = choices[i];
// 剪定:要素の重複選択を許可せず、等しい要素の重複選択も許可しない
if (!selected[i] && duplicated.find(choice) == duplicated.end()) {
// 試行:選択を行い、状態を更新
duplicated.emplace(choice); // 選択された要素値を記録
selected[i] = true;
state.push_back(choice);
// 次のラウンドの選択に進む
backtrack(state, choices, selected, res);
// 回退:選択を取り消し、前の状態に復元
selected[i] = false;
state.pop_back();
}
}
}
/* 順列 II */
vector<vector<int>> permutationsII(vector<int> nums) {
vector<int> state;
vector<bool> selected(nums.size(), false);
vector<vector<int>> res;
backtrack(state, nums, selected, res);
return res;
}
/* ドライバーコード */
int main() {
vector<int> nums = {1, 1, 2};
vector<vector<int>> res = permutationsII(nums);
cout << "入力配列 nums = ";
printVector(nums);
cout << "すべての順列 res = ";
printVectorMatrix(res);
return 0;
}

View File

@@ -0,0 +1,43 @@
/**
* File: preorder_traversal_i_compact.cpp
* Created Time: 2023-04-16
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
vector<TreeNode *> res;
/* 前順走査:例1 */
void preOrder(TreeNode *root) {
if (root == nullptr) {
return;
}
if (root->val == 7) {
// 解を記録
res.push_back(root);
}
preOrder(root->left);
preOrder(root->right);
}
/* ドライバーコード */
int main() {
vector<int> arr = {1, 7, 3, 4, 5, 6, 7};
TreeNode *root = vecToTree(arr);
cout << "\n二分木を初期化" << endl;
printTree(root);
// 前順走査
res.clear();
preOrder(root);
cout << "\n値7のードをすべて出力" << endl;
vector<int> vals;
for (TreeNode *node : res) {
vals.push_back(node->val);
}
printVector(vals);
return 0;
}

View File

@@ -0,0 +1,51 @@
/**
* File: preorder_traversal_ii_compact.cpp
* Created Time: 2023-04-16
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
vector<TreeNode *> path;
vector<vector<TreeNode *>> res;
/* 前順走査:例2 */
void preOrder(TreeNode *root) {
if (root == nullptr) {
return;
}
// 試行
path.push_back(root);
if (root->val == 7) {
// 解を記録
res.push_back(path);
}
preOrder(root->left);
preOrder(root->right);
// 回退
path.pop_back();
}
/* ドライバーコード */
int main() {
vector<int> arr = {1, 7, 3, 4, 5, 6, 7};
TreeNode *root = vecToTree(arr);
cout << "\n二分木を初期化" << endl;
printTree(root);
// 前順走査
path.clear();
res.clear();
preOrder(root);
cout << "\nルートからード7までのすべてのパスを出力" << endl;
for (vector<TreeNode *> &path : res) {
vector<int> vals;
for (TreeNode *node : path) {
vals.push_back(node->val);
}
printVector(vals);
}
return 0;
}

View File

@@ -0,0 +1,52 @@
/**
* File: preorder_traversal_iii_compact.cpp
* Created Time: 2023-04-16
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
vector<TreeNode *> path;
vector<vector<TreeNode *>> res;
/* 前順走査:例3 */
void preOrder(TreeNode *root) {
// 剪定
if (root == nullptr || root->val == 3) {
return;
}
// 試行
path.push_back(root);
if (root->val == 7) {
// 解を記録
res.push_back(path);
}
preOrder(root->left);
preOrder(root->right);
// 回退
path.pop_back();
}
/* ドライバーコード */
int main() {
vector<int> arr = {1, 7, 3, 4, 5, 6, 7};
TreeNode *root = vecToTree(arr);
cout << "\n二分木を初期化" << endl;
printTree(root);
// 前順走査
path.clear();
res.clear();
preOrder(root);
cout << "\nルートからード7までのすべてのパスを出力、値3のードは含まない" << endl;
for (vector<TreeNode *> &path : res) {
vector<int> vals;
for (TreeNode *node : path) {
vals.push_back(node->val);
}
printVector(vals);
}
return 0;
}

View File

@@ -0,0 +1,79 @@
/**
* File: preorder_traversal_iii_template.cpp
* Created Time: 2023-04-16
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 現在の状態が解かどうかを判定 */
bool isSolution(vector<TreeNode *> &state) {
return !state.empty() && state.back()->val == 7;
}
/* 解を記録 */
void recordSolution(vector<TreeNode *> &state, vector<vector<TreeNode *>> &res) {
res.push_back(state);
}
/* 現在の状態下で選択が合法かどうかを判定 */
bool isValid(vector<TreeNode *> &state, TreeNode *choice) {
return choice != nullptr && choice->val != 3;
}
/* 状態を更新 */
void makeChoice(vector<TreeNode *> &state, TreeNode *choice) {
state.push_back(choice);
}
/* 状態を復元 */
void undoChoice(vector<TreeNode *> &state, TreeNode *choice) {
state.pop_back();
}
/* バックトラッキングアルゴリズム:例3 */
void backtrack(vector<TreeNode *> &state, vector<TreeNode *> &choices, vector<vector<TreeNode *>> &res) {
// 解かどうかをチェック
if (isSolution(state)) {
// 解を記録
recordSolution(state, res);
}
// すべての選択肢を走査
for (TreeNode *choice : choices) {
// 剪定:選択が合法かどうかをチェック
if (isValid(state, choice)) {
// 試行:選択を行い、状態を更新
makeChoice(state, choice);
// 次のラウンドの選択に進む
vector<TreeNode *> nextChoices{choice->left, choice->right};
backtrack(state, nextChoices, res);
// 回退:選択を取り消し、前の状態に復元
undoChoice(state, choice);
}
}
}
/* ドライバーコード */
int main() {
vector<int> arr = {1, 7, 3, 4, 5, 6, 7};
TreeNode *root = vecToTree(arr);
cout << "\n二分木を初期化" << endl;
printTree(root);
// バックトラッキングアルゴリズム
vector<TreeNode *> state;
vector<TreeNode *> choices = {root};
vector<vector<TreeNode *>> res;
backtrack(state, choices, res);
cout << "\nルートからード7までのすべてのパスを出力、パスには値3のードを含まないことが要求される" << endl;
for (vector<TreeNode *> &path : res) {
vector<int> vals;
for (TreeNode *node : path) {
vals.push_back(node->val);
}
printVector(vals);
}
return 0;
}

View File

@@ -0,0 +1,57 @@
/**
* File: subset_sum_i.cpp
* Created Time: 2023-06-21
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* バックトラッキングアルゴリズム:部分集合和 I */
void backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {
// 部分集合の和がtargetと等しいとき、解を記録
if (target == 0) {
res.push_back(state);
return;
}
// すべての選択肢を走査
// 剪定二startから走査を開始し、重複する部分集合の生成を回避
for (int i = start; i < choices.size(); i++) {
// 剪定一部分集合の和がtargetを超えた場合、即座にループを終了
// 配列がソートされているため、後の要素はさらに大きく、部分集合の和は必ずtargetを超える
if (target - choices[i] < 0) {
break;
}
// 試行選択を行い、target、startを更新
state.push_back(choices[i]);
// 次のラウンドの選択に進む
backtrack(state, target - choices[i], choices, i, res);
// 回退:選択を取り消し、前の状態に復元
state.pop_back();
}
}
/* 部分集合和 I を解く */
vector<vector<int>> subsetSumI(vector<int> nums, int target) {
vector<int> state; // 状態(部分集合)
sort(nums.begin(), nums.end()); // nums をソート
int start = 0; // 走査の開始点
vector<vector<int>> res; // 結果リスト(部分集合リスト)
backtrack(state, target, nums, start, res);
return res;
}
/* ドライバーコード */
int main() {
vector<int> nums = {3, 4, 5};
int target = 9;
vector<vector<int>> res = subsetSumI(nums, target);
cout << "入力配列 nums = ";
printVector(nums);
cout << "target = " << target << endl;
cout << "和が " << target << " のすべての部分集合 res = " << endl;
printVectorMatrix(res);
return 0;
}

View File

@@ -0,0 +1,55 @@
/**
* File: subset_sum_i_naive.cpp
* Created Time: 2023-06-21
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* バックトラッキングアルゴリズム:部分集合和 I */
void backtrack(vector<int> &state, int target, int total, vector<int> &choices, vector<vector<int>> &res) {
// 部分集合の和がtargetと等しいとき、解を記録
if (total == target) {
res.push_back(state);
return;
}
// すべての選択肢を走査
for (int i = 0; i < choices.size(); i++) {
// 剪定部分集合の和がtargetを超えた場合、その選択をスキップ
if (total + choices[i] > target) {
continue;
}
// 試行選択を行い、要素とtotalを更新
state.push_back(choices[i]);
// 次のラウンドの選択に進む
backtrack(state, target, total + choices[i], choices, res);
// 回退:選択を取り消し、前の状態に復元
state.pop_back();
}
}
/* 部分集合和 I を解く(重複する部分集合を含む) */
vector<vector<int>> subsetSumINaive(vector<int> nums, int target) {
vector<int> state; // 状態(部分集合)
int total = 0; // 部分集合の和
vector<vector<int>> res; // 結果リスト(部分集合リスト)
backtrack(state, target, total, nums, res);
return res;
}
/* ドライバーコード */
int main() {
vector<int> nums = {3, 4, 5};
int target = 9;
vector<vector<int>> res = subsetSumINaive(nums, target);
cout << "入力配列 nums = ";
printVector(nums);
cout << "target = " << target << endl;
cout << "和が " << target << " のすべての部分集合 res = " << endl;
printVectorMatrix(res);
cout << "この方法の結果には重複する集合が含まれています" << endl;
return 0;
}

View File

@@ -0,0 +1,62 @@
/**
* File: subset_sum_ii.cpp
* Created Time: 2023-06-21
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* バックトラッキングアルゴリズム:部分集合和 II */
void backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {
// 部分集合の和がtargetと等しいとき、解を記録
if (target == 0) {
res.push_back(state);
return;
}
// すべての選択肢を走査
// 剪定二startから走査を開始し、重複する部分集合の生成を回避
// 剪定三startから走査を開始し、同じ要素の繰り返し選択を回避
for (int i = start; i < choices.size(); i++) {
// 剪定一部分集合の和がtargetを超えた場合、即座にループを終了
// 配列がソートされているため、後の要素はさらに大きく、部分集合の和は必ずtargetを超える
if (target - choices[i] < 0) {
break;
}
// 剪定四:要素が左の要素と等しい場合、検索ブランチの重複を示すのでスキップ
if (i > start && choices[i] == choices[i - 1]) {
continue;
}
// 試行選択を行い、target、startを更新
state.push_back(choices[i]);
// 次のラウンドの選択に進む
backtrack(state, target - choices[i], choices, i + 1, res);
// 回退:選択を取り消し、前の状態に復元
state.pop_back();
}
}
/* 部分集合和 II を解く */
vector<vector<int>> subsetSumII(vector<int> nums, int target) {
vector<int> state; // 状態(部分集合)
sort(nums.begin(), nums.end()); // nums をソート
int start = 0; // 走査の開始点
vector<vector<int>> res; // 結果リスト(部分集合リスト)
backtrack(state, target, nums, start, res);
return res;
}
/* ドライバーコード */
int main() {
vector<int> nums = {4, 4, 5};
int target = 9;
vector<vector<int>> res = subsetSumII(nums, target);
cout << "入力配列 nums = ";
printVector(nums);
cout << "target = " << target << endl;
cout << "和が " << target << " のすべての部分集合 res = " << endl;
printVectorMatrix(res);
return 0;
}

View File

@@ -0,0 +1,76 @@
/**
* File: iteration.cpp
* Created Time: 2023-08-24
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* for ループ */
int forLoop(int n) {
int res = 0;
// 1, 2, ..., n-1, n の合計をループ計算
for (int i = 1; i <= n; ++i) {
res += i;
}
return res;
}
/* while ループ */
int whileLoop(int n) {
int res = 0;
int i = 1; // 条件変数を初期化
// 1, 2, ..., n-1, n の合計をループ計算
while (i <= n) {
res += i;
i++; // 条件変数を更新
}
return res;
}
/* while ループ2つの更新 */
int whileLoopII(int n) {
int res = 0;
int i = 1; // 条件変数を初期化
// 1, 4, 10, ... の合計をループ計算
while (i <= n) {
res += i;
// 条件変数を更新
i++;
i *= 2;
}
return res;
}
/* 2重 for ループ */
string nestedForLoop(int n) {
ostringstream res;
// ループ i = 1, 2, ..., n-1, n
for (int i = 1; i <= n; ++i) {
// ループ j = 1, 2, ..., n-1, n
for (int j = 1; j <= n; ++j) {
res << "(" << i << ", " << j << "), ";
}
}
return res.str();
}
/* ドライバーコード */
int main() {
int n = 5;
int res;
res = forLoop(n);
cout << "\nfor ループの合計結果 res = " << res << endl;
res = whileLoop(n);
cout << "\nwhile ループの合計結果 res = " << res << endl;
res = whileLoopII(n);
cout << "\nwhile ループ2つの更新の合計結果 res = " << res << endl;
string resStr = nestedForLoop(n);
cout << "\n2重 for ループ走査の結果 = " << resStr << endl;
return 0;
}

View File

@@ -0,0 +1,78 @@
/**
* File: recursion.cpp
* Created Time: 2023-08-24
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 再帰 */
int recur(int n) {
// 終了条件
if (n == 1)
return 1;
// 再帰:再帰呼び出し
int res = recur(n - 1);
// 戻り値:結果を返す
return n + res;
}
/* 反復で再帰をシミュレート */
int forLoopRecur(int n) {
// 明示的なスタックを使用してシステムコールスタックをシミュレート
stack<int> stack;
int res = 0;
// 再帰:再帰呼び出し
for (int i = n; i > 0; i--) {
// 「スタックへのプッシュ」で「再帰」をシミュレート
stack.push(i);
}
// 戻り値:結果を返す
while (!stack.empty()) {
// 「スタックからのポップ」で「戻り値」をシミュレート
res += stack.top();
stack.pop();
}
// res = 1+2+3+...+n
return res;
}
/* 末尾再帰 */
int tailRecur(int n, int res) {
// 終了条件
if (n == 0)
return res;
// 末尾再帰呼び出し
return tailRecur(n - 1, res + n);
}
/* フィボナッチ数列:再帰 */
int fib(int n) {
// 終了条件 f(1) = 0, f(2) = 1
if (n == 1 || n == 2)
return n - 1;
// 再帰呼び出し f(n) = f(n-1) + f(n-2)
int res = fib(n - 1) + fib(n - 2);
// 結果 f(n) を返す
return res;
}
/* ドライバーコード */
int main() {
int n = 5;
int res;
res = recur(n);
cout << "\n再帰関数の合計結果 res = " << res << endl;
res = forLoopRecur(n);
cout << "\n反復を使用して再帰をシミュレートした合計結果 res = " << res << endl;
res = tailRecur(n, 0);
cout << "\n末尾再帰関数の合計結果 res = " << res << endl;
res = fib(n);
cout << "フィボナッチ数列の第 " << n << " 番目の数は " << res << endl;
return 0;
}

View File

@@ -0,0 +1,107 @@
/**
* File: space_complexity.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 関数 */
int func() {
// 何らかの操作を実行
return 0;
}
/* 定数計算量 */
void constant(int n) {
// 定数、変数、オブジェクトは O(1) 空間を占める
const int a = 0;
int b = 0;
vector<int> nums(10000);
ListNode node(0);
// ループ内の変数は O(1) 空間を占める
for (int i = 0; i < n; i++) {
int c = 0;
}
// ループ内の関数は O(1) 空間を占める
for (int i = 0; i < n; i++) {
func();
}
}
/* 線形計算量 */
void linear(int n) {
// 長さ n の配列は O(n) 空間を占める
vector<int> nums(n);
// 長さ n のリストは O(n) 空間を占める
vector<ListNode> nodes;
for (int i = 0; i < n; i++) {
nodes.push_back(ListNode(i));
}
// 長さ n のハッシュテーブルは O(n) 空間を占める
unordered_map<int, string> map;
for (int i = 0; i < n; i++) {
map[i] = to_string(i);
}
}
/* 線形計算量(再帰実装) */
void linearRecur(int n) {
cout << "再帰 n = " << n << endl;
if (n == 1)
return;
linearRecur(n - 1);
}
/* 二次計算量 */
void quadratic(int n) {
// 二次元リストは O(n^2) 空間を占める
vector<vector<int>> numMatrix;
for (int i = 0; i < n; i++) {
vector<int> tmp;
for (int j = 0; j < n; j++) {
tmp.push_back(0);
}
numMatrix.push_back(tmp);
}
}
/* 二次計算量(再帰実装) */
int quadraticRecur(int n) {
if (n <= 0)
return 0;
vector<int> nums(n);
cout << "再帰 n = " << n << ", nums の長さ = " << nums.size() << endl;
return quadraticRecur(n - 1);
}
/* 指数計算量(完全二分木の構築) */
TreeNode *buildTree(int n) {
if (n == 0)
return nullptr;
TreeNode *root = new TreeNode(0);
root->left = buildTree(n - 1);
root->right = buildTree(n - 1);
return root;
}
/* ドライバーコード */
int main() {
int n = 5;
// 定数計算量
constant(n);
// 線形計算量
linear(n);
linearRecur(n);
// 二次計算量
quadratic(n);
quadraticRecur(n);
// 指数計算量
TreeNode *root = buildTree(n);
printTree(root);
// メモリを解放
freeMemoryTree(root);
return 0;
}

View File

@@ -0,0 +1,168 @@
/**
* File: time_complexity.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 定数計算量 */
int constant(int n) {
int count = 0;
int size = 100000;
for (int i = 0; i < size; i++)
count++;
return count;
}
/* 線形計算量 */
int linear(int n) {
int count = 0;
for (int i = 0; i < n; i++)
count++;
return count;
}
/* 線形計算量(配列の走査) */
int arrayTraversal(vector<int> &nums) {
int count = 0;
// ループ回数は配列の長さに比例
for (int num : nums) {
count++;
}
return count;
}
/* 二次計算量 */
int quadratic(int n) {
int count = 0;
// ループ回数はデータサイズ n の二乗に比例
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
count++;
}
}
return count;
}
/* 二次計算量(バブルソート) */
int bubbleSort(vector<int> &nums) {
int count = 0; // カウンター
// 外側ループ:未ソート範囲は [0, i]
for (int i = nums.size() - 1; i > 0; i--) {
// 内側ループ:未ソート範囲 [0, i] の最大要素を範囲の右端にスワップ
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// nums[j] と nums[j + 1] をスワップ
int tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
count += 3; // 要素のスワップには3つの個別操作が含まれる
}
}
}
return count;
}
/* 指数計算量(ループ実装) */
int exponential(int n) {
int count = 0, base = 1;
// セルは毎ラウンド2つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成
for (int i = 0; i < n; i++) {
for (int j = 0; j < base; j++) {
count++;
}
base *= 2;
}
// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
return count;
}
/* 指数計算量(再帰実装) */
int expRecur(int n) {
if (n == 1)
return 1;
return expRecur(n - 1) + expRecur(n - 1) + 1;
}
/* 対数計算量(ループ実装) */
int logarithmic(int n) {
int count = 0;
while (n > 1) {
n = n / 2;
count++;
}
return count;
}
/* 対数計算量(再帰実装) */
int logRecur(int n) {
if (n <= 1)
return 0;
return logRecur(n / 2) + 1;
}
/* 線形対数計算量 */
int linearLogRecur(int n) {
if (n <= 1)
return 1;
int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);
for (int i = 0; i < n; i++) {
count++;
}
return count;
}
/* 階乗計算量(再帰実装) */
int factorialRecur(int n) {
if (n == 0)
return 1;
int count = 0;
// 1から n に分裂
for (int i = 0; i < n; i++) {
count += factorialRecur(n - 1);
}
return count;
}
/* ドライバーコード */
int main() {
// n を変更して、さまざまな計算量での操作回数の変化傾向を体験可能
int n = 8;
cout << "入力データサイズ n = " << n << endl;
int count = constant(n);
cout << "定数計算量の操作回数 = " << count << endl;
count = linear(n);
cout << "線形計算量の操作回数 = " << count << endl;
vector<int> arr(n);
count = arrayTraversal(arr);
cout << "線形計算量の操作回数(配列走査) = " << count << endl;
count = quadratic(n);
cout << "二次計算量の操作回数 = " << count << endl;
vector<int> nums(n);
for (int i = 0; i < n; i++)
nums[i] = n - i; // [n,n-1,...,2,1]
count = bubbleSort(nums);
cout << "二次計算量の操作回数(バブルソート) = " << count << endl;
count = exponential(n);
cout << "指数計算量の操作回数(ループ実装) = " << count << endl;
count = expRecur(n);
cout << "指数計算量の操作回数(再帰実装) = " << count << endl;
count = logarithmic(n);
cout << "対数計算量の操作回数(ループ実装) = " << count << endl;
count = logRecur(n);
cout << "対数計算量の操作回数(再帰実装) = " << count << endl;
count = linearLogRecur(n);
cout << "線形対数計算量の操作回数(再帰実装) = " << count << endl;
count = factorialRecur(n);
cout << "階乗計算量の操作回数(再帰実装) = " << count << endl;
return 0;
}

View File

@@ -0,0 +1,45 @@
/**
* File: worst_best_time_complexity.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 要素 {1, 2, ..., n} をランダムにシャッフルした配列を生成 */
vector<int> randomNumbers(int n) {
vector<int> nums(n);
// 配列 nums = { 1, 2, 3, ..., n } を生成
for (int i = 0; i < n; i++) {
nums[i] = i + 1;
}
// システム時刻を使用してランダムシードを生成
unsigned seed = chrono::system_clock::now().time_since_epoch().count();
// 配列要素をランダムにシャッフル
shuffle(nums.begin(), nums.end(), default_random_engine(seed));
return nums;
}
/* 配列 nums で数値1のインデックスを見つける */
int findOne(vector<int> &nums) {
for (int i = 0; i < nums.size(); i++) {
// 要素1が配列の先頭にある場合、最良時間計算量 O(1) を達成
// 要素1が配列の末尾にある場合、最悪時間計算量 O(n) を達成
if (nums[i] == 1)
return i;
}
return -1;
}
/* ドライバーコード */
int main() {
for (int i = 0; i < 1000; i++) {
int n = 100;
vector<int> nums = randomNumbers(n);
int index = findOne(nums);
cout << "\n配列 [ 1, 2, ..., n ] をシャッフル後 = ";
printVector(nums);
cout << "数値1のインデックスは " << index << endl;
}
return 0;
}

View File

@@ -0,0 +1,46 @@
/**
* File: binary_search_recur.cpp
* Created Time: 2023-07-17
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 二分探索:問題 f(i, j) */
int dfs(vector<int> &nums, int target, int i, int j) {
// 区間が空の場合、対象要素が存在しないことを示すため、-1 を返す
if (i > j) {
return -1;
}
// 中点インデックス m を計算
int m = i + (j - i) / 2;
if (nums[m] < target) {
// 再帰的な部分問題 f(m+1, j)
return dfs(nums, target, m + 1, j);
} else if (nums[m] > target) {
// 再帰的な部分問題 f(i, m-1)
return dfs(nums, target, i, m - 1);
} else {
// 対象要素が見つかったため、そのインデックスを返す
return m;
}
}
/* 二分探索 */
int binarySearch(vector<int> &nums, int target) {
int n = nums.size();
// 問題 f(0, n-1) を解く
return dfs(nums, target, 0, n - 1);
}
/* ドライバーコード */
int main() {
int target = 6;
vector<int> nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35};
// 二分探索(両端閉区間)
int index = binarySearch(nums, target);
cout << "対象要素 6 のインデックス =" << index << endl;
return 0;
}

View File

@@ -0,0 +1,51 @@
/**
* File: build_tree.cpp
* Created Time: 2023-07-17
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 二分木の構築:分割統治 */
TreeNode *dfs(vector<int> &preorder, unordered_map<int, int> &inorderMap, int i, int l, int r) {
// 部分木の区間が空の場合に終了
if (r - l < 0)
return NULL;
// ルートノードを初期化
TreeNode *root = new TreeNode(preorder[i]);
// m を問い合わせて左右の部分木を分割
int m = inorderMap[preorder[i]];
// 部分問題:左の部分木を構築
root->left = dfs(preorder, inorderMap, i + 1, l, m - 1);
// 部分問題:右の部分木を構築
root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);
// ルートノードを返す
return root;
}
/* 二分木の構築 */
TreeNode *buildTree(vector<int> &preorder, vector<int> &inorder) {
// ハッシュテーブルを初期化し、中間順序の要素からインデックスへのマッピングを格納
unordered_map<int, int> inorderMap;
for (int i = 0; i < inorder.size(); i++) {
inorderMap[inorder[i]] = i;
}
TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1);
return root;
}
/* ドライバーコード */
int main() {
vector<int> preorder = {3, 9, 2, 1, 7};
vector<int> inorder = {9, 3, 1, 2, 7};
cout << "前順走査 = ";
printVector(preorder);
cout << "中間順序走査 = ";
printVector(inorder);
TreeNode *root = buildTree(preorder, inorder);
cout << "構築された二分木:\n";
printTree(root);
return 0;
}

View File

@@ -0,0 +1,66 @@
/**
* File: hanota.cpp
* Created Time: 2023-07-17
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 円盤を移動 */
void move(vector<int> &src, vector<int> &tar) {
// src の最上部から円盤を取り出す
int pan = src.back();
src.pop_back();
// 円盤を tar の最上部に配置
tar.push_back(pan);
}
/* ハノイの塔問題 f(i) を解く */
void dfs(int i, vector<int> &src, vector<int> &buf, vector<int> &tar) {
// src に円盤が1つだけ残っている場合、それを tar に移動
if (i == 1) {
move(src, tar);
return;
}
// 部分問題 f(i-1)tar の助けを借りて、上位 i-1 個の円盤を src から buf に移動
dfs(i - 1, src, tar, buf);
// 部分問題 f(1)残りの1つの円盤を src から tar に移動
move(src, tar);
// 部分問題 f(i-1)src の助けを借りて、上位 i-1 個の円盤を buf から tar に移動
dfs(i - 1, buf, src, tar);
}
/* ハノイの塔問題を解く */
void solveHanota(vector<int> &A, vector<int> &B, vector<int> &C) {
int n = A.size();
// B の助けを借りて、上位 n 個の円盤を A から C に移動
dfs(n, A, B, C);
}
/* ドライバーコード */
int main() {
// リストの末尾が柱の最上部
vector<int> A = {5, 4, 3, 2, 1};
vector<int> B = {};
vector<int> C = {};
cout << "初期状態:\n";
cout << "A =";
printVector(A);
cout << "B =";
printVector(B);
cout << "C =";
printVector(C);
solveHanota(A, B, C);
cout << "円盤移動後:\n";
cout << "A =";
printVector(A);
cout << "B =";
printVector(B);
cout << "C =";
printVector(C);
return 0;
}

View File

@@ -0,0 +1,42 @@
/**
* File: climbing_stairs_backtrack.cpp
* Created Time: 2023-06-30
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* バックトラッキング */
void backtrack(vector<int> &choices, int state, int n, vector<int> &res) {
// n段目に到達したとき、解の数に1を加える
if (state == n)
res[0]++;
// すべての選択肢を走査
for (auto &choice : choices) {
// 剪定n段を超えて登ることを許可しない
if (state + choice > n)
continue;
// 試行:選択を行い、状態を更新
backtrack(choices, state + choice, n, res);
// 撤回
}
}
/* 階段登り:バックトラッキング */
int climbingStairsBacktrack(int n) {
vector<int> choices = {1, 2}; // 1段または2段登ることを選択可能
int state = 0; // 0段目から登り始める
vector<int> res = {0}; // res[0] を使用して解の数を記録
backtrack(choices, state, n, res);
return res[0];
}
/* ドライバーコード */
int main() {
int n = 9;
int res = climbingStairsBacktrack(n);
cout << n << "段の階段を登る解は" << res << "通りです" << endl;
return 0;
}

View File

@@ -0,0 +1,37 @@
/**
* File: climbing_stairs_constraint_dp.cpp
* Created Time: 2023-07-01
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 制約付き階段登り:動的プログラミング */
int climbingStairsConstraintDP(int n) {
if (n == 1 || n == 2) {
return 1;
}
// DPテーブルを初期化し、部分問題の解を格納するために使用
vector<vector<int>> dp(n + 1, vector<int>(3, 0));
// 初期状態:最小の部分問題の解を事前設定
dp[1][1] = 1;
dp[1][2] = 0;
dp[2][1] = 0;
dp[2][2] = 1;
// 状態遷移:小さな問題から大きな部分問題を段階的に解く
for (int i = 3; i <= n; i++) {
dp[i][1] = dp[i - 1][2];
dp[i][2] = dp[i - 2][1] + dp[i - 2][2];
}
return dp[n][1] + dp[n][2];
}
/* ドライバーコード */
int main() {
int n = 9;
int res = climbingStairsConstraintDP(n);
cout << n << "段の階段を登る解は" << res << "通りです" << endl;
return 0;
}

View File

@@ -0,0 +1,32 @@
/**
* File: climbing_stairs_dfs.cpp
* Created Time: 2023-06-30
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 探索 */
int dfs(int i) {
// 既知の dp[1] と dp[2] を返す
if (i == 1 || i == 2)
return i;
// dp[i] = dp[i-1] + dp[i-2]
int count = dfs(i - 1) + dfs(i - 2);
return count;
}
/* 階段登り:探索 */
int climbingStairsDFS(int n) {
return dfs(n);
}
/* ドライバーコード */
int main() {
int n = 9;
int res = climbingStairsDFS(n);
cout << n << "段の階段を登る解は" << res << "通りです" << endl;
return 0;
}

View File

@@ -0,0 +1,39 @@
/**
* File: climbing_stairs_dfs_mem.cpp
* Created Time: 2023-06-30
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* メモ化探索 */
int dfs(int i, vector<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 は記録なしを意味する
vector<int> mem(n + 1, -1);
return dfs(n, mem);
}
/* ドライバーコード */
int main() {
int n = 9;
int res = climbingStairsDFSMem(n);
cout << n << "段の階段を登る解は" << res << "通りです" << endl;
return 0;
}

View File

@@ -0,0 +1,49 @@
/**
* File: climbing_stairs_dp.cpp
* Created Time: 2023-06-30
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 階段登り:動的プログラミング */
int climbingStairsDP(int n) {
if (n == 1 || n == 2)
return n;
// DPテーブルを初期化し、部分問題の解を格納するために使用
vector<int> dp(n + 1);
// 初期状態:最小の部分問題の解を事前設定
dp[1] = 1;
dp[2] = 2;
// 状態遷移:小さな問題から大きな部分問題を段階的に解く
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
/* 階段登り:空間最適化動的プログラミング */
int climbingStairsDPComp(int n) {
if (n == 1 || n == 2)
return n;
int a = 1, b = 2;
for (int i = 3; i <= n; i++) {
int tmp = b;
b = a + b;
a = tmp;
}
return b;
}
/* ドライバーコード */
int main() {
int n = 9;
int res = climbingStairsDP(n);
cout << n << "段の階段を登る解は" << res << "通りです" << endl;
res = climbingStairsDPComp(n);
cout << n << "段の階段を登る解は" << res << "通りです" << endl;
return 0;
}

View File

@@ -0,0 +1,70 @@
/**
* File: coin_change.cpp
* Created Time: 2023-07-11
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 硬貨両替:動的プログラミング */
int coinChangeDP(vector<int> &coins, int amt) {
int n = coins.size();
int MAX = amt + 1;
// DPテーブルを初期化
vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));
// 状態遷移:最初の行と最初の列
for (int a = 1; a <= amt; a++) {
dp[0][a] = MAX;
}
// 状態遷移:残りの行と列
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 目標金額を超える場合、硬貨 i を選択しない
dp[i][a] = dp[i - 1][a];
} else {
// 選択しない場合と硬貨 i を選択する場合のより小さい値
dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);
}
}
}
return dp[n][amt] != MAX ? dp[n][amt] : -1;
}
/* 硬貨両替:空間最適化動的プログラミング */
int coinChangeDPComp(vector<int> &coins, int amt) {
int n = coins.size();
int MAX = amt + 1;
// DPテーブルを初期化
vector<int> dp(amt + 1, MAX);
dp[0] = 0;
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 目標金額を超える場合、硬貨 i を選択しない
dp[a] = dp[a];
} else {
// 選択しない場合と硬貨 i を選択する場合のより小さい値
dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);
}
}
}
return dp[amt] != MAX ? dp[amt] : -1;
}
/* ドライバーコード */
int main() {
vector<int> coins = {1, 2, 5};
int amt = 4;
// 動的プログラミング
int res = coinChangeDP(coins, amt);
cout << "目標金額を作るのに必要な最小硬貨数は " << res << " です" << endl;
// 空間最適化動的プログラミング
res = coinChangeDPComp(coins, amt);
cout << "目標金額を作るのに必要な最小硬貨数は " << res << " です" << endl;
return 0;
}

View File

@@ -0,0 +1,68 @@
/**
* File: coin_change_ii.cpp
* Created Time: 2023-07-11
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 硬貨両替 II動的プログラミング */
int coinChangeIIDP(vector<int> &coins, int amt) {
int n = coins.size();
// DPテーブルを初期化
vector<vector<int>> dp(n + 1, vector<int>(amt + 1, 0));
// 最初の列を初期化
for (int i = 0; i <= n; i++) {
dp[i][0] = 1;
}
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 目標金額を超える場合、硬貨 i を選択しない
dp[i][a] = dp[i - 1][a];
} else {
// 選択しない場合と硬貨 i を選択する場合の2つの選択肢の合計
dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];
}
}
}
return dp[n][amt];
}
/* 硬貨両替 II空間最適化動的プログラミング */
int coinChangeIIDPComp(vector<int> &coins, int amt) {
int n = coins.size();
// DPテーブルを初期化
vector<int> dp(amt + 1, 0);
dp[0] = 1;
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int a = 1; a <= amt; a++) {
if (coins[i - 1] > a) {
// 目標金額を超える場合、硬貨 i を選択しない
dp[a] = dp[a];
} else {
// 選択しない場合と硬貨 i を選択する場合の2つの選択肢の合計
dp[a] = dp[a] + dp[a - coins[i - 1]];
}
}
}
return dp[amt];
}
/* ドライバーコード */
int main() {
vector<int> coins = {1, 2, 5};
int amt = 5;
// 動的プログラミング
int res = coinChangeIIDP(coins, amt);
cout << "目標金額を作る硬貨の組み合わせ数は " << res << " です" << endl;
// 空間最適化動的プログラミング
res = coinChangeIIDPComp(coins, amt);
cout << "目標金額を作る硬貨の組み合わせ数は " << res << " です" << endl;
return 0;
}

View File

@@ -0,0 +1,72 @@
/**
* File: edit_distance.cpp
* Created Time: 2023-07-13
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 編集距離:ブルートフォース探索 */
int editDistanceDFS(string s, string t, int i, int j) {
// s と t の両方が空の場合、0 を返す
if (i == 0 && j == 0)
return 0;
// s が空の場合、t の長さを返す
if (i == 0)
return j;
// t が空の場合、s の長さを返す
if (j == 0)
return i;
// 2つの文字が等しい場合、これら2つの文字をスキップ
if (s[i - 1] == t[j - 1])
return editDistanceDFS(s, t, i - 1, j - 1);
// 最小編集数 = 3つの操作挿入、削除、置換からの最小編集数 + 1
int insert = editDistanceDFS(s, t, i, j - 1);
int del = editDistanceDFS(s, t, i - 1, j);
int replace = editDistanceDFS(s, t, i - 1, j - 1);
// 最小編集数を返す
return min(min(insert, del), replace) + 1;
}
/* 編集距離:動的プログラミング */
int editDistanceDP(string s, string t) {
int n = s.length(), m = t.length();
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
// 状態遷移:最初の行と最初の列
for (int i = 1; i <= n; i++) {
dp[i][0] = i;
}
for (int j = 1; j <= m; j++) {
dp[0][j] = j;
}
// 状態遷移:残りの行と列
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s[i - 1] == t[j - 1]) {
// 2つの文字が等しい場合、これら2つの文字をスキップ
dp[i][j] = dp[i - 1][j - 1];
} else {
// 最小編集数 = 3つの操作挿入、削除、置換からの最小編集数 + 1
dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
}
}
}
return dp[n][m];
}
/* ドライバーコード */
int main() {
string s = "bag";
string t = "pack";
int n = s.length(), m = t.length();
// ブルートフォース探索
int res = editDistanceDFS(s, t, n, m);
cout << s << "" << t << " に変更するには最低 " << res << " 回の編集が必要です" << endl;
// 動的プログラミング
res = editDistanceDP(s, t);
cout << s << "" << t << " に変更するには最低 " << res << " 回の編集が必要です" << endl;
return 0;
}

View File

@@ -0,0 +1,84 @@
/**
* File: knapsack.cpp
* Created Time: 2023-07-10
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 0-1 ナップサック:ブルートフォース探索 */
int knapsackDFS(vector<int> &wgt, vector<int> &val, int i, int c) {
// すべてのアイテムが選択されたか、ナップサックに残り容量がない場合、値 0 を返す
if (i == 0 || c == 0) {
return 0;
}
// ナップサックの容量を超える場合、ナップサックに入れないことしか選択できない
if (wgt[i - 1] > c) {
return knapsackDFS(wgt, val, i - 1, c);
}
// アイテム i を入れない場合と入れる場合の最大値を計算
int no = knapsackDFS(wgt, val, i - 1, c);
int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];
// 2つの選択肢のより大きい値を返す
return max(no, yes);
}
/* 0-1 ナップサック:動的プログラミング */
int knapsackDP(vector<int> &wgt, vector<int> &val, int cap) {
int n = wgt.size();
// DPテーブルを初期化
vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0));
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int c = 1; c <= cap; c++) {
if (wgt[i - 1] > c) {
// ナップサックの容量を超える場合、アイテム i を選択しない
dp[i][c] = dp[i - 1][c];
} else {
// 選択しない場合とアイテム i を選択する場合のより大きい値
dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);
}
}
}
return dp[n][cap];
}
/* 0-1 ナップサック:空間最適化動的プログラミング */
int knapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {
int n = wgt.size();
// DPテーブルを初期化
vector<int> dp(cap + 1, 0);
// 状態遷移
for (int i = 1; i <= n; i++) {
// 逆順で走査
for (int c = cap; c >= 1; c--) {
if (wgt[i - 1] <= c) {
// 選択しない場合とアイテム i を選択する場合のより大きい値
dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);
}
}
}
return dp[cap];
}
/* ドライバーコード */
int main() {
vector<int> wgt = {10, 20, 30, 40, 50};
vector<int> val = {50, 120, 150, 210, 240};
int cap = 50;
int n = wgt.size();
// ブルートフォース探索
int res = knapsackDFS(wgt, val, n, cap);
cout << "ナップサック容量内での最大値は " << res << " です" << endl;
// 動的プログラミング
res = knapsackDP(wgt, val, cap);
cout << "ナップサック容量内での最大値は " << res << " です" << endl;
// 空間最適化動的プログラミング
res = knapsackDPComp(wgt, val, cap);
cout << "ナップサック容量内での最大値は " << res << " です" << endl;
return 0;
}

View File

@@ -0,0 +1,57 @@
/**
* File: min_cost_climbing_stairs_dp.cpp
* Created Time: 2023-06-30
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 最小コスト階段登り:動的プログラミング */
int minCostClimbingStairsDP(vector<int> &cost) {
int n = cost.size() - 1;
if (n == 1 || n == 2)
return cost[n];
// DPテーブルを初期化し、部分問題の解を格納するために使用
vector<int> dp(n + 1);
// 初期状態:最小の部分問題の解を事前設定
dp[1] = cost[1];
dp[2] = cost[2];
// 状態遷移:小さな問題から大きな部分問題を段階的に解く
for (int i = 3; i <= n; i++) {
dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];
}
return dp[n];
}
/* 最小コスト階段登り:空間最適化動的プログラミング */
int minCostClimbingStairsDPComp(vector<int> &cost) {
int n = cost.size() - 1;
if (n == 1 || n == 2)
return cost[n];
int a = cost[1], b = cost[2];
for (int i = 3; i <= n; i++) {
int tmp = b;
b = min(a, tmp) + cost[i];
a = tmp;
}
return b;
}
/* ドライバーコード */
int main() {
vector<int> cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1};
cout << "階段のコストリストを [";
for (int i = 0; i < cost.size(); i++) {
cout << cost[i];
if (i < cost.size() - 1) cout << ", ";
}
cout << "] として入力" << endl;
int res = minCostClimbingStairsDP(cost);
cout << "階段を登るための最小コスト " << res << endl;
res = minCostClimbingStairsDPComp(cost);
cout << "階段を登るための最小コスト " << res << endl;
return 0;
}

View File

@@ -0,0 +1,68 @@
/**
* File: min_path_sum.cpp
* Created Time: 2023-07-10
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 最小パス和:ブルートフォース探索 */
int minPathSumDFS(vector<vector<int>> &grid, int i, int j) {
// 左上のセルの場合、探索を終了
if (i == 0 && j == 0) {
return grid[0][0];
}
// 行または列のインデックスが範囲外の場合、+∞ のコストを返す
if (i < 0 || j < 0) {
return INT_MAX;
}
// 左上から (i-1, j) と (i, j-1) への最小パスコストを計算
int up = minPathSumDFS(grid, i - 1, j);
int left = minPathSumDFS(grid, i, j - 1);
// 左上から (i, j) への最小パスコストを返す
return min(left, up) + grid[i][j];
}
/* 最小パス和:動的プログラミング */
int minPathSumDP(vector<vector<int>> &grid) {
int n = grid.size(), m = grid[0].size();
// DPテーブルを初期化
vector<vector<int>> dp(n, vector<int>(m));
dp[0][0] = grid[0][0];
// 状態遷移:最初の行
for (int j = 1; j < m; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
// 状態遷移:最初の列
for (int i = 1; i < n; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
// 状態遷移:残りの行と列
for (int i = 1; i < n; i++) {
for (int j = 1; j < m; j++) {
dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];
}
}
return dp[n - 1][m - 1];
}
/* ドライバーコード */
int main() {
vector<vector<int>> grid = {
{1, 3, 1, 5},
{2, 2, 4, 2},
{5, 3, 2, 1},
{4, 3, 5, 2}
};
int n = grid.size(), m = grid[0].size();
// ブルートフォース探索
int res = minPathSumDFS(grid, n - 1, m - 1);
cout << "左上角から右下角への最小パス和は " << res << " です" << endl;
// 動的プログラミング
res = minPathSumDP(grid);
cout << "左上角から右下角への最小パス和は " << res << " です" << endl;
return 0;
}

View File

@@ -0,0 +1,64 @@
/**
* File: unbounded_knapsack.cpp
* Created Time: 2023-07-11
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 完全ナップサック:動的プログラミング */
int unboundedKnapsackDP(vector<int> &wgt, vector<int> &val, int cap) {
int n = wgt.size();
// DPテーブルを初期化
vector<vector<int>> dp(n + 1, vector<int>(cap + 1, 0));
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int c = 1; c <= cap; c++) {
if (wgt[i - 1] > c) {
// ナップサックの容量を超える場合、アイテム i を選択しない
dp[i][c] = dp[i - 1][c];
} else {
// 選択しない場合とアイテム i を選択する場合のより大きい値
dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);
}
}
}
return dp[n][cap];
}
/* 完全ナップサック:空間最適化動的プログラミング */
int unboundedKnapsackDPComp(vector<int> &wgt, vector<int> &val, int cap) {
int n = wgt.size();
// DPテーブルを初期化
vector<int> dp(cap + 1, 0);
// 状態遷移
for (int i = 1; i <= n; i++) {
for (int c = 1; c <= cap; c++) {
if (wgt[i - 1] > c) {
// ナップサックの容量を超える場合、アイテム i を選択しない
dp[c] = dp[c];
} else {
// 選択しない場合とアイテム i を選択する場合のより大きい値
dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);
}
}
}
return dp[cap];
}
/* ドライバーコード */
int main() {
vector<int> wgt = {1, 2, 3};
vector<int> val = {5, 11, 15};
int cap = 4;
// 動的プログラミング
int res = unboundedKnapsackDP(wgt, val, cap);
cout << "ナップサック容量内での最大値は " << res << " です" << endl;
// 空間最適化動的プログラミング
res = unboundedKnapsackDPComp(wgt, val, cap);
cout << "ナップサック容量内での最大値は " << res << " です" << endl;
return 0;
}

View File

@@ -0,0 +1,90 @@
/**
* File: graph_adjacency_list.cpp
* Created Time: 2023-02-09
* Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 隣接リストに基づく無向グラフクラス */
class GraphAdjList {
public:
// 隣接リスト、キー:頂点、値:その頂点のすべての隣接頂点
unordered_map<Vertex *, vector<Vertex *>> adjList;
/* ベクターから指定されたノードを削除 */
void remove(vector<Vertex *> &vec, Vertex *vet) {
for (int i = 0; i < vec.size(); i++) {
if (vec[i] == vet) {
vec.erase(vec.begin() + i);
break;
}
}
}
/* コンストラクタ */
GraphAdjList(const vector<vector<Vertex *>> &edges) {
// すべての頂点と辺を追加
for (const vector<Vertex *> &edge : edges) {
addVertex(edge[0]);
addVertex(edge[1]);
addEdge(edge[0], edge[1]);
}
}
/* 頂点数を取得 */
int size() {
return adjList.size();
}
/* 辺を追加 */
void addEdge(Vertex *vet1, Vertex *vet2) {
if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)
throw invalid_argument("Vertex does not exist");
// 辺 vet1 - vet2 を追加
adjList[vet1].push_back(vet2);
adjList[vet2].push_back(vet1);
}
/* 辺を削除 */
void removeEdge(Vertex *vet1, Vertex *vet2) {
if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)
throw invalid_argument("Vertex does not exist");
// 辺 vet1 - vet2 を削除
remove(adjList[vet1], vet2);
remove(adjList[vet2], vet1);
}
/* 頂点を追加 */
void addVertex(Vertex *vet) {
if (adjList.count(vet))
return;
// 隣接リストに新しい連結リストを追加
adjList[vet] = vector<Vertex *>();
}
/* 頂点を削除 */
void removeVertex(Vertex *vet) {
if (!adjList.count(vet))
throw invalid_argument("Vertex does not exist");
// 隣接リストから頂点vetに対応する連結リストを削除
adjList.erase(vet);
// 他の頂点の連結リストを走査し、vetを含むすべての辺を削除
for (auto &adj : adjList) {
remove(adj.second, vet);
}
}
/* 隣接リストを印刷 */
void print() {
cout << "隣接リスト =" << endl;
for (auto &adj : adjList) {
const auto &key = adj.first;
const auto &vec = adj.second;
cout << key->val << ": ";
printVector(vetsToVals(vec));
}
}
};
// テストケースはgraph_adjacency_list_test.cppを参照

View File

@@ -0,0 +1,127 @@
/**
* File: graph_adjacency_matrix.cpp
* Created Time: 2023-02-09
* Author: what-is-me (whatisme@outlook.jp)
*/
#include "../utils/common.hpp"
/* 隣接行列に基づく無向グラフクラス */
class GraphAdjMat {
vector<int> vertices; // 頂点リスト、要素は「頂点値」を表し、インデックスは「頂点インデックス」を表す
vector<vector<int>> adjMat; // 隣接行列、行と列のインデックスは「頂点インデックス」に対応
public:
/* コンストラクタ */
GraphAdjMat(const vector<int> &vertices, const vector<vector<int>> &edges) {
// 頂点を追加
for (int val : vertices) {
addVertex(val);
}
// 辺を追加
// 辺の要素は頂点インデックスを表す
for (const vector<int> &edge : edges) {
addEdge(edge[0], edge[1]);
}
}
/* 頂点数を取得 */
int size() const {
return vertices.size();
}
/* 頂点を追加 */
void addVertex(int val) {
int n = size();
// 頂点リストに新しい頂点値を追加
vertices.push_back(val);
// 隣接行列に行を追加
adjMat.emplace_back(vector<int>(n, 0));
// 隣接行列に列を追加
for (vector<int> &row : adjMat) {
row.push_back(0);
}
}
/* 頂点を削除 */
void removeVertex(int index) {
if (index >= size()) {
throw out_of_range("Vertex does not exist");
}
// 頂点リストから`index`の頂点を削除
vertices.erase(vertices.begin() + index);
// 隣接行列から`index`の行を削除
adjMat.erase(adjMat.begin() + index);
// 隣接行列から`index`の列を削除
for (vector<int> &row : adjMat) {
row.erase(row.begin() + index);
}
}
/* 辺を追加 */
// パラメータi、jは頂点要素のインデックスに対応
void addEdge(int i, int j) {
// インデックス範囲外と等価性を処理
if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {
throw out_of_range("Vertex does not exist");
}
// 無向グラフでは、隣接行列は主対角線について対称、即ち(i, j) == (j, i)を満たす
adjMat[i][j] = 1;
adjMat[j][i] = 1;
}
/* 辺を削除 */
// パラメータi、jは頂点要素のインデックスに対応
void removeEdge(int i, int j) {
// インデックス範囲外と等価性を処理
if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {
throw out_of_range("Vertex does not exist");
}
adjMat[i][j] = 0;
adjMat[j][i] = 0;
}
/* 隣接行列を印刷 */
void print() {
cout << "頂点リスト = ";
printVector(vertices);
cout << "隣接行列 =" << endl;
printVectorMatrix(adjMat);
}
};
/* ドライバーコード */
int main() {
/* 無向グラフを初期化 */
// 辺の要素は頂点インデックスを表す
vector<int> vertices = {1, 3, 2, 5, 4};
vector<vector<int>> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}};
GraphAdjMat graph(vertices, edges);
cout << "\n初期化後、グラフは" << endl;
graph.print();
/* 辺を追加 */
// 頂点1、2のインデックスはそれぞれ0、2
graph.addEdge(0, 2);
cout << "\n辺 1-2 を追加後、グラフは" << endl;
graph.print();
/* 辺を削除 */
// 頂点1、3のインデックスはそれぞれ0、1
graph.removeEdge(0, 1);
cout << "\n辺 1-3 を削除後、グラフは" << endl;
graph.print();
/* 頂点を追加 */
graph.addVertex(6);
cout << "\n頂点 6 を追加後、グラフは" << endl;
graph.print();
/* 頂点を削除 */
// 頂点3のインデックスは1
graph.removeVertex(1);
cout << "\n頂点 3 を削除後、グラフは" << endl;
graph.print();
return 0;
}

View File

@@ -0,0 +1,59 @@
/**
* File: graph_bfs.cpp
* Created Time: 2023-03-02
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
#include "./graph_adjacency_list.cpp"
/* 幅優先走査 */
// 隣接リストを使用してグラフを表現し、指定された頂点のすべての隣接頂点を取得
vector<Vertex *> graphBFS(GraphAdjList &graph, Vertex *startVet) {
// 頂点走査順序
vector<Vertex *> res;
// ハッシュセット、訪問済み頂点を記録するために使用
unordered_set<Vertex *> visited = {startVet};
// BFSを実装するために使用されるキュー
queue<Vertex *> que;
que.push(startVet);
// 頂点vetから開始し、すべての頂点が訪問されるまでループ
while (!que.empty()) {
Vertex *vet = que.front();
que.pop(); // キューの先頭の頂点をデキュー
res.push_back(vet); // 訪問済み頂点を記録
// その頂点のすべての隣接頂点を走査
for (auto adjVet : graph.adjList[vet]) {
if (visited.count(adjVet))
continue; // すでに訪問済みの頂点をスキップ
que.push(adjVet); // 未訪問の頂点のみをエンキュー
visited.emplace(adjVet); // 頂点を訪問済みとしてマーク
}
}
// 頂点走査順序を返す
return res;
}
/* ドライバーコード */
int main() {
/* 無向グラフを初期化 */
vector<Vertex *> v = valsToVets({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
vector<vector<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(edges);
cout << "\n初期化後、グラフは\n";
graph.print();
/* 幅優先走査 */
vector<Vertex *> res = graphBFS(graph, v[0]);
cout << "\n幅優先走査BFSの頂点順序は" << endl;
printVector(vetsToVals(res));
// メモリを解放
for (Vertex *vet : v) {
delete vet;
}
return 0;
}

View File

@@ -0,0 +1,55 @@
/**
* File: graph_dfs.cpp
* Created Time: 2023-03-02
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
#include "./graph_adjacency_list.cpp"
/* 深さ優先走査ヘルパー関数 */
void dfs(GraphAdjList &graph, unordered_set<Vertex *> &visited, vector<Vertex *> &res, Vertex *vet) {
res.push_back(vet); // 訪問済み頂点を記録
visited.emplace(vet); // 頂点を訪問済みとしてマーク
// その頂点のすべての隣接頂点を走査
for (Vertex *adjVet : graph.adjList[vet]) {
if (visited.count(adjVet))
continue; // すでに訪問済みの頂点をスキップ
// 隣接頂点を再帰的に訪問
dfs(graph, visited, res, adjVet);
}
}
/* 深さ優先走査 */
// 隣接リストを使用してグラフを表現し、指定された頂点のすべての隣接頂点を取得
vector<Vertex *> graphDFS(GraphAdjList &graph, Vertex *startVet) {
// 頂点走査順序
vector<Vertex *> res;
// ハッシュセット、訪問済み頂点を記録するために使用
unordered_set<Vertex *> visited;
dfs(graph, visited, res, startVet);
return res;
}
/* ドライバーコード */
int main() {
/* 無向グラフを初期化 */
vector<Vertex *> v = valsToVets(vector<int>{0, 1, 2, 3, 4, 5, 6});
vector<vector<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(edges);
cout << "\n初期化後、グラフは" << endl;
graph.print();
/* 深さ優先走査 */
vector<Vertex *> res = graphDFS(graph, v[0]);
cout << "\n深さ優先走査DFSの頂点順序は" << endl;
printVector(vetsToVals(res));
// メモリを解放
for (Vertex *vet : v) {
delete vet;
}
return 0;
}

View File

@@ -0,0 +1,60 @@
/**
* File: coin_change_greedy.cpp
* Created Time: 2023-07-20
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 硬貨両替:貪欲法 */
int coinChangeGreedy(vector<int> &coins, int amt) {
// 硬貨リストが順序付けされていると仮定
int i = coins.size() - 1;
int count = 0;
// 残り金額がなくなるまで貪欲選択をループ
while (amt > 0) {
// 残り金額に近く、それ以下の最小硬貨を見つける
while (i > 0 && coins[i] > amt) {
i--;
}
// coins[i] を選択
amt -= coins[i];
count++;
}
// 実行可能な解が見つからない場合、-1 を返す
return amt == 0 ? count : -1;
}
/* ドライバーコード */
int main() {
// 貪欲法:大域最適解の発見を保証できる
vector<int> coins = {1, 5, 10, 20, 50, 100};
int amt = 186;
int res = coinChangeGreedy(coins, amt);
cout << "\ncoins = ";
printVector(coins);
cout << "amt = " << amt << endl;
cout << amt << " を作るのに必要な最小硬貨数は " << res << " です" << endl;
// 貪欲法:大域最適解の発見を保証できない
coins = {1, 20, 50};
amt = 60;
res = coinChangeGreedy(coins, amt);
cout << "\ncoins = ";
printVector(coins);
cout << "amt = " << amt << endl;
cout << amt << " を作るのに必要な最小硬貨数は " << res << " です" << endl;
cout << "実際には、最小必要数は 3 です。つまり、20 + 20 + 20" << endl;
// 貪欲法:大域最適解の発見を保証できない
coins = {1, 49, 50};
amt = 98;
res = coinChangeGreedy(coins, amt);
cout << "\ncoins = ";
printVector(coins);
cout << "amt = " << amt << endl;
cout << amt << " を作るのに必要な最小硬貨数は " << res << " です" << endl;
cout << "実際には、最小必要数は 2 です。つまり、49 + 49" << endl;
return 0;
}

View File

@@ -0,0 +1,56 @@
/**
* File: fractional_knapsack.cpp
* Created Time: 2023-07-20
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* アイテム */
class Item {
public:
int w; // アイテムの重量
int v; // アイテムの価値
Item(int w, int v) : w(w), v(v) {
}
};
/* 分数ナップサック:貪欲法 */
double fractionalKnapsack(vector<int> &wgt, vector<int> &val, int cap) {
// アイテムリストを作成、2つの属性を含む重量、価値
vector<Item> items;
for (int i = 0; i < wgt.size(); i++) {
items.push_back(Item(wgt[i], val[i]));
}
// 単位価値 item.v / item.w で高い順にソート
sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; });
// 貪欲選択をループ
double res = 0;
for (auto &item : items) {
if (item.w <= cap) {
// 残り容量が十分な場合、アイテム全体をナップサックに入れる
res += item.v;
cap -= item.w;
} else {
// 残り容量が不十分な場合、アイテムの一部をナップサックに入れる
res += (double)item.v / item.w * cap;
// 残り容量がなくなったため、ループを中断
break;
}
}
return res;
}
/* ドライバーコード */
int main() {
vector<int> wgt = {10, 20, 30, 40, 50};
vector<int> val = {50, 120, 150, 210, 240};
int cap = 50;
// 貪欲アルゴリズム
double res = fractionalKnapsack(wgt, val, cap);
cout << "ナップサック容量内での最大値は " << res << " です" << endl;
return 0;
}

View File

@@ -0,0 +1,39 @@
/**
* File: max_capacity.cpp
* Created Time: 2023-07-21
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 最大容量:貪欲法 */
int maxCapacity(vector<int> &ht) {
// i、j を初期化し、配列の両端で分割させる
int i = 0, j = ht.size() - 1;
// 初期最大容量は 0
int res = 0;
// 2つの板が出会うまで貪欲選択をループ
while (i < j) {
// 最大容量を更新
int cap = min(ht[i], ht[j]) * (j - i);
res = max(res, cap);
// より短い板を内側に移動
if (ht[i] < ht[j]) {
i++;
} else {
j--;
}
}
return res;
}
/* ドライバーコード */
int main() {
vector<int> ht = {3, 8, 5, 2, 7, 7, 3, 4};
// 貪欲アルゴリズム
int res = maxCapacity(ht);
cout << "最大容量は " << res << " です" << endl;
return 0;
}

View File

@@ -0,0 +1,39 @@
/**
* File: max_product_cutting.cpp
* Created Time: 2023-07-21
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 最大積切断:貪欲法 */
int maxProductCutting(int n) {
// n <= 3 の場合、1 を切り出す必要がある
if (n <= 3) {
return 1 * (n - 1);
}
// 貪欲に 3 を切り出す。a は 3 の個数、b は余り
int a = n / 3;
int b = n % 3;
if (b == 1) {
// 余りが 1 の場合、1 * 3 のペアを 2 * 2 に変換
return (int)pow(3, a - 1) * 2 * 2;
}
if (b == 2) {
// 余りが 2 の場合、何もしない
return (int)pow(3, a) * 2;
}
// 余りが 0 の場合、何もしない
return (int)pow(3, a);
}
/* ドライバーコード */
int main() {
int n = 58;
// 貪欲アルゴリズム
int res = maxProductCutting(n);
cout << "分割の最大積は " << res << " です" << endl;
return 0;
}

View File

@@ -0,0 +1,110 @@
/**
* File: array_hash_map.cpp
* Created Time: 2022-12-14
* Author: msk397 (machangxinq@gmail.com)
*/
#include "../utils/common.hpp"
/* キー値ペア */
struct Pair {
public:
int key;
string val;
Pair(int key, string val) {
this->key = key;
this->val = val;
}
};
/* 配列実装に基づくハッシュテーブル */
class ArrayHashMap {
private:
vector<Pair *> buckets;
public:
ArrayHashMap() {
// 配列を初期化、100個のバケットを含む
buckets = vector<Pair *>(100);
}
~ArrayHashMap() {
// メモリを解放
for (const auto &bucket : buckets) {
delete bucket;
}
buckets.clear();
}
/* ハッシュ関数 */
int hashFunc(int key) {
int index = key % 100;
return index;
}
/* クエリ操作 */
string get(int key) {
int index = hashFunc(key);
Pair *pair = buckets[index];
if (pair == nullptr)
return "";
return pair->val;
}
/* 追加操作 */
void put(int key, string val) {
Pair *pair = new Pair(key, val);
int index = hashFunc(key);
buckets[index] = pair;
}
/* 削除操作 */
void remove(int key) {
int index = hashFunc(key);
// メモリを解放してnullptrに設定
delete buckets[index];
buckets[index] = nullptr;
}
/* すべてのキー値ペアを取得 */
vector<Pair *> pairSet() {
vector<Pair *> pairSet;
for (Pair *pair : buckets) {
if (pair != nullptr) {
pairSet.push_back(pair);
}
}
return pairSet;
}
/* すべてのキーを取得 */
vector<int> keySet() {
vector<int> keySet;
for (Pair *pair : buckets) {
if (pair != nullptr) {
keySet.push_back(pair->key);
}
}
return keySet;
}
/* すべての値を取得 */
vector<string> valueSet() {
vector<string> valueSet;
for (Pair *pair : buckets) {
if (pair != nullptr) {
valueSet.push_back(pair->val);
}
}
return valueSet;
}
/* ハッシュテーブルを印刷 */
void print() {
for (Pair *kv : pairSet()) {
cout << kv->key << " -> " << kv->val << endl;
}
}
};
// テストケースはarray_hash_map_test.cppを参照

View File

@@ -0,0 +1,52 @@
/**
* File: array_hash_map_test.cpp
* Created Time: 2022-12-14
* Author: msk397 (machangxinq@gmail.com)
*/
#include "./array_hash_map.cpp"
/* ドライバーコード */
int main() {
/* ハッシュテーブルを初期化 */
ArrayHashMap map = ArrayHashMap();
/* 追加操作 */
// キー値ペア(key, value)をハッシュテーブルに追加
map.put(12836, "Ha");
map.put(15937, "Luo");
map.put(16750, "Suan");
map.put(13276, "Fa");
map.put(10583, "Ya");
cout << "\nAfter adding, the hash table is\nKey -> Value" << endl;
map.print();
/* クエリ操作 */
// ハッシュテーブルにキーを入力、値を取得
string name = map.get(15937);
cout << "\nEnter student ID 15937, found name " << name << endl;
/* 削除操作 */
// ハッシュテーブルからキー値ペア(key, value)を削除
map.remove(10583);
cout << "\nAfter removing 10583, the hash table is\nKey -> Value" << endl;
map.print();
/* ハッシュテーブルを走査 */
cout << "\nTraverse key-value pairs Key->Value" << endl;
for (auto kv : map.pairSet()) {
cout << kv->key << " -> " << kv->val << endl;
}
cout << "\nIndividually traverse keys Key" << endl;
for (auto key : map.keySet()) {
cout << key << endl;
}
cout << "\nIndividually traverse values Value" << endl;
for (auto val : map.valueSet()) {
cout << val << endl;
}
return 0;
}

View File

@@ -0,0 +1,29 @@
/**
* File: built_in_hash.cpp
* Created Time: 2023-06-21
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* ドライバーコード */
int main() {
int num = 3;
size_t hashNum = hash<int>()(num);
cout << "The hash value of integer " << num << " is " << hashNum << "\n";
bool bol = true;
size_t hashBol = hash<bool>()(bol);
cout << "The hash value of boolean " << bol << " is " << hashBol << "\n";
double dec = 3.14159;
size_t hashDec = hash<double>()(dec);
cout << "The hash value of decimal " << dec << " is " << hashDec << "\n";
string str = "Hello algorithm";
size_t hashStr = hash<string>()(str);
cout << "The hash value of string " << str << " is " << hashStr << "\n";
// C++では、組み込みのstd:hash()は基本データ型のハッシュ値のみを提供
// 配列やオブジェクトのハッシュ値計算は手動で実装する必要がある
}

View File

@@ -0,0 +1,46 @@
/**
* File: hash_map.cpp
* Created Time: 2022-12-14
* Author: msk397 (machangxinq@gmail.com)
*/
#include "../utils/common.hpp"
/* ドライバーコード */
int main() {
/* ハッシュテーブルを初期化 */
unordered_map<int, string> map;
/* 追加操作 */
// キー値ペア(key, value)をハッシュテーブルに追加
map[12836] = "Ha";
map[15937] = "Luo";
map[16750] = "Suan";
map[13276] = "Fa";
map[10583] = "Ya";
cout << "\nAfter adding, the hash table is\nKey -> Value" << endl;
printHashMap(map);
/* クエリ操作 */
// ハッシュテーブルにキーを入力、値を取得
string name = map[15937];
cout << "\nEnter student ID 15937, found name " << name << endl;
/* 削除操作 */
// ハッシュテーブルからキー値ペア(key, value)を削除
map.erase(10583);
cout << "\nAfter removing 10583, the hash table is\nKey -> Value" << endl;
printHashMap(map);
/* ハッシュテーブルを走査 */
cout << "\nTraverse key-value pairs Key->Value" << endl;
for (auto kv : map) {
cout << kv.first << " -> " << kv.second << endl;
}
cout << "\nIterate through Key->Value using an iterator" << endl;
for (auto iter = map.begin(); iter != map.end(); iter++) {
cout << iter->first << "->" << iter->second << endl;
}
return 0;
}

View File

@@ -0,0 +1,150 @@
/**
* File: hash_map_chaining.cpp
* Created Time: 2023-06-13
* Author: krahets (krahets@163.com)
*/
#include "./array_hash_map.cpp"
/* チェイン法ハッシュテーブル */
class HashMapChaining {
private:
int size; // キー値ペアの数
int capacity; // ハッシュテーブルの容量
double loadThres; // 拡張をトリガーする負荷率の閾値
int extendRatio; // 拡張倍率
vector<vector<Pair *>> buckets; // バケット配列
public:
/* コンストラクタ */
HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) {
buckets.resize(capacity);
}
/* デストラクタ */
~HashMapChaining() {
for (auto &bucket : buckets) {
for (Pair *pair : bucket) {
// メモリを解放
delete pair;
}
}
}
/* ハッシュ関数 */
int hashFunc(int key) {
return key % capacity;
}
/* 負荷率 */
double loadFactor() {
return (double)size / (double)capacity;
}
/* クエリ操作 */
string get(int key) {
int index = hashFunc(key);
// バケットを走査、キーが見つかった場合、対応するvalを返却
for (Pair *pair : buckets[index]) {
if (pair->key == key) {
return pair->val;
}
}
// キーが見つからない場合、空文字列を返却
return "";
}
/* 追加操作 */
void put(int key, string val) {
// 負荷率が閾値を超えた場合、拡張を実行
if (loadFactor() > loadThres) {
extend();
}
int index = hashFunc(key);
// バケットを走査、指定キーに遭遇した場合、対応するvalを更新して返却
for (Pair *pair : buckets[index]) {
if (pair->key == key) {
pair->val = val;
return;
}
}
// キーが見つからない場合、キー値ペアを末尾に追加
buckets[index].push_back(new Pair(key, val));
size++;
}
/* 削除操作 */
void remove(int key) {
int index = hashFunc(key);
auto &bucket = buckets[index];
// バケットを走査、キー値ペアを削除
for (int i = 0; i < bucket.size(); i++) {
if (bucket[i]->key == key) {
Pair *tmp = bucket[i];
bucket.erase(bucket.begin() + i); // キー値ペアを削除
delete tmp; // メモリを解放
size--;
return;
}
}
}
/* ハッシュテーブルを拡張 */
void extend() {
// 元のハッシュテーブルを一時保存
vector<vector<Pair *>> bucketsTmp = buckets;
// 拡張された新しいハッシュテーブルを初期化
capacity *= extendRatio;
buckets.clear();
buckets.resize(capacity);
size = 0;
// 元のハッシュテーブルから新しいハッシュテーブルにキー値ペアを移動
for (auto &bucket : bucketsTmp) {
for (Pair *pair : bucket) {
put(pair->key, pair->val);
// メモリを解放
delete pair;
}
}
}
/* ハッシュテーブルを印刷 */
void print() {
for (auto &bucket : buckets) {
cout << "[";
for (Pair *pair : bucket) {
cout << pair->key << " -> " << pair->val << ", ";
}
cout << "]\n";
}
}
};
/* ドライバーコード */
int main() {
/* ハッシュテーブルを初期化 */
HashMapChaining map = HashMapChaining();
/* 追加操作 */
// キー値ペア(key, value)をハッシュテーブルに追加
map.put(12836, "Ha");
map.put(15937, "Luo");
map.put(16750, "Suan");
map.put(13276, "Fa");
map.put(10583, "Ya");
cout << "\nAfter adding, the hash table is\nKey -> Value" << endl;
map.print();
/* クエリ操作 */
// ハッシュテーブルにキーを入力、値を取得
string name = map.get(13276);
cout << "\nEnter student ID 13276, found name " << name << endl;
/* 削除操作 */
// ハッシュテーブルからキー値ペア(key, value)を削除
map.remove(12836);
cout << "\nAfter removing 12836, the hash table is\nKey -> Value" << endl;
map.print();
return 0;
}

View File

@@ -0,0 +1,171 @@
/**
* File: hash_map_open_addressing.cpp
* Created Time: 2023-06-13
* Author: krahets (krahets@163.com)
*/
#include "./array_hash_map.cpp"
/* オープンアドレス法ハッシュテーブル */
class HashMapOpenAddressing {
private:
int size; // キー値ペアの数
int capacity = 4; // ハッシュテーブルの容量
const double loadThres = 2.0 / 3.0; // 拡張をトリガーする負荷率の閾値
const int extendRatio = 2; // 拡張倍率
vector<Pair *> buckets; // バケット配列
Pair *TOMBSTONE = new Pair(-1, "-1"); // 削除マーク
public:
/* コンストラクタ */
HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) {
}
/* デストラクタ */
~HashMapOpenAddressing() {
for (Pair *pair : buckets) {
if (pair != nullptr && pair != TOMBSTONE) {
delete pair;
}
}
delete TOMBSTONE;
}
/* ハッシュ関数 */
int hashFunc(int key) {
return key % capacity;
}
/* 負荷率 */
double loadFactor() {
return (double)size / capacity;
}
/* keyに対応するバケットインデックスを検索 */
int findBucket(int key) {
int index = hashFunc(key);
int firstTombstone = -1;
// 線形探査、空のバケットに遭遇したら中断
while (buckets[index] != nullptr) {
// keyに遭遇した場合、対応するバケットインデックスを返却
if (buckets[index]->key == key) {
// 以前に削除マークに遭遇していた場合、キー値ペアをそのインデックスに移動
if (firstTombstone != -1) {
buckets[firstTombstone] = buckets[index];
buckets[index] = TOMBSTONE;
return firstTombstone; // 移動されたバケットインデックスを返却
}
return index; // バケットインデックスを返却
}
// 最初に遭遇した削除マークを記録
if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {
firstTombstone = index;
}
// バケットインデックスを計算、末尾を超えた場合は先頭に戻る
index = (index + 1) % capacity;
}
// keyが存在しない場合、挿入ポイントのインデックスを返却
return firstTombstone == -1 ? index : firstTombstone;
}
/* クエリ操作 */
string get(int key) {
// keyに対応するバケットインデックスを検索
int index = findBucket(key);
// キー値ペアが見つかった場合、対応するvalを返却
if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {
return buckets[index]->val;
}
// キー値ペアが存在しない場合、空文字列を返却
return "";
}
/* 追加操作 */
void put(int key, string val) {
// 負荷率が閾値を超えた場合、拡張を実行
if (loadFactor() > loadThres) {
extend();
}
// keyに対応するバケットインデックスを検索
int index = findBucket(key);
// キー値ペアが見つかった場合、valを上書きして返却
if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {
buckets[index]->val = val;
return;
}
// キー値ペアが存在しない場合、キー値ペアを追加
buckets[index] = new Pair(key, val);
size++;
}
/* 削除操作 */
void remove(int key) {
// keyに対応するバケットインデックスを検索
int index = findBucket(key);
// キー値ペアが見つかった場合、削除マークで覆う
if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {
delete buckets[index];
buckets[index] = TOMBSTONE;
size--;
}
}
/* ハッシュテーブルを拡張 */
void extend() {
// 元のハッシュテーブルを一時保存
vector<Pair *> bucketsTmp = buckets;
// 拡張された新しいハッシュテーブルを初期化
capacity *= extendRatio;
buckets = vector<Pair *>(capacity, nullptr);
size = 0;
// 元のハッシュテーブルから新しいハッシュテーブルにキー値ペアを移動
for (Pair *pair : bucketsTmp) {
if (pair != nullptr && pair != TOMBSTONE) {
put(pair->key, pair->val);
delete pair;
}
}
}
/* ハッシュテーブルを印刷 */
void print() {
for (Pair *pair : buckets) {
if (pair == nullptr) {
cout << "nullptr" << endl;
} else if (pair == TOMBSTONE) {
cout << "TOMBSTONE" << endl;
} else {
cout << pair->key << " -> " << pair->val << endl;
}
}
}
};
/* ドライバーコード */
int main() {
// ハッシュテーブルを初期化
HashMapOpenAddressing hashmap;
// 追加操作
// キー値ペア(key, val)をハッシュテーブルに追加
hashmap.put(12836, "Ha");
hashmap.put(15937, "Luo");
hashmap.put(16750, "Suan");
hashmap.put(13276, "Fa");
hashmap.put(10583, "Ya");
cout << "\nAfter adding, the hash table is\nKey -> Value" << endl;
hashmap.print();
// クエリ操作
// ハッシュテーブルにキーを入力、値valを取得
string name = hashmap.get(13276);
cout << "\nEnter student ID 13276, found name " << name << endl;
// 削除操作
// ハッシュテーブルからキー値ペア(key, val)を削除
hashmap.remove(16750);
cout << "\nAfter removing 16750, the hash table is\nKey -> Value" << endl;
hashmap.print();
return 0;
}

View File

@@ -0,0 +1,66 @@
/**
* File: simple_hash.cpp
* Created Time: 2023-06-21
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 加算ハッシュ */
int addHash(string key) {
long long hash = 0;
const int MODULUS = 1000000007;
for (unsigned char c : key) {
hash = (hash + (int)c) % MODULUS;
}
return (int)hash;
}
/* 乗算ハッシュ */
int mulHash(string key) {
long long hash = 0;
const int MODULUS = 1000000007;
for (unsigned char c : key) {
hash = (31 * hash + (int)c) % MODULUS;
}
return (int)hash;
}
/* XORハッシュ */
int xorHash(string key) {
int hash = 0;
const int MODULUS = 1000000007;
for (unsigned char c : key) {
hash ^= (int)c;
}
return hash & MODULUS;
}
/* 回転ハッシュ */
int rotHash(string key) {
long long hash = 0;
const int MODULUS = 1000000007;
for (unsigned char c : key) {
hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS;
}
return (int)hash;
}
/* ドライバーコード */
int main() {
string key = "Hello algorithm";
int hash = addHash(key);
cout << "Additive hash value is " << hash << endl;
hash = mulHash(key);
cout << "Multiplicative hash value is " << hash << endl;
hash = xorHash(key);
cout << "XOR hash value is " << hash << endl;
hash = rotHash(key);
cout << "Rotational hash value is " << hash << endl;
return 0;
}

View File

@@ -0,0 +1,66 @@
/**
* File: heap.cpp
* Created Time: 2023-01-19
* Author: LoneRanger(836253168@qq.com)
*/
#include "../utils/common.hpp"
void testPush(priority_queue<int> &heap, int val) {
heap.push(val); // 要素をヒープにプッシュ
cout << "\n要素 " << val << " をヒープに追加後" << endl;
printHeap(heap);
}
void testPop(priority_queue<int> &heap) {
int val = heap.top();
heap.pop();
cout << "\nヒープから先頭要素 " << val << " を削除後" << endl;
printHeap(heap);
}
/* ドライバーコード */
int main() {
/* ヒープを初期化 */
// 最小ヒープを初期化
// priority_queue<int, vector<int>, greater<int>> minHeap;
// 最大ヒープを初期化
priority_queue<int, vector<int>, less<int>> maxHeap;
cout << "\n以下のテストケースは最大ヒープ用です" << endl;
/* ヒープに要素をプッシュ */
testPush(maxHeap, 1);
testPush(maxHeap, 3);
testPush(maxHeap, 2);
testPush(maxHeap, 5);
testPush(maxHeap, 4);
/* ヒープの先頭要素にアクセス */
int peek = maxHeap.top();
cout << "\nヒープの先頭要素は " << peek << endl;
/* ヒープ先頭の要素をポップ */
testPop(maxHeap);
testPop(maxHeap);
testPop(maxHeap);
testPop(maxHeap);
testPop(maxHeap);
/* ヒープのサイズを取得 */
int size = maxHeap.size();
cout << "\nヒープ内の要素数は " << size << endl;
/* ヒープが空かどうか判定 */
bool isEmpty = maxHeap.empty();
cout << "\nヒープが空かどうか " << isEmpty << endl;
/* リストを入力してヒープを構築 */
// 時間計算量はO(n)、O(nlogn)ではない
vector<int> input{1, 3, 2, 5, 4};
priority_queue<int, vector<int>, greater<int>> minHeap(input.begin(), input.end());
cout << "リストを入力して最小ヒープを構築後" << endl;
printHeap(minHeap);
return 0;
}

View File

@@ -0,0 +1,155 @@
/**
* File: my_heap.cpp
* Created Time: 2023-02-04
* Author: LoneRanger (836253168@qq.com), what-is-me (whatisme@outlook.jp)
*/
#include "../utils/common.hpp"
/* 最大ヒープ */
class MaxHeap {
private:
// 動的配列を使用してサイズ変更の必要性を回避
vector<int> maxHeap;
/* 左の子ノードのインデックスを取得 */
int left(int i) {
return 2 * i + 1;
}
/* 右の子ノードのインデックスを取得 */
int right(int i) {
return 2 * i + 2;
}
/* 親ノードのインデックスを取得 */
int parent(int i) {
return (i - 1) / 2; // 整数除算で切り下げ
}
/* ードiから上向きにヒープ化を開始 */
void siftUp(int i) {
while (true) {
// ードiの親ードを取得
int p = parent(i);
// 「ルートノードを超える」または「ノードが修復不要」の場合、ヒープ化を終了
if (p < 0 || maxHeap[i] <= maxHeap[p])
break;
// 2つのードを交換
swap(maxHeap[i], maxHeap[p]);
// 上向きにループしてヒープ化
i = p;
}
}
/* ードiから下向きにヒープ化を開始 */
void siftDown(int i) {
while (true) {
// i、l、rの中で最大のードを決定し、maとして記録
int l = left(i), r = right(i), ma = i;
if (l < size() && maxHeap[l] > maxHeap[ma])
ma = l;
if (r < size() && maxHeap[r] > maxHeap[ma])
ma = r;
// ードiが最大、またはインデックスl、rが範囲外の場合、これ以上のヒープ化は不要、ブレーク
if (ma == i)
break;
swap(maxHeap[i], maxHeap[ma]);
// 下向きにループしてヒープ化
i = ma;
}
}
public:
/* コンストラクタ、入力リストに基づいてヒープを構築 */
MaxHeap(vector<int> nums) {
// すべてのリスト要素をヒープに追加
maxHeap = nums;
// 葉以外のすべてのノードをヒープ化
for (int i = parent(size() - 1); i >= 0; i--) {
siftDown(i);
}
}
/* ヒープのサイズを取得 */
int size() {
return maxHeap.size();
}
/* ヒープが空かどうか判定 */
bool isEmpty() {
return size() == 0;
}
/* ヒープの先頭要素にアクセス */
int peek() {
return maxHeap[0];
}
/* ヒープに要素をプッシュ */
void push(int val) {
// ノードを追加
maxHeap.push_back(val);
// 下から上へヒープ化
siftUp(size() - 1);
}
/* 要素がヒープから退出 */
void pop() {
// 空の処理
if (isEmpty()) {
throw out_of_range("Heap is empty");
}
// ルートノードを最も右の葉ノードと交換(最初の要素と最後の要素を交換)
swap(maxHeap[0], maxHeap[size() - 1]);
// ノードを削除
maxHeap.pop_back();
// 上から下へヒープ化
siftDown(0);
}
/* ヒープを印刷(二分木)*/
void print() {
cout << "ヒープの配列表現:";
printVector(maxHeap);
cout << "ヒープの木表現:" << endl;
TreeNode *root = vectorToTree(maxHeap);
printTree(root);
freeMemoryTree(root);
}
};
/* ドライバーコード */
int main() {
/* 最大ヒープを初期化 */
vector<int> vec{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2};
MaxHeap maxHeap(vec);
cout << "\nリストを入力してヒープを構築" << endl;
maxHeap.print();
/* ヒープの先頭要素にアクセス */
int peek = maxHeap.peek();
cout << "\nヒープの先頭要素は " << peek << endl;
/* ヒープに要素をプッシュ */
int val = 7;
maxHeap.push(val);
cout << "\n要素 " << val << " をヒープに追加後" << endl;
maxHeap.print();
/* ヒープ先頭の要素をポップ */
peek = maxHeap.peek();
maxHeap.pop();
cout << "\nヒープから先頭要素 " << peek << " を削除後" << endl;
maxHeap.print();
/* ヒープのサイズを取得 */
int size = maxHeap.size();
cout << "\nヒープ内の要素数は " << size << endl;
/* ヒープが空かどうか判定 */
bool isEmpty = maxHeap.isEmpty();
cout << "\nヒープが空かどうか " << isEmpty << endl;
return 0;
}

View File

@@ -0,0 +1,38 @@
/**
* File: top_k.cpp
* Created Time: 2023-06-12
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* ヒープを使用して配列内の最大k個の要素を見つける */
priority_queue<int, vector<int>, greater<int>> topKHeap(vector<int> &nums, int k) {
// 最小ヒープを初期化
priority_queue<int, vector<int>, greater<int>> heap;
// 配列の最初のk個の要素をヒープに入力
for (int i = 0; i < k; i++) {
heap.push(nums[i]);
}
// k+1番目の要素から、ヒープの長さをkに保つ
for (int i = k; i < nums.size(); i++) {
// 現在の要素がヒープの先頭要素より大きい場合、ヒープの先頭要素を削除し、現在の要素をヒープに入力
if (nums[i] > heap.top()) {
heap.pop();
heap.push(nums[i]);
}
}
return heap;
}
// ドライバーコード
int main() {
vector<int> nums = {1, 7, 6, 3, 2};
int k = 3;
priority_queue<int, vector<int>, greater<int>> res = topKHeap(nums, k);
cout << "最大 " << k << " 個の要素は:";
printHeap(res);
return 0;
}

View File

@@ -0,0 +1,59 @@
/**
* File: binary_search.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 二分探索(両端閉区間) */
int binarySearch(vector<int> &nums, int target) {
// 両端閉区間[0, n-1]を初期化、すなわちi、jはそれぞれ配列の最初の要素と最後の要素を指す
int i = 0, j = nums.size() - 1;
// 探索区間が空になるまでループi > jの時空になる
while (i <= j) {
int m = i + (j - i) / 2; // 中点インデックスmを計算
if (nums[m] < target) // この状況はtargetが区間[m+1, j]にあることを示す
i = m + 1;
else if (nums[m] > target) // この状況はtargetが区間[i, m-1]にあることを示す
j = m - 1;
else // ターゲット要素が見つかったため、そのインデックスを返す
return m;
}
// ターゲット要素が見つからなかったため、-1を返す
return -1;
}
/* 二分探索(左閉右開区間) */
int binarySearchLCRO(vector<int> &nums, int target) {
// 左閉右開区間[0, n)を初期化、すなわちi、jはそれぞれ配列の最初の要素と最後の要素+1を指す
int i = 0, j = nums.size();
// 探索区間が空になるまでループi = jの時空になる
while (i < j) {
int m = i + (j - i) / 2; // 中点インデックスmを計算
if (nums[m] < target) // この状況はtargetが区間[m+1, j)にあることを示す
i = m + 1;
else if (nums[m] > target) // この状況はtargetが区間[i, m)にあることを示す
j = m;
else // ターゲット要素が見つかったため、そのインデックスを返す
return m;
}
// ターゲット要素が見つからなかったため、-1を返す
return -1;
}
/* ドライバコード */
int main() {
int target = 6;
vector<int> nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35};
/* 二分探索(両端閉区間) */
int index = binarySearch(nums, target);
cout << "ターゲット要素6のインデックス =" << index << endl;
/* 二分探索(左閉右開区間) */
index = binarySearchLCRO(nums, target);
cout << "ターゲット要素6のインデックス =" << index << endl;
return 0;
}

View File

@@ -0,0 +1,66 @@
/**
* File: binary_search_edge.cpp
* Created Time: 2023-08-04
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 挿入ポイントの二分探索(重複要素あり) */
int binarySearchInsertion(const vector<int> &nums, int target) {
int i = 0, j = nums.size() - 1; // 両端閉区間[0, n-1]を初期化
while (i <= j) {
int m = i + (j - i) / 2; // 中点インデックスmを計算
if (nums[m] < target) {
i = m + 1; // ターゲットは区間[m+1, j]にある
} else {
j = m - 1; // ターゲット未満の最初の要素は区間[i, m-1]にある
}
}
// 挿入ポイントiを返す
return i;
}
/* 最左のターゲットの二分探索 */
int binarySearchLeftEdge(vector<int> &nums, int target) {
// targetの挿入ポイントを見つけることと等価
int i = binarySearchInsertion(nums, target);
// targetが見つからなかったため、-1を返す
if (i == nums.size() || nums[i] != target) {
return -1;
}
// targetが見つかったため、インデックスiを返す
return i;
}
/* 最右のターゲットの二分探索 */
int binarySearchRightEdge(vector<int> &nums, int target) {
// 最左のtarget + 1を見つけることに変換
int i = binarySearchInsertion(nums, target + 1);
// jは最右のターゲットを指し、iはtargetより大きい最初の要素を指す
int j = i - 1;
// targetが見つからなかったため、-1を返す
if (j == -1 || nums[j] != target) {
return -1;
}
// targetが見つかったため、インデックスjを返す
return j;
}
/* ドライバコード */
int main() {
// 重複要素を含む配列
vector<int> nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15};
cout << "\n配列 nums = ";
printVector(nums);
// 左右の境界の二分探索
for (int target : {6, 7}) {
int index = binarySearchLeftEdge(nums, target);
cout << "要素 " << target << " の最左インデックスは " << index << " です" << endl;
index = binarySearchRightEdge(nums, target);
cout << "要素 " << target << " の最右インデックスは " << index << " です" << endl;
}
return 0;
}

View File

@@ -0,0 +1,66 @@
/**
* File: binary_search_insertion.cpp
* Created Time: 2023-08-04
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 挿入ポイントの二分探索(重複要素なし) */
int binarySearchInsertionSimple(vector<int> &nums, int target) {
int i = 0, j = nums.size() - 1; // 両端閉区間[0, n-1]を初期化
while (i <= j) {
int m = i + (j - i) / 2; // 中点インデックスmを計算
if (nums[m] < target) {
i = m + 1; // ターゲットは区間[m+1, j]にある
} else if (nums[m] > target) {
j = m - 1; // ターゲットは区間[i, m-1]にある
} else {
return m; // ターゲットが見つかったため、挿入ポイントmを返す
}
}
// ターゲットが見つからなかったため、挿入ポイントiを返す
return i;
}
/* 挿入ポイントの二分探索(重複要素あり) */
int binarySearchInsertion(vector<int> &nums, int target) {
int i = 0, j = nums.size() - 1; // 両端閉区間[0, n-1]を初期化
while (i <= j) {
int m = i + (j - i) / 2; // 中点インデックスmを計算
if (nums[m] < target) {
i = m + 1; // ターゲットは区間[m+1, j]にある
} else if (nums[m] > target) {
j = m - 1; // ターゲットは区間[i, m-1]にある
} else {
j = m - 1; // ターゲット未満の最初の要素は区間[i, m-1]にある
}
}
// 挿入ポイントiを返す
return i;
}
/* ドライバコード */
int main() {
// 重複要素のない配列
vector<int> nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35};
cout << "\n配列 nums = ";
printVector(nums);
// 挿入ポイントの二分探索
for (int target : {6, 9}) {
int index = binarySearchInsertionSimple(nums, target);
cout << "要素 " << target << " の挿入ポイントインデックスは " << index << " です" << endl;
}
// 重複要素を含む配列
nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15};
cout << "\n配列 nums = ";
printVector(nums);
// 挿入ポイントの二分探索
for (int target : {2, 6, 20}) {
int index = binarySearchInsertion(nums, target);
cout << "要素 " << target << " の挿入ポイントインデックスは " << index << " です" << endl;
}
return 0;
}

View File

@@ -0,0 +1,53 @@
/**
* File: hashing_search.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* ハッシュ探索(配列) */
int hashingSearchArray(unordered_map<int, int> map, int target) {
// ハッシュテーブルのキー:ターゲット要素、値:インデックス
// ハッシュテーブルにこのキーが含まれていない場合、-1を返す
if (map.find(target) == map.end())
return -1;
return map[target];
}
/* ハッシュ探索(連結リスト) */
ListNode *hashingSearchLinkedList(unordered_map<int, ListNode *> map, int target) {
// ハッシュテーブルのキー:ターゲットノード値、値:ノードオブジェクト
// キーがハッシュテーブルにない場合、nullptrを返す
if (map.find(target) == map.end())
return nullptr;
return map[target];
}
/* ドライバコード */
int main() {
int target = 3;
/* ハッシュ探索(配列) */
vector<int> nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8};
// ハッシュテーブルを初期化
unordered_map<int, int> map;
for (int i = 0; i < nums.size(); i++) {
map[nums[i]] = i; // キー:要素、値:インデックス
}
int index = hashingSearchArray(map, target);
cout << "ターゲット要素3のインデックスは " << index << " です" << endl;
/* ハッシュ探索(連結リスト) */
ListNode *head = vecToLinkedList(nums);
// ハッシュテーブルを初期化
unordered_map<int, ListNode *> map1;
while (head != nullptr) {
map1[head->val] = head; // キー:ノード値、値:ノード
head = head->next;
}
ListNode *node = hashingSearchLinkedList(map1, target);
cout << "ターゲットード値3に対応するードオブジェクトは " << node << " です" << endl;
return 0;
}

View File

@@ -0,0 +1,49 @@
/**
* File: linear_search.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 線形探索(配列) */
int linearSearchArray(vector<int> &nums, int target) {
// 配列を走査
for (int i = 0; i < nums.size(); i++) {
// ターゲット要素が見つかったため、そのインデックスを返す
if (nums[i] == target)
return i;
}
// ターゲット要素が見つからなかったため、-1を返す
return -1;
}
/* 線形探索(連結リスト) */
ListNode *linearSearchLinkedList(ListNode *head, int target) {
// リストを走査
while (head != nullptr) {
// ターゲットノードが見つかった場合、それを返す
if (head->val == target)
return head;
head = head->next;
}
// ターゲットードが見つからない場合、nullptrを返す
return nullptr;
}
/* ドライバコード */
int main() {
int target = 3;
/* 配列で線形探索を実行 */
vector<int> nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8};
int index = linearSearchArray(nums, target);
cout << "ターゲット要素3のインデックスは " << index << " です" << endl;
/* 連結リストで線形探索を実行 */
ListNode *head = vecToLinkedList(nums);
ListNode *node = linearSearchLinkedList(head, target);
cout << "ターゲットード値3に対応するードオブジェクトは " << node << " です" << endl;
return 0;
}

View File

@@ -0,0 +1,54 @@
/**
* File: two_sum.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 方法一:ブルートフォース列挙 */
vector<int> twoSumBruteForce(vector<int> &nums, int target) {
int size = nums.size();
// 二重ループ、時間計算量はO(n^2)
for (int i = 0; i < size - 1; i++) {
for (int j = i + 1; j < size; j++) {
if (nums[i] + nums[j] == target)
return {i, j};
}
}
return {};
}
/* 方法二:補助ハッシュテーブル */
vector<int> twoSumHashTable(vector<int> &nums, int target) {
int size = nums.size();
// 補助ハッシュテーブル、空間計算量はO(n)
unordered_map<int, int> dic;
// 単層ループ、時間計算量はO(n)
for (int i = 0; i < size; i++) {
if (dic.find(target - nums[i]) != dic.end()) {
return {dic[target - nums[i]], i};
}
dic.emplace(nums[i], i);
}
return {};
}
/* ドライバコード */
int main() {
// ======= テストケース =======
vector<int> nums = {2, 7, 11, 15};
int target = 13;
// ====== ドライバコード ======
// 方法一
vector<int> res = twoSumBruteForce(nums, target);
cout << "方法一 res = ";
printVector(res);
// 方法二
res = twoSumHashTable(nums, target);
cout << "方法二 res = ";
printVector(res);
return 0;
}

View File

@@ -0,0 +1,56 @@
/**
* File: bubble_sort.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* バブルソート */
void bubbleSort(vector<int> &nums) {
// 外側ループ:未ソート範囲は[0, i]
for (int i = nums.size() - 1; i > 0; i--) {
// 内側ループ:未ソート範囲[0, i]内の最大要素を範囲の右端に交換
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// nums[j]とnums[j + 1]を交換
// ここではstdのswapを使用
swap(nums[j], nums[j + 1]);
}
}
}
}
/* バブルソート(フラグ最適化版)*/
void bubbleSortWithFlag(vector<int> &nums) {
// 外側ループ:未ソート範囲は[0, i]
for (int i = nums.size() - 1; i > 0; i--) {
bool flag = false; // フラグを初期化
// 内側ループ:未ソート範囲[0, i]内の最大要素を範囲の右端に交換
for (int j = 0; j < i; j++) {
if (nums[j] > nums[j + 1]) {
// nums[j]とnums[j + 1]を交換
// ここではstdのswapを使用
swap(nums[j], nums[j + 1]);
flag = true; // 交換された要素を記録
}
}
if (!flag)
break; // この回の「バブリング」で要素が交換されなかった場合、終了
}
}
/* ドライバコード */
int main() {
vector<int> nums = {4, 1, 3, 1, 5, 2};
bubbleSort(nums);
cout << "バブルソート後、nums = ";
printVector(nums);
vector<int> nums1 = {4, 1, 3, 1, 5, 2};
bubbleSortWithFlag(nums1);
cout << "バブルソート後、nums1 = ";
printVector(nums1);
return 0;
}

View File

@@ -0,0 +1,44 @@
/**
* File: bucket_sort.cpp
* Created Time: 2023-03-30
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* バケットソート */
void bucketSort(vector<float> &nums) {
// k = n/2個のバケットを初期化、各バケットに2つの要素を割り当てることを期待
int k = nums.size() / 2;
vector<vector<float>> buckets(k);
// 1. 配列要素を各バケットに分配
for (float num : nums) {
// 入力データ範囲は[0, 1)、num * kを使用してインデックス範囲[0, k-1]にマップ
int i = num * k;
// bucket_idxバケットに数値を追加
buckets[i].push_back(num);
}
// 2. 各バケットをソート
for (vector<float> &bucket : buckets) {
// 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能
sort(bucket.begin(), bucket.end());
}
// 3. バケットを走査して結果をマージ
int i = 0;
for (vector<float> &bucket : buckets) {
for (float num : bucket) {
nums[i++] = num;
}
}
}
/* ドライバコード */
int main() {
// 入力データが浮動小数点数、範囲[0, 1)と仮定
vector<float> nums = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f};
bucketSort(nums);
cout << "バケットソート後、nums = ";
printVector(nums);
return 0;
}

View File

@@ -0,0 +1,77 @@
/**
* File: counting_sort.cpp
* Created Time: 2023-03-17
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* カウントソート */
// 簡単な実装、オブジェクトのソートには使用できない
void countingSortNaive(vector<int> &nums) {
// 1. 配列の最大要素mを統計
int m = 0;
for (int num : nums) {
m = max(m, num);
}
// 2. 各数字の出現回数を統計
// counter[num]はnumの出現回数を表す
vector<int> counter(m + 1, 0);
for (int num : nums) {
counter[num]++;
}
// 3. counterを走査し、各要素を元の配列numsに戻す
int i = 0;
for (int num = 0; num < m + 1; num++) {
for (int j = 0; j < counter[num]; j++, i++) {
nums[i] = num;
}
}
}
/* カウントソート */
// 完全な実装、オブジェクトのソートが可能で安定ソート
void countingSort(vector<int> &nums) {
// 1. 配列の最大要素mを統計
int m = 0;
for (int num : nums) {
m = max(m, num);
}
// 2. 各数字の出現回数を統計
// counter[num]はnumの出現回数を表す
vector<int> counter(m + 1, 0);
for (int num : nums) {
counter[num]++;
}
// 3. counterの前缀和を計算し、「出現回数」を「末尾インデックス」に変換
// counter[num]-1はnumがresで現れる最後のインデックス
for (int i = 0; i < m; i++) {
counter[i + 1] += counter[i];
}
// 4. numsを逆順で走査し、各要素を結果配列resに配置
// 結果を記録する配列resを初期化
int n = nums.size();
vector<int> res(n);
for (int i = n - 1; i >= 0; i--) {
int num = nums[i];
res[counter[num] - 1] = num; // numを対応するインデックスに配置
counter[num]--; // 前缀和を1減らし、numを配置する次のインデックスを取得
}
// 結果配列resで元の配列numsを上書き
nums = res;
}
/* ドライバコード */
int main() {
vector<int> nums = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4};
countingSortNaive(nums);
cout << "カウントソートオブジェクトソート不可後、nums = ";
printVector(nums);
vector<int> nums1 = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4};
countingSort(nums1);
cout << "カウントソート後、nums1 = ";
printVector(nums1);
return 0;
}

View File

@@ -0,0 +1,54 @@
/**
* File: heap_sort.cpp
* Created Time: 2023-05-26
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* ヒープの長さはn、ードiから上から下へヒープ化を開始 */
void siftDown(vector<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;
}
// 二つのノードを交換
swap(nums[i], nums[ma]);
// 下向きにヒープ化をループ
i = ma;
}
}
/* ヒープソート */
void heapSort(vector<int> &nums) {
// ヒープ構築操作:葉以外のすべてのノードをヒープ化
for (int i = nums.size() / 2 - 1; i >= 0; --i) {
siftDown(nums, nums.size(), i);
}
// ヒープから最大要素を抽出し、n-1回繰り返す
for (int i = nums.size() - 1; i > 0; --i) {
// ルートノードを最右葉ノードと交換(最初の要素を最後の要素と交換)
swap(nums[0], nums[i]);
// ルートノードから上から下へヒープ化を開始
siftDown(nums, i, 0);
}
}
/* ドライバコード */
int main() {
vector<int> nums = {4, 1, 3, 1, 5, 2};
heapSort(nums);
cout << "ヒープソート後、nums = ";
printVector(nums);
return 0;
}

View File

@@ -0,0 +1,31 @@
/**
* File: insertion_sort.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 挿入ソート */
void insertionSort(vector<int> &nums) {
// 外側ループ:ソート済み範囲は[0, i-1]
for (int i = 1; i < nums.size(); i++) {
int base = nums[i], j = i - 1;
// 内側ループbaseをソート済み範囲[0, i-1]内の正しい位置に挿入
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j]; // nums[j]を一つ右に移動
j--;
}
nums[j + 1] = base; // baseを正しい位置に代入
}
}
/* ドライバコード */
int main() {
vector<int> nums = {4, 1, 3, 1, 5, 2};
insertionSort(nums);
cout << "挿入ソート後、nums = ";
printVector(nums);
return 0;
}

View File

@@ -0,0 +1,58 @@
/**
* File: merge_sort.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 左サブ配列と右サブ配列をマージ */
void merge(vector<int> &nums, int left, int mid, int right) {
// 左サブ配列の区間は[left, mid]、右サブ配列の区間は[mid+1, right]
// マージ結果を保存する一時配列tmpを作成
vector<int> tmp(right - left + 1);
// 左右サブ配列の開始インデックスを初期化
int i = left, j = mid + 1, k = 0;
// 両サブ配列に要素がある間、小さい方の要素を一時配列にコピー
while (i <= mid && j <= right) {
if (nums[i] <= nums[j])
tmp[k++] = nums[i++];
else
tmp[k++] = nums[j++];
}
// 左右サブ配列の残りの要素を一時配列にコピー
while (i <= mid) {
tmp[k++] = nums[i++];
}
while (j <= right) {
tmp[k++] = nums[j++];
}
// 一時配列tmpの要素を元の配列numsの対応する区間にコピー
for (k = 0; k < tmp.size(); k++) {
nums[left + k] = tmp[k];
}
}
/* マージソート */
void mergeSort(vector<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);
}
/* ドライバコード */
int main() {
/* マージソート */
vector<int> nums = {7, 3, 2, 6, 0, 1, 5, 4};
mergeSort(nums, 0, nums.size() - 1);
cout << "マージソート後、nums = ";
printVector(nums);
return 0;
}

View File

@@ -0,0 +1,166 @@
/**
* File: quick_sort.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* クイックソートクラス */
class QuickSort {
private:
/* 要素を交換 */
static void swap(vector<int> &nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
/* 分割 */
static int partition(vector<int> &nums, int left, int right) {
// nums[left]をピボットとして使用
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--; // 右から左へピボットより小さい最初の要素を検索
while (i < j && nums[i] <= nums[left])
i++; // 左から右へピボットより大きい最初の要素を検索
swap(nums, i, j); // これら二つの要素を交換
}
swap(nums, i, left); // ピボットを二つのサブ配列の境界に交換
return i; // ピボットのインデックスを返す
}
public:
/* クイックソート */
static void quickSort(vector<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 {
private:
/* 要素を交換 */
static void swap(vector<int> &nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
/* 三つの候補要素の中央値を選択 */
static int medianThree(vector<int> &nums, int left, int mid, int right) {
int l = nums[left], m = nums[mid], r = nums[right];
if ((l <= m && m <= r) || (r <= m && m <= l))
return mid; // mはlとrの間
if ((m <= l && l <= r) || (r <= l && l <= m))
return left; // lはmとrの間
return right;
}
/* 分割(三つの中央値) */
static int partition(vector<int> &nums, int left, int right) {
// 三つの候補要素の中央値を選択
int med = medianThree(nums, left, (left + right) / 2, right);
// 中央値を配列の最左位置に交換
swap(nums, left, med);
// nums[left]をピボットとして使用
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--; // 右から左へピボットより小さい最初の要素を検索
while (i < j && nums[i] <= nums[left])
i++; // 左から右へピボットより大きい最初の要素を検索
swap(nums, i, j); // これら二つの要素を交換
}
swap(nums, i, left); // ピボットを二つのサブ配列の境界に交換
return i; // ピボットのインデックスを返す
}
public:
/* クイックソート */
static void quickSort(vector<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 {
private:
/* 要素を交換 */
static void swap(vector<int> &nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
/* 分割 */
static int partition(vector<int> &nums, int left, int right) {
// nums[left]をピボットとして使用
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--; // 右から左へピボットより小さい最初の要素を検索
while (i < j && nums[i] <= nums[left])
i++; // 左から右へピボットより大きい最初の要素を検索
swap(nums, i, j); // これら二つの要素を交換
}
swap(nums, i, left); // ピボットを二つのサブ配列の境界に交換
return i; // ピボットのインデックスを返す
}
public:
/* クイックソート(末尾再帰最適化) */
static void quickSort(vector<int> &nums, int left, int right) {
// サブ配列の長さが1の時終了
while (left < right) {
// 分割操作
int pivot = partition(nums, left, right);
// 二つのサブ配列のうち短い方でクイックソートを実行
if (pivot - left < right - pivot) {
quickSort(nums, left, pivot - 1); // 左サブ配列を再帰的にソート
left = pivot + 1; // 残りの未ソート区間は[pivot + 1, right]
} else {
quickSort(nums, pivot + 1, right); // 右サブ配列を再帰的にソート
right = pivot - 1; // 残りの未ソート区間は[left, pivot - 1]
}
}
}
};
/* ドライバコード */
int main() {
/* クイックソート */
vector<int> nums{2, 4, 1, 0, 3, 5};
QuickSort::quickSort(nums, 0, nums.size() - 1);
cout << "クイックソート後、nums = ";
printVector(nums);
/* クイックソート(中央値ピボット最適化) */
vector<int> nums1 = {2, 4, 1, 0, 3, 5};
QuickSortMedian::quickSort(nums1, 0, nums1.size() - 1);
cout << "クイックソート中央値ピボット最適化完了、nums = ";
printVector(nums1);
/* クイックソート(末尾再帰最適化) */
vector<int> nums2 = {2, 4, 1, 0, 3, 5};
QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1);
cout << "クイックソート末尾再帰最適化完了、nums = ";
printVector(nums2);
return 0;
}

View File

@@ -0,0 +1,65 @@
/**
* File: radix_sort.cpp
* Created Time: 2023-03-26
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 要素numのk番目の桁を取得、exp = 10^(k-1) */
int digit(int num, int exp) {
// kの代わりにexpを渡すことで、ここで繰り返される高価な冪乗計算を避けることができる
return (num / exp) % 10;
}
/* カウントソートnumsのk番目の桁に基づく */
void countingSortDigit(vector<int> &nums, int exp) {
// 10進数の桁範囲は0~9なので、長さ10のバケット配列が必要
vector<int> counter(10, 0);
int n = nums.size();
// 数字0~9の出現回数を統計
for (int i = 0; i < n; i++) {
int d = digit(nums[i], exp); // nums[i]のk番目の桁を取得、dとして記録
counter[d]++; // 数字dの出現回数を統計
}
// 前缀和を計算し、「出現回数」を「配列インデックス」に変換
for (int i = 1; i < 10; i++) {
counter[i] += counter[i - 1];
}
// 逆順で走査し、バケット統計に基づいて各要素をresに配置
vector<int> res(n, 0);
for (int i = n - 1; i >= 0; i--) {
int d = digit(nums[i], exp);
int j = counter[d] - 1; // dが配列内にあるインデックスjを取得
res[j] = nums[i]; // 現在の要素をインデックスjに配置
counter[d]--; // dのカウントを1減らす
}
// 結果で元の配列numsを上書き
for (int i = 0; i < n; i++)
nums[i] = res[i];
}
/* 基数ソート */
void radixSort(vector<int> &nums) {
// 配列の最大要素を取得、最大桁数を判定するために使用
int m = *max_element(nums.begin(), nums.end());
// 最下位桁から最上位桁まで走査
for (int exp = 1; exp <= m; exp *= 10)
// 配列要素のk番目の桁でカウントソートを実行
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// つまり、exp = 10^(k-1)
countingSortDigit(nums, exp);
}
/* ドライバコード */
int main() {
// 基数ソート
vector<int> nums = {10546151, 35663510, 42865989, 34862445, 81883077,
88906420, 72429244, 30524779, 82060337, 63832996};
radixSort(nums);
cout << "基数ソート後、nums = ";
printVector(nums);
return 0;
}

View File

@@ -0,0 +1,34 @@
/**
* File: selection_sort.cpp
* Created Time: 2023-05-23
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 選択ソート */
void selectionSort(vector<int> &nums) {
int n = nums.size();
// 外側ループ:未ソート範囲は[i, n-1]
for (int i = 0; i < n - 1; i++) {
// 内側ループ:未ソート範囲内で最小要素を見つける
int k = i;
for (int j = i + 1; j < n; j++) {
if (nums[j] < nums[k])
k = j; // 最小要素のインデックスを記録
}
// 最小要素を未ソート範囲の最初の要素と交換
swap(nums[i], nums[k]);
}
}
/* ドライバコード */
int main() {
vector<int> nums = {4, 1, 3, 1, 5, 2};
selectionSort(nums);
cout << "選択ソート後、nums = ";
printVector(nums);
return 0;
}

View File

@@ -0,0 +1,156 @@
/**
* File: array_deque.cpp
* Created Time: 2023-03-02
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 循環配列に基づく両端キュークラス */
class ArrayDeque {
private:
vector<int> nums; // 両端キューの要素を格納する配列
int front; // 先頭ポインタ、先頭要素を指す
int queSize; // 両端キューの長さ
public:
/* コンストラクタ */
ArrayDeque(int capacity) {
nums.resize(capacity);
front = queSize = 0;
}
/* 両端キューの容量を取得 */
int capacity() {
return nums.size();
}
/* 両端キューの長さを取得 */
int size() {
return queSize;
}
/* 両端キューが空かどうかを判定 */
bool isEmpty() {
return queSize == 0;
}
/* 循環配列のインデックスを計算 */
int index(int i) {
// 剰余演算で循環配列を実現
// iが配列の末尾を超えた場合、先頭に戻る
// iが配列の先頭を超えた場合、末尾に戻る
return (i + capacity()) % capacity();
}
/* 先頭エンキュー */
void pushFirst(int num) {
if (queSize == capacity()) {
cout << "Double-ended queue is full" << endl;
return;
}
// 先頭ポインタを1つ左に移動
// 剰余演算でfrontが配列の先頭を越えて末尾に戻ることを実現
front = index(front - 1);
// numを先頭に追加
nums[front] = num;
queSize++;
}
/* 末尾エンキュー */
void pushLast(int num) {
if (queSize == capacity()) {
cout << "Double-ended queue is full" << endl;
return;
}
// 末尾ポインタを計算、末尾インデックス + 1を指す
int rear = index(front + queSize);
// numを末尾に追加
nums[rear] = num;
queSize++;
}
/* 先頭デキュー */
int popFirst() {
int num = peekFirst();
// 先頭ポインタを1つ後ろに移動
front = index(front + 1);
queSize--;
return num;
}
/* 末尾デキュー */
int popLast() {
int num = peekLast();
queSize--;
return num;
}
/* 先頭要素にアクセス */
int peekFirst() {
if (isEmpty())
throw out_of_range("Double-ended queue is empty");
return nums[front];
}
/* 末尾要素にアクセス */
int peekLast() {
if (isEmpty())
throw out_of_range("Double-ended queue is empty");
// 末尾要素のインデックスを計算
int last = index(front + queSize - 1);
return nums[last];
}
/* 印刷用に配列を返却 */
vector<int> toVector() {
// 有効な長さ範囲内の要素のみを変換
vector<int> res(queSize);
for (int i = 0, j = front; i < queSize; i++, j++) {
res[i] = nums[index(j)];
}
return res;
}
};
/* ドライバーコード */
int main() {
/* 両端キューを初期化 */
ArrayDeque *deque = new ArrayDeque(10);
deque->pushLast(3);
deque->pushLast(2);
deque->pushLast(5);
cout << "Double-ended queue deque = ";
printVector(deque->toVector());
/* 要素にアクセス */
int peekFirst = deque->peekFirst();
cout << "Front element peekFirst = " << peekFirst << endl;
int peekLast = deque->peekLast();
cout << "Back element peekLast = " << peekLast << endl;
/* 要素エンキュー */
deque->pushLast(4);
cout << "Element 4 enqueued at the tail, deque = ";
printVector(deque->toVector());
deque->pushFirst(1);
cout << "Element 1 enqueued at the head, deque = ";
printVector(deque->toVector());
/* 要素デキュー */
int popLast = deque->popLast();
cout << "Deque tail element = " << popLast << ", after dequeuing from the tail";
printVector(deque->toVector());
int popFirst = deque->popFirst();
cout << "Deque front element = " << popFirst << ", after dequeuing from the front";
printVector(deque->toVector());
/* 両端キューの長さを取得 */
int size = deque->size();
cout << "Length of the double-ended queue size = " << size << endl;
/* 両端キューが空かどうかを判定 */
bool isEmpty = deque->isEmpty();
cout << "Is the double-ended queue empty = " << boolalpha << isEmpty << endl;
return 0;
}

View File

@@ -0,0 +1,129 @@
/**
* File: array_queue.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 循環配列に基づくキュークラス */
class ArrayQueue {
private:
int *nums; // キュー要素を格納する配列
int front; // 先頭ポインタ、先頭要素を指す
int queSize; // キューの長さ
int queCapacity; // キューの容量
public:
ArrayQueue(int capacity) {
// 配列を初期化
nums = new int[capacity];
queCapacity = capacity;
front = queSize = 0;
}
~ArrayQueue() {
delete[] nums;
}
/* キューの容量を取得 */
int capacity() {
return queCapacity;
}
/* キューの長さを取得 */
int size() {
return queSize;
}
/* キューが空かどうかを判定 */
bool isEmpty() {
return size() == 0;
}
/* エンキュー */
void push(int num) {
if (queSize == queCapacity) {
cout << "Queue is full" << endl;
return;
}
// 末尾ポインタを計算、末尾インデックス + 1を指す
// 剰余演算を使用して末尾ポインタが配列の末尾から先頭に戻るようにラップ
int rear = (front + queSize) % queCapacity;
// numを末尾に追加
nums[rear] = num;
queSize++;
}
/* デキュー */
int pop() {
int num = peek();
// 先頭ポインタを1つ後ろに移動、末尾を超えた場合は配列の先頭に戻る
front = (front + 1) % queCapacity;
queSize--;
return num;
}
/* 先頭要素にアクセス */
int peek() {
if (isEmpty())
throw out_of_range("Queue is empty");
return nums[front];
}
/* 配列をVectorに変換して返却 */
vector<int> toVector() {
// 有効な長さ範囲内の要素のみを変換
vector<int> arr(queSize);
for (int i = 0, j = front; i < queSize; i++, j++) {
arr[i] = nums[j % queCapacity];
}
return arr;
}
};
/* ドライバーコード */
int main() {
/* キューを初期化 */
int capacity = 10;
ArrayQueue *queue = new ArrayQueue(capacity);
/* 要素エンキュー */
queue->push(1);
queue->push(3);
queue->push(2);
queue->push(5);
queue->push(4);
cout << "Queue queue = ";
printVector(queue->toVector());
/* 先頭要素にアクセス */
int peek = queue->peek();
cout << "Front element peek = " << peek << endl;
/* 要素デキュー */
peek = queue->pop();
cout << "Element dequeued = " << peek << ", after dequeuing";
printVector(queue->toVector());
/* キューの長さを取得 */
int size = queue->size();
cout << "Length of the queue size = " << size << endl;
/* キューが空かどうかを判定 */
bool empty = queue->isEmpty();
cout << "Is the queue empty = " << empty << endl;
/* 循環配列をテスト */
for (int i = 0; i < 10; i++) {
queue->push(i);
queue->pop();
cout << "After the " << i << "th round of enqueueing + dequeuing, queue = ";
printVector(queue->toVector());
}
// メモリを解放
delete queue;
return 0;
}

View File

@@ -0,0 +1,85 @@
/**
* File: array_stack.cpp
* Created Time: 2022-11-28
* Author: qualifier1024 (2539244001@qq.com)
*/
#include "../utils/common.hpp"
/* 配列に基づくスタッククラス */
class ArrayStack {
private:
vector<int> stack;
public:
/* スタックの長さを取得 */
int size() {
return stack.size();
}
/* スタックが空かどうかを判定 */
bool isEmpty() {
return stack.size() == 0;
}
/* プッシュ */
void push(int num) {
stack.push_back(num);
}
/* ポップ */
int pop() {
int num = top();
stack.pop_back();
return num;
}
/* スタックトップ要素にアクセス */
int top() {
if (isEmpty())
throw out_of_range("Stack is empty");
return stack.back();
}
/* Vectorを返却 */
vector<int> toVector() {
return stack;
}
};
/* ドライバーコード */
int main() {
/* スタックを初期化 */
ArrayStack *stack = new ArrayStack();
/* 要素プッシュ */
stack->push(1);
stack->push(3);
stack->push(2);
stack->push(5);
stack->push(4);
cout << "Stack stack = ";
printVector(stack->toVector());
/* スタックトップ要素にアクセス */
int top = stack->top();
cout << "Top element of the stack top = " << top << endl;
/* 要素ポップ */
top = stack->pop();
cout << "Element popped from the stack = " << top << ", after popping";
printVector(stack->toVector());
/* スタックの長さを取得 */
int size = stack->size();
cout << "Length of the stack size = " << size << endl;
/* 空かどうかを判定 */
bool empty = stack->isEmpty();
cout << "Is the stack empty = " << empty << endl;
// メモリを解放
delete stack;
return 0;
}

View File

@@ -0,0 +1,46 @@
/**
* File: deque.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* ドライバーコード */
int main() {
/* 両端キューを初期化 */
deque<int> deque;
/* 要素エンキュー */
deque.push_back(2);
deque.push_back(5);
deque.push_back(4);
deque.push_front(3);
deque.push_front(1);
cout << "Double-ended queue deque = ";
printDeque(deque);
/* 要素にアクセス */
int front = deque.front();
cout << "Front element of the queue front = " << front << endl;
int back = deque.back();
cout << "Back element of the queue back = " << back << endl;
/* 要素デキュー */
deque.pop_front();
cout << "Front element dequeued = " << front << ", after dequeuing from the front";
printDeque(deque);
deque.pop_back();
cout << "Back element dequeued = " << back << ", after dequeuing from the back";
printDeque(deque);
/* 両端キューの長さを取得 */
int size = deque.size();
cout << "Length of the double-ended queue size = " << size << endl;
/* 両端キューが空かどうかを判定 */
bool empty = deque.empty();
cout << "Is the double-ended queue empty = " << empty << endl;
return 0;
}

View File

@@ -0,0 +1,194 @@
/**
* File: linkedlist_deque.cpp
* Created Time: 2023-03-02
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 双方向連結リストノード */
struct DoublyListNode {
int val; // ノードの値
DoublyListNode *next; // 後続ノードへのポインタ
DoublyListNode *prev; // 前続ノードへのポインタ
DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {
}
};
/* 双方向連結リストに基づく両端キュークラス */
class LinkedListDeque {
private:
DoublyListNode *front, *rear; // 先頭ードfront、末尾ードrear
int queSize = 0; // 両端キューの長さ
public:
/* コンストラクタ */
LinkedListDeque() : front(nullptr), rear(nullptr) {
}
/* デストラクタ */
~LinkedListDeque() {
// 連結リストを走査、ノードを削除、メモリを解放
DoublyListNode *pre, *cur = front;
while (cur != nullptr) {
pre = cur;
cur = cur->next;
delete pre;
}
}
/* 両端キューの長さを取得 */
int size() {
return queSize;
}
/* 両端キューが空かどうかを判定 */
bool isEmpty() {
return size() == 0;
}
/* エンキュー操作 */
void push(int num, bool isFront) {
DoublyListNode *node = new DoublyListNode(num);
// リストが空の場合、frontとrearの両方をnodeに向ける
if (isEmpty())
front = rear = node;
// 先頭エンキュー操作
else if (isFront) {
// ノードをリストの先頭に追加
front->prev = node;
node->next = front;
front = node; // 先頭ノードを更新
// 末尾エンキュー操作
} else {
// ノードをリストの末尾に追加
rear->next = node;
node->prev = rear;
rear = node; // 末尾ノードを更新
}
queSize++; // キュー長を更新
}
/* 先頭エンキュー */
void pushFirst(int num) {
push(num, true);
}
/* 末尾エンキュー */
void pushLast(int num) {
push(num, false);
}
/* デキュー操作 */
int pop(bool isFront) {
if (isEmpty())
throw out_of_range("Queue is empty");
int val;
// 先頭デキュー操作
if (isFront) {
val = front->val; // 先頭ノードの値を一時保存
// 先頭ノードを削除
DoublyListNode *fNext = front->next;
if (fNext != nullptr) {
fNext->prev = nullptr;
front->next = nullptr;
}
delete front;
front = fNext; // 先頭ノードを更新
// 末尾デキュー操作
} else {
val = rear->val; // 末尾ノードの値を一時保存
// 末尾ノードを削除
DoublyListNode *rPrev = rear->prev;
if (rPrev != nullptr) {
rPrev->next = nullptr;
rear->prev = nullptr;
}
delete rear;
rear = rPrev; // 末尾ノードを更新
}
queSize--; // キュー長を更新
return val;
}
/* 先頭デキュー */
int popFirst() {
return pop(true);
}
/* 末尾デキュー */
int popLast() {
return pop(false);
}
/* 先頭要素にアクセス */
int peekFirst() {
if (isEmpty())
throw out_of_range("Double-ended queue is empty");
return front->val;
}
/* 末尾要素にアクセス */
int peekLast() {
if (isEmpty())
throw out_of_range("Double-ended queue is empty");
return rear->val;
}
/* 印刷用に配列を返却 */
vector<int> toVector() {
DoublyListNode *node = front;
vector<int> res(size());
for (int i = 0; i < res.size(); i++) {
res[i] = node->val;
node = node->next;
}
return res;
}
};
/* ドライバーコード */
int main() {
/* 両端キューを初期化 */
LinkedListDeque *deque = new LinkedListDeque();
deque->pushLast(3);
deque->pushLast(2);
deque->pushLast(5);
cout << "Double-ended queue deque = ";
printVector(deque->toVector());
/* 要素にアクセス */
int peekFirst = deque->peekFirst();
cout << "Front element peekFirst = " << peekFirst << endl;
int peekLast = deque->peekLast();
cout << "Back element peekLast = " << peekLast << endl;
/* 要素エンキュー */
deque->pushLast(4);
cout << "Element 4 rear enqueued, deque =";
printVector(deque->toVector());
deque->pushFirst(1);
cout << "Element 1 enqueued at the head, deque = ";
printVector(deque->toVector());
/* 要素デキュー */
int popLast = deque->popLast();
cout << "Deque tail element = " << popLast << ", after dequeuing from the tail";
printVector(deque->toVector());
int popFirst = deque->popFirst();
cout << "Deque front element = " << popFirst << ", after dequeuing from the front";
printVector(deque->toVector());
/* 両端キューの長さを取得 */
int size = deque->size();
cout << "Length of the double-ended queue size = " << size << endl;
/* 両端キューが空かどうかを判定 */
bool isEmpty = deque->isEmpty();
cout << "Is the double-ended queue empty = " << boolalpha << isEmpty << endl;
// メモリを解放
delete deque;
return 0;
}

View File

@@ -0,0 +1,120 @@
/**
* File: linkedlist_queue.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 連結リストに基づくキュークラス */
class LinkedListQueue {
private:
ListNode *front, *rear; // 先頭ードfront、末尾ードrear
int queSize;
public:
LinkedListQueue() {
front = nullptr;
rear = nullptr;
queSize = 0;
}
~LinkedListQueue() {
// 連結リストを走査、ノードを削除、メモリを解放
freeMemoryLinkedList(front);
}
/* キューの長さを取得 */
int size() {
return queSize;
}
/* キューが空かどうかを判定 */
bool isEmpty() {
return queSize == 0;
}
/* エンキュー */
void push(int num) {
// 末尾ードの後ろにnumを追加
ListNode *node = new ListNode(num);
// キューが空の場合、先頭と末尾ノードの両方をそのノードに向ける
if (front == nullptr) {
front = node;
rear = node;
}
// キューが空でない場合、そのノードを末尾ノードの後ろに追加
else {
rear->next = node;
rear = node;
}
queSize++;
}
/* デキュー */
int pop() {
int num = peek();
// 先頭ノードを削除
ListNode *tmp = front;
front = front->next;
// メモリを解放
delete tmp;
queSize--;
return num;
}
/* 先頭要素にアクセス */
int peek() {
if (size() == 0)
throw out_of_range("Queue is empty");
return front->val;
}
/* 連結リストをVectorに変換して返却 */
vector<int> toVector() {
ListNode *node = front;
vector<int> res(size());
for (int i = 0; i < res.size(); i++) {
res[i] = node->val;
node = node->next;
}
return res;
}
};
/* ドライバーコード */
int main() {
/* キューを初期化 */
LinkedListQueue *queue = new LinkedListQueue();
/* 要素エンキュー */
queue->push(1);
queue->push(3);
queue->push(2);
queue->push(5);
queue->push(4);
cout << "Queue queue = ";
printVector(queue->toVector());
/* 先頭要素にアクセス */
int peek = queue->peek();
cout << "Front element peek = " << peek << endl;
/* 要素デキュー */
peek = queue->pop();
cout << "Element dequeued = " << peek << ", after dequeuing";
printVector(queue->toVector());
/* キューの長さを取得 */
int size = queue->size();
cout << "Length of the queue size = " << size << endl;
/* キューが空かどうかを判定 */
bool empty = queue->isEmpty();
cout << "Is the queue empty = " << empty << endl;
// メモリを解放
delete queue;
return 0;
}

View File

@@ -0,0 +1,109 @@
/**
* File: linkedlist_stack.cpp
* Created Time: 2022-11-28
* Author: qualifier1024 (2539244001@qq.com)
*/
#include "../utils/common.hpp"
/* 連結リストに基づくスタッククラス */
class LinkedListStack {
private:
ListNode *stackTop; // 先頭ノードをスタックトップとして使用
int stkSize; // スタックの長さ
public:
LinkedListStack() {
stackTop = nullptr;
stkSize = 0;
}
~LinkedListStack() {
// 連結リストを走査、ノードを削除、メモリを解放
freeMemoryLinkedList(stackTop);
}
/* スタックの長さを取得 */
int size() {
return stkSize;
}
/* スタックが空かどうかを判定 */
bool isEmpty() {
return size() == 0;
}
/* プッシュ */
void push(int num) {
ListNode *node = new ListNode(num);
node->next = stackTop;
stackTop = node;
stkSize++;
}
/* ポップ */
int pop() {
int num = top();
ListNode *tmp = stackTop;
stackTop = stackTop->next;
// メモリを解放
delete tmp;
stkSize--;
return num;
}
/* スタックトップ要素にアクセス */
int top() {
if (isEmpty())
throw out_of_range("Stack is empty");
return stackTop->val;
}
/* リストを配列に変換して返却 */
vector<int> toVector() {
ListNode *node = stackTop;
vector<int> res(size());
for (int i = res.size() - 1; i >= 0; i--) {
res[i] = node->val;
node = node->next;
}
return res;
}
};
/* ドライバーコード */
int main() {
/* スタックを初期化 */
LinkedListStack *stack = new LinkedListStack();
/* 要素プッシュ */
stack->push(1);
stack->push(3);
stack->push(2);
stack->push(5);
stack->push(4);
cout << "Stack stack = ";
printVector(stack->toVector());
/* スタックトップ要素にアクセス */
int top = stack->top();
cout << "Top element of the stack top = " << top << endl;
/* 要素ポップ */
top = stack->pop();
cout << "Element popped from the stack = " << top << ", after popping";
printVector(stack->toVector());
/* スタックの長さを取得 */
int size = stack->size();
cout << "Length of the stack size = " << size << endl;
/* 空かどうかを判定 */
bool empty = stack->isEmpty();
cout << "Is the stack empty = " << empty << endl;
// メモリを解放
delete stack;
return 0;
}

View File

@@ -0,0 +1,41 @@
/**
* File: queue.cpp
* Created Time: 2022-11-28
* Author: qualifier1024 (2539244001@qq.com)
*/
#include "../utils/common.hpp"
/* ドライバーコード */
int main() {
/* キューを初期化 */
queue<int> queue;
/* 要素エンキュー */
queue.push(1);
queue.push(3);
queue.push(2);
queue.push(5);
queue.push(4);
cout << "Queue queue = ";
printQueue(queue);
/* 先頭要素にアクセス */
int front = queue.front();
cout << "Front element of the queue front = " << front << endl;
/* 要素デキュー */
queue.pop();
cout << "Element dequeued = " << front << ", after dequeuing";
printQueue(queue);
/* キューの長さを取得 */
int size = queue.size();
cout << "Length of the queue size = " << size << endl;
/* キューが空かどうかを判定 */
bool empty = queue.empty();
cout << "Is the queue empty = " << empty << endl;
return 0;
}

View File

@@ -0,0 +1,41 @@
/**
* File: stack.cpp
* Created Time: 2022-11-28
* Author: qualifier1024 (2539244001@qq.com)
*/
#include "../utils/common.hpp"
/* ドライバーコード */
int main() {
/* スタックを初期化 */
stack<int> stack;
/* 要素プッシュ */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);
cout << "Stack stack = ";
printStack(stack);
/* スタックトップ要素にアクセス */
int top = stack.top();
cout << "Top element of the stack top = " << top << endl;
/* 要素ポップ */
stack.pop(); // 戻り値なし
cout << "Element popped from the stack = " << top << ", after popping";
printStack(stack);
/* スタックの長さを取得 */
int size = stack.size();
cout << "Length of the stack size = " << size << endl;
/* 空かどうかを判定 */
bool empty = stack.empty();
cout << "Is the stack empty = " << empty << endl;
return 0;
}

View File

@@ -0,0 +1,137 @@
/**
* File: array_binary_tree.cpp
* Created Time: 2023-07-19
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 配列ベースの二分木クラス */
class ArrayBinaryTree {
public:
/* コンストラクタ */
ArrayBinaryTree(vector<int> arr) {
tree = arr;
}
/* リストの容量 */
int size() {
return tree.size();
}
/* インデックス i のノードの値を取得 */
int val(int i) {
// インデックスが範囲外の場合、INT_MAX を返すnull を表す)
if (i < 0 || i >= size())
return INT_MAX;
return tree[i];
}
/* インデックス i のノードの左の子のインデックスを取得 */
int left(int i) {
return 2 * i + 1;
}
/* インデックス i のノードの右の子のインデックスを取得 */
int right(int i) {
return 2 * i + 2;
}
/* インデックス i のノードの親のインデックスを取得 */
int parent(int i) {
return (i - 1) / 2;
}
/* レベル順走査 */
vector<int> levelOrder() {
vector<int> res;
// 配列を走査
for (int i = 0; i < size(); i++) {
if (val(i) != INT_MAX)
res.push_back(val(i));
}
return res;
}
/* 前順走査 */
vector<int> preOrder() {
vector<int> res;
dfs(0, "pre", res);
return res;
}
/* 中順走査 */
vector<int> inOrder() {
vector<int> res;
dfs(0, "in", res);
return res;
}
/* 後順走査 */
vector<int> postOrder() {
vector<int> res;
dfs(0, "post", res);
return res;
}
private:
vector<int> tree;
/* 深さ優先走査 */
void dfs(int i, string order, vector<int> &res) {
// 空の位置の場合、戻る
if (val(i) == INT_MAX)
return;
// 前順走査
if (order == "pre")
res.push_back(val(i));
dfs(left(i), order, res);
// 中順走査
if (order == "in")
res.push_back(val(i));
dfs(right(i), order, res);
// 後順走査
if (order == "post")
res.push_back(val(i));
}
};
/* ドライバーコード */
int main() {
// 二分木を初期化
// INT_MAX を使用して空の位置 nullptr を表す
vector<int> arr = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};
TreeNode *root = vectorToTree(arr);
cout << "\n二分木を初期化\n";
cout << "二分木の配列表現:\n";
printVector(arr);
cout << "二分木の連結リスト表現:\n";
printTree(root);
// 配列ベースの二分木クラス
ArrayBinaryTree abt(arr);
// ノードにアクセス
int i = 1;
int l = abt.left(i), r = abt.right(i), p = abt.parent(i);
cout << "\n現在のノードのインデックスは " << i << "、値 = " << abt.val(i) << "\n";
cout << "その左の子のインデックスは " << l << "、値 = " << (l != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n";
cout << "その右の子のインデックスは " << r << "、値 = " << (r != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n";
cout << "その親のインデックスは " << p << "、値 = " << (p != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n";
// 木を走査
vector<int> res = abt.levelOrder();
cout << "\nレベル順走査は:";
printVector(res);
res = abt.preOrder();
cout << "前順走査は:";
printVector(res);
res = abt.inOrder();
cout << "中順走査は:";
printVector(res);
res = abt.postOrder();
cout << "後順走査は:";
printVector(res);
return 0;
}

View File

@@ -0,0 +1,233 @@
/**
* File: avl_tree.cpp
* Created Time: 2023-02-03
* Author: what-is-me (whatisme@outlook.jp)
*/
#include "../utils/common.hpp"
/* AVL木 */
class AVLTree {
private:
/* ノードの高さを更新 */
void updateHeight(TreeNode *node) {
// ノードの高さ = 最も高い部分木の高さ + 1
node->height = max(height(node->left), height(node->right)) + 1;
}
/* 右回転操作 */
TreeNode *rightRotate(TreeNode *node) {
TreeNode *child = node->left;
TreeNode *grandChild = child->right;
// childを中心にnodeを右に回転
child->right = node;
node->left = grandChild;
// ノードの高さを更新
updateHeight(node);
updateHeight(child);
// 回転後の部分木のルートを返す
return child;
}
/* 左回転操作 */
TreeNode *leftRotate(TreeNode *node) {
TreeNode *child = node->right;
TreeNode *grandChild = child->left;
// childを中心にnodeを左に回転
child->left = node;
node->right = grandChild;
// ノードの高さを更新
updateHeight(node);
updateHeight(child);
// 回転後の部分木のルートを返す
return child;
}
/* 回転操作を実行して部分木の平衡を回復 */
TreeNode *rotate(TreeNode *node) {
// nodeの平衡因子を取得
int _balanceFactor = balanceFactor(node);
// 左に傾いた木
if (_balanceFactor > 1) {
if (balanceFactor(node->left) >= 0) {
// 右回転
return rightRotate(node);
} else {
// 先に左回転、その後右回転
node->left = leftRotate(node->left);
return rightRotate(node);
}
}
// 右に傾いた木
if (_balanceFactor < -1) {
if (balanceFactor(node->right) <= 0) {
// 左回転
return leftRotate(node);
} else {
// 先に右回転、その後左回転
node->right = rightRotate(node->right);
return leftRotate(node);
}
}
// 平衡な木、回転不要、そのまま戻る
return node;
}
/* ノードを再帰的に挿入(ヘルパーメソッド) */
TreeNode *insertHelper(TreeNode *node, int val) {
if (node == nullptr)
return new TreeNode(val);
/* 1. 挿入位置を見つけてノードを挿入 */
if (val < node->val)
node->left = insertHelper(node->left, val);
else if (val > node->val)
node->right = insertHelper(node->right, val);
else
return node; // 重複ノードは挿入しない、そのまま戻る
updateHeight(node); // ノードの高さを更新
/* 2. 回転操作を実行して部分木の平衡を回復 */
node = rotate(node);
// 部分木のルートノードを返す
return node;
}
/* ノードを再帰的に削除(ヘルパーメソッド) */
TreeNode *removeHelper(TreeNode *node, int val) {
if (node == nullptr)
return nullptr;
/* 1. ノードを見つけて削除 */
if (val < node->val)
node->left = removeHelper(node->left, val);
else if (val > node->val)
node->right = removeHelper(node->right, val);
else {
if (node->left == nullptr || node->right == nullptr) {
TreeNode *child = node->left != nullptr ? node->left : node->right;
// 子ノード数 = 0、ードを削除して戻る
if (child == nullptr) {
delete node;
return nullptr;
}
// 子ノード数 = 1、ードを削除
else {
delete node;
node = child;
}
} else {
// 子ノード数 = 2、中順走査の次のードを削除し、現在のードと置き換える
TreeNode *temp = node->right;
while (temp->left != nullptr) {
temp = temp->left;
}
int tempVal = temp->val;
node->right = removeHelper(node->right, temp->val);
node->val = tempVal;
}
}
updateHeight(node); // ノードの高さを更新
/* 2. 回転操作を実行して部分木の平衡を回復 */
node = rotate(node);
// 部分木のルートノードを返す
return node;
}
public:
TreeNode *root; // ルートノード
/* ノードの高さを取得 */
int height(TreeNode *node) {
// 空ノードの高さは-1、葉ードの高さは0
return node == nullptr ? -1 : node->height;
}
/* 平衡因子を取得 */
int balanceFactor(TreeNode *node) {
// 空ードの平衡因子は0
if (node == nullptr)
return 0;
// ノードの平衡因子 = 左部分木の高さ - 右部分木の高さ
return height(node->left) - height(node->right);
}
/* ノードを挿入 */
void insert(int val) {
root = insertHelper(root, val);
}
/* ノードを削除 */
void remove(int val) {
root = removeHelper(root, val);
}
/* ノードを検索 */
TreeNode *search(int val) {
TreeNode *cur = root;
// ループで検索、葉ノードを通り過ぎたら終了
while (cur != nullptr) {
// 目標ードはcurの右部分木にある
if (cur->val < val)
cur = cur->right;
// 目標ードはcurの左部分木にある
else if (cur->val > val)
cur = cur->left;
// 目標ノードを見つけた、ループを抜ける
else
break;
}
// 目標ノードを返す
return cur;
}
/*コンストラクタ*/
AVLTree() : root(nullptr) {
}
/*デストラクタ*/
~AVLTree() {
freeMemoryTree(root);
}
};
void testInsert(AVLTree &tree, int val) {
tree.insert(val);
cout << "\nノード " << val << " を挿入後、AVL木は" << endl;
printTree(tree.root);
}
void testRemove(AVLTree &tree, int val) {
tree.remove(val);
cout << "\nノード " << val << " を削除後、AVL木は" << endl;
printTree(tree.root);
}
/* ドライバーコード */
int main() {
/* 空のAVL木を初期化 */
AVLTree avlTree;
/* ノードを挿入 */
// AVL木がード挿入後に平衡を維持する様子に注目
testInsert(avlTree, 1);
testInsert(avlTree, 2);
testInsert(avlTree, 3);
testInsert(avlTree, 4);
testInsert(avlTree, 5);
testInsert(avlTree, 8);
testInsert(avlTree, 7);
testInsert(avlTree, 9);
testInsert(avlTree, 10);
testInsert(avlTree, 6);
/* 重複ノードを挿入 */
testInsert(avlTree, 7);
/* ノードを削除 */
// AVL木がード削除後に平衡を維持する様子に注目
testRemove(avlTree, 8); // 次数0のードを削除
testRemove(avlTree, 5); // 次数1のードを削除
testRemove(avlTree, 4); // 次数2のードを削除
/* ノードを検索 */
TreeNode *node = avlTree.search(7);
cout << "\n見つかったノードオブジェクトは " << node << "、ノード値 =" << node->val << endl;
}

View File

@@ -0,0 +1,170 @@
/**
* File: binary_search_tree.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* 二分探索木 */
class BinarySearchTree {
private:
TreeNode *root;
public:
/* コンストラクタ */
BinarySearchTree() {
// 空の木を初期化
root = nullptr;
}
/* デストラクタ */
~BinarySearchTree() {
freeMemoryTree(root);
}
/* 二分木のルートノードを取得 */
TreeNode *getRoot() {
return root;
}
/* ノードを検索 */
TreeNode *search(int num) {
TreeNode *cur = root;
// ループで検索、葉ノードを通り過ぎたら終了
while (cur != nullptr) {
// 目標ードはcurの右部分木にある
if (cur->val < num)
cur = cur->right;
// 目標ードはcurの左部分木にある
else if (cur->val > num)
cur = cur->left;
// 目標ノードを見つけた、ループを抜ける
else
break;
}
// 目標ノードを返す
return cur;
}
/* ノードを挿入 */
void insert(int num) {
// 木が空の場合、ルートノードを初期化
if (root == nullptr) {
root = new TreeNode(num);
return;
}
TreeNode *cur = root, *pre = nullptr;
// ループで検索、葉ノードを通り過ぎたら終了
while (cur != nullptr) {
// 重複ノードを見つけた場合、戻る
if (cur->val == num)
return;
pre = cur;
// 挿入位置はcurの右部分木にある
if (cur->val < num)
cur = cur->right;
// 挿入位置はcurの左部分木にある
else
cur = cur->left;
}
// ノードを挿入
TreeNode *node = new TreeNode(num);
if (pre->val < num)
pre->right = node;
else
pre->left = node;
}
/* ノードを削除 */
void remove(int num) {
// 木が空の場合、戻る
if (root == nullptr)
return;
TreeNode *cur = root, *pre = nullptr;
// ループで検索、葉ノードを通り過ぎたら終了
while (cur != nullptr) {
// 削除するノードを見つけた、ループを抜ける
if (cur->val == num)
break;
pre = cur;
// 削除するードはcurの右部分木にある
if (cur->val < num)
cur = cur->right;
// 削除するードはcurの左部分木にある
else
cur = cur->left;
}
// 削除するノードがない場合、戻る
if (cur == nullptr)
return;
// 子ノード数 = 0 または 1
if (cur->left == nullptr || cur->right == nullptr) {
// 子ノード数 = 0 / 1の場合、child = nullptr / その子ノード
TreeNode *child = cur->left != nullptr ? cur->left : cur->right;
// ードcurを削除
if (cur != root) {
if (pre->left == cur)
pre->left = child;
else
pre->right = child;
} else {
// 削除されるノードがルートの場合、ルートを再割り当て
root = child;
}
// メモリを解放
delete cur;
}
// 子ノード数 = 2
else {
// curの中順走査の次のードを取得
TreeNode *tmp = cur->right;
while (tmp->left != nullptr) {
tmp = tmp->left;
}
int tmpVal = tmp->val;
// ードtmpを再帰的に削除
remove(tmp->val);
// curをtmpで置き換え
cur->val = tmpVal;
}
}
};
/* ドライバーコード */
int main() {
/* 二分探索木を初期化 */
BinarySearchTree *bst = new BinarySearchTree();
// 異なる挿入順序は様々な木構造を生み出すことに注意。この特定の順序は完全二分木を作成します
vector<int> nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15};
for (int num : nums) {
bst->insert(num);
}
cout << endl << "初期化された二分木は\n" << endl;
printTree(bst->getRoot());
/* ノードを検索 */
TreeNode *node = bst->search(7);
cout << endl << "見つかったノードオブジェクトは " << node << "、ノード値 =" << node->val << endl;
/* ノードを挿入 */
bst->insert(16);
cout << endl << "ノード 16 を挿入後、二分木は\n" << endl;
printTree(bst->getRoot());
/* ノードを削除 */
bst->remove(1);
cout << endl << "ノード 1 を削除後、二分木は\n" << endl;
printTree(bst->getRoot());
bst->remove(2);
cout << endl << "ノード 2 を削除後、二分木は\n" << endl;
printTree(bst->getRoot());
bst->remove(4);
cout << endl << "ノード 4 を削除後、二分木は\n" << endl;
printTree(bst->getRoot());
// メモリを解放
delete bst;
return 0;
}

View File

@@ -0,0 +1,43 @@
/**
* File: binary_tree.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* ドライバーコード */
int main() {
/* 二分木を初期化 */
// ノードを初期化
TreeNode *n1 = new TreeNode(1);
TreeNode *n2 = new TreeNode(2);
TreeNode *n3 = new TreeNode(3);
TreeNode *n4 = new TreeNode(4);
TreeNode *n5 = new TreeNode(5);
// ノードの参照(ポインタ)を構築
n1->left = n2;
n1->right = n3;
n2->left = n4;
n2->right = n5;
cout << endl << "二分木を初期化\n" << endl;
printTree(n1);
/* ノードの挿入と削除 */
TreeNode *P = new TreeNode(0);
// n1 -> n2の間にードPを挿入
n1->left = P;
P->left = n2;
cout << endl << "ノード P を挿入後\n" << endl;
printTree(n1);
// ードPを削除
n1->left = n2;
delete P; // メモリを解放
cout << endl << "ノード P を削除後\n" << endl;
printTree(n1);
// メモリを解放
freeMemoryTree(n1);
return 0;
}

View File

@@ -0,0 +1,42 @@
/**
* File: binary_tree_bfs.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
/* レベル順走査 */
vector<int> levelOrder(TreeNode *root) {
// キューを初期化、ルートノードを追加
queue<TreeNode *> queue;
queue.push(root);
// 走査順序を保存するリストを初期化
vector<int> vec;
while (!queue.empty()) {
TreeNode *node = queue.front();
queue.pop(); // キューからデキュー
vec.push_back(node->val); // ノード値を保存
if (node->left != nullptr)
queue.push(node->left); // 左の子ノードをエンキュー
if (node->right != nullptr)
queue.push(node->right); // 右の子ノードをエンキュー
}
return vec;
}
/* ドライバーコード */
int main() {
/* 二分木を初期化 */
// 特定の関数を使用して配列を二分木に変換
TreeNode *root = vectorToTree(vector<int>{1, 2, 3, 4, 5, 6, 7});
cout << endl << "二分木を初期化\n" << endl;
printTree(root);
/* レベル順走査 */
vector<int> vec = levelOrder(root);
cout << endl << "レベル順走査のノード順序 = ";
printVector(vec);
return 0;
}

View File

@@ -0,0 +1,69 @@
/**
* File: binary_tree_dfs.cpp
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
#include "../utils/common.hpp"
// 走査順序を保存するリストを初期化
vector<int> vec;
/* 前順走査 */
void preOrder(TreeNode *root) {
if (root == nullptr)
return;
// 訪問優先度:ルートノード -> 左部分木 -> 右部分木
vec.push_back(root->val);
preOrder(root->left);
preOrder(root->right);
}
/* 中順走査 */
void inOrder(TreeNode *root) {
if (root == nullptr)
return;
// 訪問優先度:左部分木 -> ルートノード -> 右部分木
inOrder(root->left);
vec.push_back(root->val);
inOrder(root->right);
}
/* 後順走査 */
void postOrder(TreeNode *root) {
if (root == nullptr)
return;
// 訪問優先度:左部分木 -> 右部分木 -> ルートノード
postOrder(root->left);
postOrder(root->right);
vec.push_back(root->val);
}
/* ドライバーコード */
int main() {
/* 二分木を初期化 */
// 特定の関数を使用して配列を二分木に変換
TreeNode *root = vectorToTree(vector<int>{1, 2, 3, 4, 5, 6, 7});
cout << endl << "二分木を初期化\n" << endl;
printTree(root);
/* 前順走査 */
vec.clear();
preOrder(root);
cout << endl << "前順走査のノード順序 = ";
printVector(vec);
/* 中順走査 */
vec.clear();
inOrder(root);
cout << endl << "中順走査のノード順序 = ";
printVector(vec);
/* 後順走査 */
vec.clear();
postOrder(root);
cout << endl << "後順走査のノード順序 = ";
printVector(vec);
return 0;
}

View File

@@ -0,0 +1,28 @@
/**
* File: common.hpp
* Created Time: 2021-12-19
* Author: krahets (krahets@163.com)
*/
#pragma once
#include <algorithm>
#include <chrono>
#include <deque>
#include <iostream>
#include <list>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "list_node.hpp"
#include "print_utils.hpp"
#include "tree_node.hpp"
#include "vertex.hpp"
using namespace std;

View File

@@ -0,0 +1,42 @@
/**
* File: list_node.hpp
* Created Time: 2021-12-19
* Author: krahets (krahets@163.com)
*/
#pragma once
#include <iostream>
#include <vector>
using namespace std;
/* 連結リストノード */
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {
}
};
/* 配列を連結リストに逆シリアル化する */
ListNode *vecToLinkedList(vector<int> list) {
ListNode *dum = new ListNode(0);
ListNode *head = dum;
for (int val : list) {
head->next = new ListNode(val);
head = head->next;
}
return dum->next;
}
/* 連結リストに割り当てられたメモリを解放する */
void freeMemoryLinkedList(ListNode *cur) {
// メモリを解放
ListNode *pre;
while (cur != nullptr) {
pre = cur;
cur = cur->next;
delete pre;
}
}

View File

@@ -0,0 +1,228 @@
/**
* File: print_utils.hpp
* Created Time: 2021-12-19
* Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com), LoneRanger(836253168@qq.com)
*/
#pragma once
#include "list_node.hpp"
#include "tree_node.hpp"
#include <climits>
#include <iostream>
#include <sstream>
#include <string>
/* ベクター内の要素を検索する */
template <typename T> int vecFind(const vector<T> &vec, T ele) {
int j = INT_MAX;
for (int i = 0; i < vec.size(); i++) {
if (vec[i] == ele) {
j = i;
}
}
return j;
}
/* ベクターを区切り文字で連結する */
template <typename T> string strJoin(const string &delim, const T &vec) {
ostringstream s;
for (const auto &i : vec) {
if (&i != &vec[0]) {
s << delim;
}
s << i;
}
return s.str();
}
/* 文字列をn回繰り返す */
string strRepeat(string str, int n) {
ostringstream os;
for (int i = 0; i < n; i++)
os << str;
return os.str();
}
/* 配列を印刷する */
template <typename T> void printArray(T *arr, int n) {
cout << "[";
for (int i = 0; i < n - 1; i++) {
cout << arr[i] << ", ";
}
if (n >= 1)
cout << arr[n - 1] << "]" << endl;
else
cout << "]" << endl;
}
/* ベクター文字列オブジェクトを取得する */
template <typename T> string getVectorString(vector<T> &list) {
return "[" + strJoin(", ", list) + "]";
}
/* リストを印刷する */
template <typename T> void printVector(vector<T> list) {
cout << getVectorString(list) << '\n';
}
/* 行列を印刷する */
template <typename T> void printVectorMatrix(vector<vector<T>> &matrix) {
cout << "[" << '\n';
for (vector<T> &list : matrix)
cout << " " + getVectorString(list) + "," << '\n';
cout << "]" << '\n';
}
/* 連結リストを印刷する */
void printLinkedList(ListNode *head) {
vector<int> list;
while (head != nullptr) {
list.push_back(head->val);
head = head->next;
}
cout << strJoin(" -> ", list) << '\n';
}
struct Trunk {
Trunk *prev;
string str;
Trunk(Trunk *prev, string str) {
this->prev = prev;
this->str = str;
}
};
void showTrunks(Trunk *p) {
if (p == nullptr) {
return;
}
showTrunks(p->prev);
cout << p->str;
}
/**
* 二分木を印刷する
* この木プリンターはTECHIE DELIGHTから借用しました
* https://www.techiedelight.com/c-program-print-binary-tree/
*/
void printTree(TreeNode *root, Trunk *prev, bool isRight) {
if (root == nullptr) {
return;
}
string prev_str = " ";
Trunk trunk(prev, prev_str);
printTree(root->right, &trunk, true);
if (!prev) {
trunk.str = "———";
} else if (isRight) {
trunk.str = "/———";
prev_str = " |";
} else {
trunk.str = "\\———";
prev->str = prev_str;
}
showTrunks(&trunk);
cout << " " << root->val << endl;
if (prev) {
prev->str = prev_str;
}
trunk.str = " |";
printTree(root->left, &trunk, false);
}
/* 二分木を印刷する */
void printTree(TreeNode *root) {
printTree(root, nullptr, false);
}
/* スタックを印刷する */
template <typename T> void printStack(stack<T> stk) {
// 入力スタックを逆順にする
stack<T> tmp;
while (!stk.empty()) {
tmp.push(stk.top());
stk.pop();
}
// 印刷する文字列を生成
ostringstream s;
bool flag = true;
while (!tmp.empty()) {
if (flag) {
s << tmp.top();
flag = false;
} else
s << ", " << tmp.top();
tmp.pop();
}
cout << "[" + s.str() + "]" << '\n';
}
/* キューを印刷する */
template <typename T> void printQueue(queue<T> queue) {
// 印刷する文字列を生成
ostringstream s;
bool flag = true;
while (!queue.empty()) {
if (flag) {
s << queue.front();
flag = false;
} else
s << ", " << queue.front();
queue.pop();
}
cout << "[" + s.str() + "]" << '\n';
}
/* デックを印刷する */
template <typename T> void printDeque(deque<T> deque) {
// 印刷する文字列を生成
ostringstream s;
bool flag = true;
while (!deque.empty()) {
if (flag) {
s << deque.front();
flag = false;
} else
s << ", " << deque.front();
deque.pop_front();
}
cout << "[" + s.str() + "]" << '\n';
}
/* ハッシュテーブルを印刷する */
// キー値ペアの型を指定するためにテンプレートパラメータTKeyとTValueを定義
template <typename TKey, typename TValue> void printHashMap(unordered_map<TKey, TValue> map) {
for (auto kv : map) {
cout << kv.first << " -> " << kv.second << '\n';
}
}
/* priority_queueコンテナの基礎となるストレージを公開する */
template <typename T, typename S, typename C> S &Container(priority_queue<T, S, C> &pq) {
struct HackedQueue : private priority_queue<T, S, C> {
static S &Container(priority_queue<T, S, C> &pq) {
return pq.*&HackedQueue::c;
}
};
return HackedQueue::Container(pq);
}
/* ヒープ(優先度付きキュー)を印刷する */
template <typename T, typename S, typename C> void printHeap(priority_queue<T, S, C> &heap) {
vector<T> vec = Container(heap);
cout << "ヒープの配列表現:";
printVector(vec);
cout << "ヒープの木表現:" << endl;
TreeNode *root = vectorToTree(vec);
printTree(root);
freeMemoryTree(root);
}

View File

@@ -0,0 +1,84 @@
/**
* File: tree_node.hpp
* Created Time: 2021-12-19
* Author: krahets (krahets@163.com)
*/
#pragma once
#include <limits.h>
#include <vector>
using namespace std;
/* 二分木ノード構造 */
struct TreeNode {
int val{};
int height = 0;
TreeNode *parent{};
TreeNode *left{};
TreeNode *right{};
TreeNode() = default;
explicit TreeNode(int x, TreeNode *parent = nullptr) : val(x), parent(parent) {
}
};
// シリアル化エンコーディング規則については以下を参照:
// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/
// 二分木の配列表現:
// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
// 二分木の連結リスト表現:
// /——— 15
// /——— 7
// /——— 3
// | \——— 6
// | \——— 12
// ——— 1
// \——— 2
// | /——— 9
// \——— 4
// \——— 8
/* 配列を二分木に逆シリアル化する:再帰的 */
TreeNode *vectorToTreeDFS(vector<int> &arr, int i) {
if (i < 0 || i >= arr.size() || arr[i] == INT_MAX) {
return nullptr;
}
TreeNode *root = new TreeNode(arr[i]);
root->left = vectorToTreeDFS(arr, 2 * i + 1);
root->right = vectorToTreeDFS(arr, 2 * i + 2);
return root;
}
/* 配列を二分木に逆シリアル化する */
TreeNode *vectorToTree(vector<int> arr) {
return vectorToTreeDFS(arr, 0);
}
/* 二分木を配列にシリアル化する:再帰的 */
void treeToVecorDFS(TreeNode *root, int i, vector<int> &res) {
if (root == nullptr)
return;
while (i >= res.size()) {
res.push_back(INT_MAX);
}
res[i] = root->val;
treeToVecorDFS(root->left, 2 * i + 1, res);
treeToVecorDFS(root->right, 2 * i + 2, res);
}
/* 二分木を配列にシリアル化する */
vector<int> treeToVecor(TreeNode *root) {
vector<int> res;
treeToVecorDFS(root, 0, res);
return res;
}
/* 二分木に割り当てられたメモリを解放する */
void freeMemoryTree(TreeNode *root) {
if (root == nullptr)
return;
freeMemoryTree(root->left);
freeMemoryTree(root->right);
delete root;
}

View File

@@ -0,0 +1,36 @@
/**
* File: vertex.hpp
* Created Time: 2023-03-02
* Author: krahets (krahets@163.com)
*/
#pragma once
#include <vector>
using namespace std;
/* 頂点クラス */
struct Vertex {
int val;
Vertex(int x) : val(x) {
}
};
/* 値のリストvals を入力し、頂点のリストvets を返す */
vector<Vertex *> valsToVets(vector<int> vals) {
vector<Vertex *> vets;
for (int val : vals) {
vets.push_back(new Vertex(val));
}
return vets;
}
/* 頂点のリストvets を入力し、値のリストvals を返す */
vector<int> vetsToVals(vector<Vertex *> vets) {
vector<int> vals;
for (Vertex *vet : vets) {
vals.push_back(vet->val);
}
return vals;
}

View File

@@ -0,0 +1,105 @@
/**
* File: array.java
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
package chapter_array_and_linkedlist;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
public class array {
/* 要素へのランダムアクセス */
static int randomAccess(int[] nums) {
// 区間 [0, nums.length) からランダムに数を選択
int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length);
// ランダム要素を取得して返す
int randomNum = nums[randomIndex];
return randomNum;
}
/* 配列長の拡張 */
static int[] extend(int[] nums, int enlarge) {
// 拡張された長さの配列を初期化
int[] res = new int[nums.length + enlarge];
// 元の配列のすべての要素を新しい配列にコピー
for (int i = 0; i < nums.length; i++) {
res[i] = nums[i];
}
// 拡張後の新しい配列を返す
return res;
}
/* `index` に要素 num を挿入 */
static void insert(int[] nums, int num, int index) {
// `index` より後のすべての要素を1つ後ろに移動
for (int i = nums.length - 1; i > index; i--) {
nums[i] = nums[i - 1];
}
// index の要素に num を代入
nums[index] = num;
}
/* `index` の要素を削除 */
static void remove(int[] nums, int index) {
// `index` より後のすべての要素を1つ前に移動
for (int i = index; i < nums.length - 1; i++) {
nums[i] = nums[i + 1];
}
}
/* 配列を走査 */
static void traverse(int[] nums) {
int count = 0;
// インデックスによる配列の走査
for (int i = 0; i < nums.length; i++) {
count += nums[i];
}
// 配列要素の走査
for (int num : nums) {
count += num;
}
}
/* 配列内で指定された要素を検索 */
static int find(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
if (nums[i] == target)
return i;
}
return -1;
}
/* ドライバーコード */
public static void main(String[] args) {
/* 配列を初期化 */
int[] arr = new int[5];
System.out.println("配列 arr = " + Arrays.toString(arr));
int[] nums = { 1, 3, 2, 5, 4 };
System.out.println("配列 nums = " + Arrays.toString(nums));
/* ランダムアクセス */
int randomNum = randomAccess(nums);
System.out.println("nums からランダム要素を取得 = " + randomNum);
/* 長さの拡張 */
nums = extend(nums, 3);
System.out.println("配列の長さを8に拡張し、nums = " + Arrays.toString(nums));
/* 要素の挿入 */
insert(nums, 6, 3);
System.out.println("インデックス3に数値6を挿入し、nums = " + Arrays.toString(nums));
/* 要素の削除 */
remove(nums, 2);
System.out.println("インデックス2の要素を削除し、nums = " + Arrays.toString(nums));
/* 配列の走査 */
traverse(nums);
/* 要素の検索 */
int index = find(nums, 3);
System.out.println("nums で要素3を見つけ、インデックス = " + index);
}
}

View File

@@ -0,0 +1,86 @@
/**
* File: linked_list.java
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
package chapter_array_and_linkedlist;
import utils.*;
public class linked_list {
/* 連結リストでノード n0 の後にノード P を挿入 */
static void insert(ListNode n0, ListNode P) {
ListNode n1 = n0.next;
P.next = n1;
n0.next = P;
}
/* 連結リストでノード n0 の後の最初のノードを削除 */
static void remove(ListNode n0) {
if (n0.next == null)
return;
// n0 -> P -> n1
ListNode P = n0.next;
ListNode n1 = P.next;
n0.next = n1;
}
/* 連結リストの `index` のノードにアクセス */
static ListNode access(ListNode head, int index) {
for (int i = 0; i < index; i++) {
if (head == null)
return null;
head = head.next;
}
return head;
}
/* 連結リストで値 target を持つ最初のノードを検索 */
static int find(ListNode head, int target) {
int index = 0;
while (head != null) {
if (head.val == target)
return index;
head = head.next;
index++;
}
return -1;
}
/* ドライバーコード */
public static void main(String[] args) {
/* 連結リストの初期化 */
// 各ノードを初期化
ListNode n0 = new ListNode(1);
ListNode n1 = new ListNode(3);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(5);
ListNode n4 = new ListNode(4);
// ノード間の参照を構築
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
System.out.println("初期化された連結リストは");
PrintUtil.printLinkedList(n0);
/* ノードの挿入 */
insert(n0, new ListNode(0));
System.out.println("ノード挿入後の連結リストは");
PrintUtil.printLinkedList(n0);
/* ノードの削除 */
remove(n0);
System.out.println("ノード削除後の連結リストは");
PrintUtil.printLinkedList(n0);
/* ノードへのアクセス */
ListNode node = access(n0, 3);
System.out.println("連結リストのインデックス3のードの値 = " + node.val);
/* ノードの検索 */
int index = find(n0, 2);
System.out.println("連結リストで値2を持つードのインデックス = " + index);
}
}

View File

@@ -0,0 +1,66 @@
/**
* File: list.java
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
package chapter_array_and_linkedlist;
import java.util.*;
public class list {
public static void main(String[] args) {
/* リストの初期化 */
// 配列の要素型は Integer[]、int のラッパークラス
Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 };
List<Integer> nums = new ArrayList<>(Arrays.asList(numbers));
System.out.println("リスト nums = " + nums);
/* 要素へのアクセス */
int num = nums.get(1);
System.out.println("インデックス1の要素にアクセス、取得した num = " + num);
/* 要素の更新 */
nums.set(1, 0);
System.out.println("インデックス1の要素を0に更新し、nums = " + nums);
/* リストのクリア */
nums.clear();
System.out.println("リストをクリアした後、nums = " + nums);
/* 末尾に要素を追加 */
nums.add(1);
nums.add(3);
nums.add(2);
nums.add(5);
nums.add(4);
System.out.println("要素を追加した後、nums = " + nums);
/* 中間に要素を挿入 */
nums.add(3, 6);
System.out.println("インデックス3に数値6を挿入し、nums = " + nums);
/* 要素の削除 */
nums.remove(3);
System.out.println("インデックス3の要素を削除し、nums = " + nums);
/* インデックスによるリストの走査 */
int count = 0;
for (int i = 0; i < nums.size(); i++) {
count += nums.get(i);
}
/* リスト要素の走査 */
for (int x : nums) {
count += x;
}
/* 2つのリストの連結 */
List<Integer> nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 }));
nums.addAll(nums1);
System.out.println("リスト nums1 を nums に連結し、nums = " + nums);
/* リストのソート */
Collections.sort(nums);
System.out.println("リストをソートした後、nums = " + nums);
}
}

View File

@@ -0,0 +1,147 @@
/**
* File: my_list.java
* Created Time: 2022-11-25
* Author: krahets (krahets@163.com)
*/
package chapter_array_and_linkedlist;
import java.util.*;
/* リストクラス */
class MyList {
private int[] arr; // 配列(リスト要素を格納)
private int capacity = 10; // リスト容量
private int size = 0; // リスト長(現在の要素数)
private int extendRatio = 2; // リストの各拡張倍率
/* コンストラクタ */
public MyList() {
arr = new int[capacity];
}
/* リスト長を取得(現在の要素数) */
public int size() {
return size;
}
/* リスト容量を取得 */
public int capacity() {
return capacity;
}
/* 要素へのアクセス */
public int get(int index) {
// インデックスが範囲外の場合、以下のように例外をスロー
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("インデックスが範囲外です");
return arr[index];
}
/* 要素の更新 */
public void set(int index, int num) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("インデックスが範囲外です");
arr[index] = num;
}
/* 末尾に要素を追加 */
public void add(int num) {
// 要素数が容量を超える場合、拡張メカニズムを実行
if (size == capacity())
extendCapacity();
arr[size] = num;
// 要素数を更新
size++;
}
/* 中間に要素を挿入 */
public void insert(int index, int num) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("インデックスが範囲外です");
// 要素数が容量を超える場合、拡張メカニズムを実行
if (size == capacity())
extendCapacity();
// `index` より後のすべての要素を1つ後ろに移動
for (int j = size - 1; j >= index; j--) {
arr[j + 1] = arr[j];
}
arr[index] = num;
// 要素数を更新
size++;
}
/* 要素の削除 */
public int remove(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("インデックスが範囲外です");
int num = arr[index];
// `index` より後のすべての要素を1つ前に移動
for (int j = index; j < size - 1; j++) {
arr[j] = arr[j + 1];
}
// 要素数を更新
size--;
// 削除された要素を返す
return num;
}
/* リストを拡張 */
public void extendCapacity() {
// 元の配列の長さを extendRatio 倍した新しい配列を作成し、元の配列を新しい配列にコピー
arr = Arrays.copyOf(arr, capacity() * extendRatio);
// リスト容量を更新
capacity = arr.length;
}
/* リストを配列に変換 */
public int[] toArray() {
int size = size();
// 有効な長さ範囲内の要素のみを変換
int[] arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = get(i);
}
return arr;
}
}
public class my_list {
/* ドライバーコード */
public static void main(String[] args) {
/* リストの初期化 */
MyList nums = new MyList();
/* 末尾に要素を追加 */
nums.add(1);
nums.add(3);
nums.add(2);
nums.add(5);
nums.add(4);
System.out.println("リスト nums = " + Arrays.toString(nums.toArray()) +
", 容量 = " + nums.capacity() + ", 長さ = " + nums.size());
/* 中間に要素を挿入 */
nums.insert(3, 6);
System.out.println("インデックス3に数値6を挿入し、nums = " + Arrays.toString(nums.toArray()));
/* 要素の削除 */
nums.remove(3);
System.out.println("インデックス3の要素を削除し、nums = " + Arrays.toString(nums.toArray()));
/* 要素へのアクセス */
int num = nums.get(1);
System.out.println("インデックス1の要素にアクセス、取得した num = " + num);
/* 要素の更新 */
nums.set(1, 0);
System.out.println("インデックス1の要素を0に更新し、nums = " + Arrays.toString(nums.toArray()));
/* 拡張メカニズムのテスト */
for (int i = 0; i < 10; i++) {
// i = 5 の時、リスト長がリスト容量を超え、この時点で拡張メカニズムが実行される
nums.add(i);
}
System.out.println("拡張後、リスト nums = " + Arrays.toString(nums.toArray()) +
", 容量 = " + nums.capacity() + ", 長さ = " + nums.size());
}
}

View File

@@ -0,0 +1,77 @@
/**
* File: n_queens.java
* Created Time: 2023-05-04
* Author: krahets (krahets@163.com)
*/
package chapter_backtracking;
import java.util.*;
public class n_queens {
/* バックトラッキングアルゴリズムn クイーン */
public static void backtrack(int row, int n, List<List<String>> state, List<List<List<String>>> res,
boolean[] cols, boolean[] diags1, boolean[] diags2) {
// すべての行が配置されたら、解を記録
if (row == n) {
List<List<String>> copyState = new ArrayList<>();
for (List<String> sRow : state) {
copyState.add(new ArrayList<>(sRow));
}
res.add(copyState);
return;
}
// すべての列を走査
for (int col = 0; col < n; col++) {
// セルに対応する主対角線と副対角線を計算
int diag1 = row - col + n - 1;
int diag2 = row + col;
// 剪定:セルの列、主対角線、副対角線にクイーンを配置することを許可しない
if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
// 試行:セルにクイーンを配置
state.get(row).set(col, "Q");
cols[col] = diags1[diag1] = diags2[diag2] = true;
// 次の行を配置
backtrack(row + 1, n, state, res, cols, diags1, diags2);
// 回退:セルを空のスポットに復元
state.get(row).set(col, "#");
cols[col] = diags1[diag1] = diags2[diag2] = false;
}
}
}
/* n クイーンを解く */
public static List<List<List<String>>> nQueens(int n) {
// n*n サイズのチェスボードを初期化、'Q' はクイーンを表し、'#' は空のスポットを表す
List<List<String>> state = new ArrayList<>();
for (int i = 0; i < n; i++) {
List<String> row = new ArrayList<>();
for (int j = 0; j < n; j++) {
row.add("#");
}
state.add(row);
}
boolean[] cols = new boolean[n]; // クイーンのある列を記録
boolean[] diags1 = new boolean[2 * n - 1]; // クイーンのある主対角線を記録
boolean[] diags2 = new boolean[2 * n - 1]; // クイーンのある副対角線を記録
List<List<List<String>>> res = new ArrayList<>();
backtrack(0, n, state, res, cols, diags1, diags2);
return res;
}
public static void main(String[] args) {
int n = 4;
List<List<List<String>>> res = nQueens(n);
System.out.println("チェスボードの次元を " + n + " として入力");
System.out.println("クイーン配置解の総数 = " + res.size());
for (List<List<String>> state : res) {
System.out.println("--------------------");
for (List<String> row : state) {
System.out.println(row);
}
}
}
}

View File

@@ -0,0 +1,51 @@
/**
* File: permutations_i.java
* Created Time: 2023-04-24
* Author: krahets (krahets@163.com)
*/
package chapter_backtracking;
import java.util.*;
public class permutations_i {
/* バックトラッキングアルゴリズム:順列 I */
public static void backtrack(List<Integer> state, int[] choices, boolean[] selected, List<List<Integer>> res) {
// 状態の長さが要素数と等しくなったら、解を記録
if (state.size() == choices.length) {
res.add(new ArrayList<Integer>(state));
return;
}
// すべての選択肢を走査
for (int i = 0; i < choices.length; i++) {
int choice = choices[i];
// 剪定:要素の重複選択を許可しない
if (!selected[i]) {
// 試行:選択を行い、状態を更新
selected[i] = true;
state.add(choice);
// 次のラウンドの選択に進む
backtrack(state, choices, selected, res);
// 回退:選択を取り消し、前の状態に復元
selected[i] = false;
state.remove(state.size() - 1);
}
}
}
/* 順列 I */
static List<List<Integer>> permutationsI(int[] nums) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
backtrack(new ArrayList<Integer>(), nums, new boolean[nums.length], res);
return res;
}
public static void main(String[] args) {
int[] nums = { 1, 2, 3 };
List<List<Integer>> res = permutationsI(nums);
System.out.println("入力配列 nums = " + Arrays.toString(nums));
System.out.println("すべての順列 res = " + res);
}
}

View File

@@ -0,0 +1,53 @@
/**
* File: permutations_ii.java
* Created Time: 2023-04-24
* Author: krahets (krahets@163.com)
*/
package chapter_backtracking;
import java.util.*;
public class permutations_ii {
/* バックトラッキングアルゴリズム:順列 II */
static void backtrack(List<Integer> state, int[] choices, boolean[] selected, List<List<Integer>> res) {
// 状態の長さが要素数と等しくなったら、解を記録
if (state.size() == choices.length) {
res.add(new ArrayList<Integer>(state));
return;
}
// すべての選択肢を走査
Set<Integer> duplicated = new HashSet<Integer>();
for (int i = 0; i < choices.length; i++) {
int choice = choices[i];
// 剪定:要素の重複選択を許可せず、等しい要素の重複選択も許可しない
if (!selected[i] && !duplicated.contains(choice)) {
// 試行:選択を行い、状態を更新
duplicated.add(choice); // 選択された要素値を記録
selected[i] = true;
state.add(choice);
// 次のラウンドの選択に進む
backtrack(state, choices, selected, res);
// 回退:選択を取り消し、前の状態に復元
selected[i] = false;
state.remove(state.size() - 1);
}
}
}
/* 順列 II */
static List<List<Integer>> permutationsII(int[] nums) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
backtrack(new ArrayList<Integer>(), nums, new boolean[nums.length], res);
return res;
}
public static void main(String[] args) {
int[] nums = { 1, 2, 2 };
List<List<Integer>> res = permutationsII(nums);
System.out.println("入力配列 nums = " + Arrays.toString(nums));
System.out.println("すべての順列 res = " + res);
}
}

View File

@@ -0,0 +1,44 @@
/**
* File: preorder_traversal_i_compact.java
* Created Time: 2023-04-16
* Author: krahets (krahets@163.com)
*/
package chapter_backtracking;
import utils.*;
import java.util.*;
public class preorder_traversal_i_compact {
static List<TreeNode> res;
/* 前順走査:例1 */
static void preOrder(TreeNode root) {
if (root == null) {
return;
}
if (root.val == 7) {
// 解を記録
res.add(root);
}
preOrder(root.left);
preOrder(root.right);
}
public static void main(String[] args) {
TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7));
System.out.println("\n二分木を初期化");
PrintUtil.printTree(root);
// 前順走査
res = new ArrayList<>();
preOrder(root);
System.out.println("\n値7のードをすべて出力");
List<Integer> vals = new ArrayList<>();
for (TreeNode node : res) {
vals.add(node.val);
}
System.out.println(vals);
}
}

Some files were not shown because too many files have changed in this diff Show More