Files
hello-algo/ja/search.json
krahets df28e1c562 deploy
2026-10-30 02:14:22 +08:00

1 line
2.4 MiB
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"chapter_appendix/","level":1,"title":"第 16 章   付録","text":"","path":["第 16 章   付録"],"tags":[]},{"location":"chapter_appendix/#_1","level":2,"title":"この章の内容","text":"<ul> <li>16.1   プログラミング環境のインストール</li> <li>16.2   一緒に本書を作りましょう</li> <li>16.3   用語集</li> </ul>","path":["第 16 章   付録"],"tags":[]},{"location":"chapter_appendix/contribution/","level":1,"title":"16.2   一緒に本書を作りましょう","text":"<p>著者の力が及ばず、本書には見落としや誤りがあるかもしれません。どうかご了承ください。誤字脱字、リンク切れ、内容の欠落、曖昧な表現、わかりにくい説明、不適切な文章構成などに気づいたら、修正にご協力ください。読者の皆さんに、よりよい学習教材を届けることにつながります。</p> <p>オープンソースコミュニティへの惜しみない貢献に感謝し、すべての執筆協力者の GitHub ID を、原書のリポジトリ、Web 版、および既存の PDF 版のトップページに掲載します。</p> <p>オープンソースの魅力</p> <p>紙の書籍は次の印刷までに長い時間がかかることが多く、内容の更新が容易ではありません。</p> <p>一方、オープンソースである本書なら、数日、場合によっては数時間で内容を更新できます。</p>","path":["第 16 章   付録","16.2   一緒に本書を作りましょう"],"tags":[]},{"location":"chapter_appendix/contribution/#1","level":3,"title":"1.   内容の小さな修正","text":"<p>図 16-3 のように、各ページの右上に「編集アイコン」があります。次の手順で文章やコードを修正できます。</p> <ol> <li>「編集アイコン」をクリックします。「このリポジトリを Fork する必要がある」という案内が表示されたら、操作に同意してください。</li> <li>Markdown ソースファイルを編集し、内容が正しいか確認します。書式もできるだけ統一してください。</li> <li>ページ下部に変更内容の説明を入力し、「Propose file change」ボタンをクリックします。移動先のページで「Create pull request」ボタンをクリックすると、プルリクエストを作成できます。</li> </ol> <p></p> <p> 図 16-3   ページの編集ボタン </p> <p>画像は直接編集できません。新しい Issue を作成するか、コメントで問題をお知らせください。できるだけ早く描き直して差し替えます。</p>","path":["第 16 章   付録","16.2   一緒に本書を作りましょう"],"tags":[]},{"location":"chapter_appendix/contribution/#2","level":3,"title":"2.   新しい内容の作成","text":"<p>コードをほかのプログラミング言語に移植したり、記事の内容を充実させたりするなど、このオープンソースプロジェクトへの参加に関心がある方は、次の Pull Request の手順に従ってください。</p> <ol> <li>GitHub にログインし、本書のコードリポジトリを自分のアカウントに Fork します。</li> <li>Fork したリポジトリのページを開き、<code>git clone</code> コマンドでローカルにクローンします。</li> <li>ローカルで内容を作成し、十分なテストを行ってコードの正しさを確認します。</li> <li>ローカルの変更を Commit し、リモートリポジトリに Push します。</li> <li>リポジトリのページを再読み込みし、「Create pull request」ボタンをクリックすると、プルリクエストを作成できます。</li> </ol>","path":["第 16 章   付録","16.2   一緒に本書を作りましょう"],"tags":[]},{"location":"chapter_appendix/contribution/#3-docker","level":3,"title":"3.   Docker での起動","text":"<p><code>hello-algo</code> のルートディレクトリで次の Docker スクリプトを実行すると、<code>http://localhost:8000/ja/</code> から本プロジェクトの日本語版にアクセスできます。</p> <pre><code>docker-compose up -d\n</code></pre> <p>次のコマンドで起動した環境を削除できます。</p> <pre><code>docker-compose down\n</code></pre>","path":["第 16 章   付録","16.2   一緒に本書を作りましょう"],"tags":[]},{"location":"chapter_appendix/installation/","level":1,"title":"16.1   プログラミング環境のインストール","text":"","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#1611-ide","level":2,"title":"16.1.1   IDE のインストール","text":"<p>ローカルの統合開発環境(IDE)には、オープンソースで軽量な VS Code をおすすめします。VS Code 公式サイトにアクセスし、OS に合ったバージョンをダウンロードしてインストールしてください。</p> <p></p> <p> 図 16-1   公式サイトから VS Code をダウンロード </p> <p>VS Code には豊富な拡張機能があり、多くのプログラミング言語の実行とデバッグに対応しています。たとえば Python では、「Python Extension Pack」をインストールすると、Python コードをデバッグできます。インストール手順を図 16-2 に示します。</p> <p></p> <p> 図 16-2   VS Code の拡張機能をインストール </p>","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#1612","level":2,"title":"16.1.2   言語環境のインストール","text":"","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#1-python","level":3,"title":"1.   Python 環境","text":"<ol> <li>Miniconda3 をダウンロードしてインストールします。Python 3.10 以降が必要です。</li> <li>VS Code の拡張機能マーケットプレイスで <code>python</code> を検索し、Python Extension Pack をインストールします。</li> <li>(任意)コマンドラインで <code>pip install black</code> を実行し、コード整形ツールをインストールします。</li> </ol>","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#2-cc","level":3,"title":"2.   C/C++ 環境","text":"<ol> <li>Windows では MinGW のインストールが必要です(設定ガイド)。macOS には Clang が付属しているため、インストールは不要です。</li> <li>VS Code の拡張機能マーケットプレイスで <code>c++</code> を検索し、C/C++ Extension Pack をインストールします。</li> <li>(任意)Settings ページを開き、コード整形オプション <code>Clang_format_fallback Style</code> を検索して、<code>{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }</code> に設定します。</li> </ol>","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#3-java","level":3,"title":"3.   Java 環境","text":"<ol> <li>OpenJDK をダウンロードしてインストールします(JDK 9 より新しいバージョンが必要です)。</li> <li>VS Code の拡張機能マーケットプレイスで <code>java</code> を検索し、Extension Pack for Java をインストールします。</li> </ol>","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#4-c","level":3,"title":"4.   C# 環境","text":"<ol> <li>.NET 8.0 をダウンロードしてインストールします。</li> <li>VS Code の拡張機能マーケットプレイスで <code>C# Dev Kit</code> を検索し、C# Dev Kit をインストールします(設定ガイド)。</li> <li>Visual Studio も使用できます(インストールガイド)。</li> </ol>","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#5-go","level":3,"title":"5.   Go 環境","text":"<ol> <li>go をダウンロードしてインストールします。</li> <li>VS Code の拡張機能マーケットプレイスで <code>go</code> を検索し、Go をインストールします。</li> <li>ショートカットキー <code>Ctrl + Shift + P</code> でコマンドパレットを開き、go と入力して <code>Go: Install/Update Tools</code> を選択します。すべての項目を選んでインストールします。</li> </ol>","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#6-swift","level":3,"title":"6.   Swift 環境","text":"<ol> <li>Swift をダウンロードしてインストールします。</li> <li>VS Code の拡張機能マーケットプレイスで <code>swift</code> を検索し、Swift for Visual Studio Code をインストールします。</li> </ol>","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#7-javascript","level":3,"title":"7.   JavaScript 環境","text":"<ol> <li>Node.js をダウンロードしてインストールします。</li> <li>(任意)VS Code の拡張機能マーケットプレイスで <code>Prettier</code> を検索し、コード整形ツールをインストールします。</li> </ol>","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#8-typescript","level":3,"title":"8.   TypeScript 環境","text":"<ol> <li>JavaScript 環境と同じ手順でインストールします。</li> <li>TypeScript Execute (tsx) をインストールします。</li> <li>VS Code の拡張機能マーケットプレイスで <code>typescript</code> を検索し、Pretty TypeScript Errors をインストールします。</li> </ol>","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#9-dart","level":3,"title":"9.   Dart 環境","text":"<ol> <li>Dart をダウンロードしてインストールします。</li> <li>VS Code の拡張機能マーケットプレイスで <code>dart</code> を検索し、Dart をインストールします。</li> </ol>","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/installation/#10-rust","level":3,"title":"10.   Rust 環境","text":"<ol> <li>Rust をダウンロードしてインストールします。</li> <li>VS Code の拡張機能マーケットプレイスで <code>rust</code> を検索し、rust-analyzer をインストールします。</li> </ol>","path":["第 16 章   付録","16.1   プログラミング環境のインストール"],"tags":[]},{"location":"chapter_appendix/terminology/","level":1,"title":"16.3   用語集","text":"<p>表 16-1 に、本書に登場する重要な用語を示します。英語の文献を読む際に役立つよう、各用語の英語名も覚えておくことをおすすめします。</p> <p> 表 16-1   データ構造とアルゴリズムの重要用語 </p> English 日本語 algorithm アルゴリズム data structure データ構造 code コード file ファイル function 関数 method メソッド variable 変数 asymptotic complexity analysis 漸近計算量解析 time complexity 時間計算量 space complexity 空間計算量 loop ループ iteration 反復 recursion 再帰 tail recursion 末尾再帰 recursion tree 再帰木 big-\\(O\\) notation ビッグ \\(O\\) 記法 asymptotic upper bound 漸近上界 sign-magnitude 符号絶対値表現 1’s complement 1 の補数表現 2’s complement 2 の補数表現 array 配列 index インデックス linked list 連結リスト linked list node, list node 連結リストのノード head node 先頭ノード tail node 末尾ノード list リスト dynamic array 動的配列 hard disk ハードディスク random-access memory (RAM) メモリ cache memory キャッシュ cache miss キャッシュミス cache hit rate キャッシュヒット率 stack スタック top of the stack スタックの先頭 bottom of the stack スタックの底 queue キュー double-ended queue 両端キュー front of the queue キューの先頭 rear of the queue キューの末尾 hash table ハッシュテーブル hash set ハッシュ集合 bucket バケット hash function ハッシュ関数 hash collision ハッシュ衝突 load factor 負荷率 separate chaining チェイン法 open addressing オープンアドレス法 linear probing 線形探索法 lazy deletion 遅延削除 binary tree 二分木 tree node 木のノード left-child node 左の子ノード right-child node 右の子ノード parent node 親ノード left subtree 左部分木 right subtree 右部分木 root node 根ノード leaf node 葉ノード edge 辺 level レベル degree 次数 height 高さ depth 深さ perfect binary tree パーフェクト二分木 complete binary tree 完全二分木 full binary tree 全二分木 balanced binary tree 平衡二分木 binary search tree 二分探索木 AVL tree AVL 木 red-black tree 赤黒木 level-order traversal レベル順走査 breadth-first traversal 幅優先走査 depth-first traversal 深さ優先走査 pre-order traversal 行きがけ順走査 in-order traversal 通りがけ順走査 post-order traversal 帰りがけ順走査 balanced binary search tree 平衡二分探索木 balance factor 平衡係数 heap ヒープ max heap 最大ヒープ min heap 最小ヒープ priority queue 優先度付きキュー heapify ヒープ化 top-\\(k\\) problem Top-\\(k\\) 問題 graph グラフ vertex 頂点 undirected graph 無向グラフ directed graph 有向グラフ connected graph 連結グラフ disconnected graph 非連結グラフ weighted graph 重み付きグラフ adjacency 隣接 path 経路 in-degree 入次数 out-degree 出次数 adjacency matrix 隣接行列 adjacency list 隣接リスト breadth-first search 幅優先探索 depth-first search 深さ優先探索 binary search 二分探索 searching algorithm 探索アルゴリズム sorting algorithm ソートアルゴリズム selection sort 選択ソート bubble sort バブルソート insertion sort 挿入ソート quick sort クイックソート merge sort マージソート heap sort ヒープソート bucket sort バケットソート counting sort カウントソート radix sort 基数ソート divide and conquer 分割統治 hanota problem ハノイの塔の問題 backtracking algorithm バックトラッキングアルゴリズム constraint 制約 solution 解 state 状態 pruning 枝刈り permutations problem 順列列挙問題 subset-sum problem 部分集合和問題 \\(n\\)-queens problem \\(n\\) クイーン問題 dynamic programming 動的計画法 initial state 初期状態 state-transition equation 状態遷移式 knapsack problem ナップサック問題 edit distance problem 編集距離問題 greedy algorithm 貪欲アルゴリズム","path":["第 16 章   付録","16.3   用語集"],"tags":[]},{"location":"chapter_array_and_linkedlist/","level":1,"title":"第 4 章   配列と連結リスト","text":"<p>概要</p> <p>データ構造の世界は、分厚いレンガの壁のようです。</p> <p>配列のレンガは整然と並び、互いにぴったり接しています。連結リストのレンガはあちこちに散らばり、それらを結ぶつるがレンガの隙間を自由に行き交います。</p>","path":["第 4 章   配列と連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/#_1","level":2,"title":"この章の内容","text":"<ul> <li>4.1   配列</li> <li>4.2   連結リスト</li> <li>4.3   リスト</li> <li>4.4   メモリとキャッシュ *</li> <li>4.5   まとめ</li> <li>4.6   練習問題</li> </ul>","path":["第 4 章   配列と連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/","level":1,"title":"4.1   配列","text":"<p>配列(array)は、同じ型の要素を連続したメモリ領域に格納する線形データ構造です。配列内での要素の位置を、その要素のインデックス(index)と呼びます。図 4-1 に、配列の主な概念と格納方法を示します。</p> <p></p> <p> 図 4-1   配列の定義と格納方法 </p>","path":["第 4 章   配列と連結リスト","4.1   配列"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#411","level":2,"title":"4.1.1   配列の主な操作","text":"","path":["第 4 章   配列と連結リスト","4.1   配列"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#1","level":3,"title":"1.   配列の初期化","text":"<p>配列の初期化には、初期値を指定しない方法と、初期値を指定する方法があり、必要に応じて選べます。初期値を指定しない場合、多くのプログラミング言語では要素が \\(0\\) で初期化されます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py<pre><code># 配列を初期化\narr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ]\nnums: list[int] = [1, 3, 2, 5, 4]\n</code></pre> array.cpp<pre><code>/* 配列を初期化 */\n// スタック上に格納\nint arr[5] = {};\nint nums[5] = { 1, 3, 2, 5, 4 };\n// ヒープ上に格納(領域を手動で解放する必要がある)\nint* arr1 = new int[5] {};\nint* nums1 = new int[5] { 1, 3, 2, 5, 4 };\n</code></pre> array.java<pre><code>/* 配列を初期化 */\nint[] arr = new int[5]; // { 0, 0, 0, 0, 0 }\nint[] nums = { 1, 3, 2, 5, 4 };\n</code></pre> array.cs<pre><code>/* 配列を初期化 */\nint[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ]\nint[] nums = [1, 3, 2, 5, 4];\n</code></pre> array.go<pre><code>/* 配列を初期化 */\nvar arr [5]int\n// Go では、長さを指定すると([5]int)配列、指定しないと([]int)スライスになる\n// Go の配列はコンパイル時に長さが決まる設計のため、長さには定数しか指定できない\n// 容量を拡張する extend() メソッドを実装しやすくするため、以下ではスライス(Slice)を配列(Array)として扱う\nnums := []int{1, 3, 2, 5, 4}\n</code></pre> array.swift<pre><code>/* 配列を初期化 */\nlet arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]\nlet nums = [1, 3, 2, 5, 4]\n</code></pre> array.js<pre><code>/* 配列を初期化 */\nvar arr = new Array(5).fill(0);\nvar nums = [1, 3, 2, 5, 4];\n</code></pre> array.ts<pre><code>/* 配列を初期化 */\nlet arr: number[] = new Array(5).fill(0);\nlet nums: number[] = [1, 3, 2, 5, 4];\n</code></pre> array.dart<pre><code>/* 配列を初期化 */\nList&lt;int&gt; arr = List.filled(5, 0); // [0, 0, 0, 0, 0]\nList&lt;int&gt; nums = [1, 3, 2, 5, 4];\n</code></pre> array.rs<pre><code>/* 配列を初期化 */\nlet arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0]\nlet slice: &amp;[i32] = &amp;[0; 5];\n// Rust では、長さを指定すると([i32; 5])配列、指定しないと(&amp;[i32])スライスになる\n// Rust の配列はコンパイル時に長さが決まる設計のため、長さには定数しか指定できない\n// Vector は、Rust で通常、動的配列として使う型\n// 容量を拡張する extend() メソッドを実装しやすくするため、以下では vector を配列(array)として扱う\nlet nums: Vec&lt;i32&gt; = vec![1, 3, 2, 5, 4];\n</code></pre> array.c<pre><code>/* 配列を初期化 */\nint arr[5] = { 0 }; // { 0, 0, 0, 0, 0 }\nint nums[5] = { 1, 3, 2, 5, 4 };\n</code></pre> array.kt<pre><code>/* 配列を初期化 */\nvar arr = IntArray(5) // { 0, 0, 0, 0, 0 }\nvar nums = intArrayOf(1, 3, 2, 5, 4)\n</code></pre> array.rb<pre><code># 配列を初期化\narr = Array.new(5, 0)\nnums = [1, 3, 2, 5, 4]\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.1   配列"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#2","level":3,"title":"2.   要素へのアクセス","text":"<p>配列の要素は連続したメモリ領域に格納されるため、各要素のメモリアドレスは簡単に計算できます。配列のメモリアドレス(先頭要素のメモリアドレス)と要素のインデックスがわかれば、図 4-2 の式でその要素のメモリアドレスを求め、直接アクセスできます。</p> <p></p> <p> 図 4-2   配列要素のメモリアドレスの計算 </p> <p>図 4-2 では、配列の先頭要素のインデックスが \\(0\\) になっています。数えるなら \\(1\\) から始める方が自然なので、少し直感に反するかもしれません。しかし、アドレスの計算式から見ると、インデックスは本質的にメモリアドレスのオフセットを表します。先頭要素のアドレスのオフセットは \\(0\\) なので、インデックスが \\(0\\) であるのは理にかなっています。</p> <p>配列の要素へのアクセスは非常に効率的で、任意の要素に \\(O(1)\\) 時間でランダムアクセスできます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py<pre><code>def random_access(nums: list[int]) -&gt; int:\n \"\"\"要素にランダムアクセスする\"\"\"\n # 区間 [0, len(nums)-1] から数値をランダムに 1 つ選ぶ\n random_index = random.randint(0, len(nums) - 1)\n # ランダムに選んだ要素を取得して返す\n random_num = nums[random_index]\n return random_num\n</code></pre> array.cpp<pre><code>/* 要素にランダムアクセスする */\nint randomAccess(int *nums, int size) {\n // 区間 [0, size) から数値をランダムに 1 つ選ぶ\n int randomIndex = rand() % size;\n // ランダムに選んだ要素を取得して返す\n int randomNum = nums[randomIndex];\n return randomNum;\n}\n</code></pre> array.java<pre><code>/* 要素にランダムアクセスする */\nint randomAccess(int[] nums) {\n // 区間 [0, nums.length) から数値をランダムに 1 つ選ぶ\n int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length);\n // ランダムに選んだ要素を取得して返す\n int randomNum = nums[randomIndex];\n return randomNum;\n}\n</code></pre> array.cs<pre><code>/* 要素にランダムアクセスする */\nint RandomAccess(int[] nums) {\n Random random = new();\n // 区間 [0, nums.Length) から数値をランダムに 1 つ選ぶ\n int randomIndex = random.Next(nums.Length);\n // ランダムに選んだ要素を取得して返す\n int randomNum = nums[randomIndex];\n return randomNum;\n}\n</code></pre> array.go<pre><code>/* 要素にランダムアクセスする */\nfunc randomAccess(nums []int) (randomNum int) {\n // 区間 [0, nums.length) から数値をランダムに 1 つ選ぶ\n randomIndex := rand.Intn(len(nums))\n // ランダムに選んだ要素を取得して返す\n randomNum = nums[randomIndex]\n return\n}\n</code></pre> array.swift<pre><code>/* 要素にランダムアクセスする */\nfunc randomAccess(nums: [Int]) -&gt; Int {\n // 区間 [0, nums.count) から数値をランダムに 1 つ選ぶ\n let randomIndex = nums.indices.randomElement()!\n // ランダムに選んだ要素を取得して返す\n let randomNum = nums[randomIndex]\n return randomNum\n}\n</code></pre> array.js<pre><code>/* 要素にランダムアクセスする */\nfunction randomAccess(nums) {\n // 区間 [0, nums.length) から数値をランダムに 1 つ選ぶ\n const random_index = Math.floor(Math.random() * nums.length);\n // ランダムに選んだ要素を取得して返す\n const random_num = nums[random_index];\n return random_num;\n}\n</code></pre> array.ts<pre><code>/* 要素にランダムアクセスする */\nfunction randomAccess(nums: number[]): number {\n // 区間 [0, nums.length) から数値をランダムに 1 つ選ぶ\n const random_index = Math.floor(Math.random() * nums.length);\n // ランダムに選んだ要素を取得して返す\n const random_num = nums[random_index];\n return random_num;\n}\n</code></pre> array.dart<pre><code>/* 要素にランダムアクセスする */\nint randomAccess(List&lt;int&gt; nums) {\n // 区間 [0, nums.length) から数値をランダムに 1 つ選ぶ\n int randomIndex = Random().nextInt(nums.length);\n // ランダムに選んだ要素を取得して返す\n int randomNum = nums[randomIndex];\n return randomNum;\n}\n</code></pre> array.rs<pre><code>/* 要素にランダムアクセスする */\nfn random_access(nums: &amp;[i32]) -&gt; i32 {\n // 区間 [0, nums.len()) から数値をランダムに 1 つ選ぶ\n let random_index = rand::thread_rng().gen_range(0..nums.len());\n // ランダムに選んだ要素を取得して返す\n let random_num = nums[random_index];\n random_num\n}\n</code></pre> array.c<pre><code>/* 要素にランダムアクセスする */\nint randomAccess(int *nums, int size) {\n // 区間 [0, size) から数値をランダムに 1 つ選ぶ\n int randomIndex = rand() % size;\n // ランダムに選んだ要素を取得して返す\n int randomNum = nums[randomIndex];\n return randomNum;\n}\n</code></pre> array.kt<pre><code>/* 要素にランダムアクセスする */\nfun randomAccess(nums: IntArray): Int {\n // 区間 [0, nums.size) から数値をランダムに 1 つ選ぶ\n val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size)\n // ランダムに選んだ要素を取得して返す\n val randomNum = nums[randomIndex]\n return randomNum\n}\n</code></pre> array.rb<pre><code>### 要素にランダムアクセスする ###\ndef random_access(nums)\n # 区間 [0, nums.length) から数値をランダムに 1 つ選ぶ\n random_index = Random.rand(0...nums.length)\n\n # ランダムに選んだ要素を取得して返す\n nums[random_index]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.1   配列"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#3","level":3,"title":"3.   要素の挿入","text":"<p>配列の要素はメモリ内で「隙間なく並んで」おり、その間にほかのデータを入れる余地はありません。図 4-3 のように、配列の途中に要素を挿入するには、その位置以降の要素をすべて一つ後ろへずらしてから、そのインデックスに要素を代入します。</p> <p></p> <p> 図 4-3   配列への要素の挿入例 </p> <p>配列の長さは固定されているため、要素を挿入すると、末尾の要素が必ず「失われる」ことに注意してください。この問題の解決方法は「リスト」の節で説明します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py<pre><code>def insert(nums: list[int], num: int, index: int):\n \"\"\"配列のインデックス index に要素 num を挿入する\"\"\"\n # インデックス index 以降の全要素を 1 つ後ろにずらす\n for i in range(len(nums) - 1, index, -1):\n nums[i] = nums[i - 1]\n # インデックス index の要素に num を代入する\n nums[index] = num\n</code></pre> array.cpp<pre><code>/* 配列のインデックス index に要素 num を挿入する */\nvoid insert(int *nums, int size, int num, int index) {\n // インデックス index 以降の全要素を 1 つ後ろにずらす\n for (int i = size - 1; i &gt; index; i--) {\n nums[i] = nums[i - 1];\n }\n // インデックス index の要素に num を代入する\n nums[index] = num;\n}\n</code></pre> array.java<pre><code>/* 配列のインデックス index に要素 num を挿入する */\nvoid insert(int[] nums, int num, int index) {\n // インデックス index 以降の全要素を 1 つ後ろにずらす\n for (int i = nums.length - 1; i &gt; index; i--) {\n nums[i] = nums[i - 1];\n }\n // インデックス index の要素に num を代入する\n nums[index] = num;\n}\n</code></pre> array.cs<pre><code>/* 配列のインデックス index に要素 num を挿入する */\nvoid Insert(int[] nums, int num, int index) {\n // インデックス index 以降の全要素を 1 つ後ろにずらす\n for (int i = nums.Length - 1; i &gt; index; i--) {\n nums[i] = nums[i - 1];\n }\n // インデックス index の要素に num を代入する\n nums[index] = num;\n}\n</code></pre> array.go<pre><code>/* 配列のインデックス index に要素 num を挿入する */\nfunc insert(nums []int, num int, index int) {\n // インデックス index 以降の全要素を 1 つ後ろにずらす\n for i := len(nums) - 1; i &gt; index; i-- {\n nums[i] = nums[i-1]\n }\n // インデックス index の要素に num を代入する\n nums[index] = num\n}\n</code></pre> array.swift<pre><code>/* 配列のインデックス index に要素 num を挿入する */\nfunc insert(nums: inout [Int], num: Int, index: Int) {\n // インデックス index 以降の全要素を 1 つ後ろにずらす\n for i in stride(from: nums.count - 1, to: index, by: -1) {\n nums[i] = nums[i - 1]\n }\n // インデックス index の要素に num を代入する\n nums[index] = num\n}\n</code></pre> array.js<pre><code>/* 配列のインデックス index に要素 num を挿入する */\nfunction insert(nums, num, index) {\n // インデックス index 以降の全要素を 1 つ後ろにずらす\n for (let i = nums.length - 1; i &gt; index; i--) {\n nums[i] = nums[i - 1];\n }\n // インデックス index の要素に num を代入する\n nums[index] = num;\n}\n</code></pre> array.ts<pre><code>/* 配列のインデックス index に要素 num を挿入する */\nfunction insert(nums: number[], num: number, index: number): void {\n // インデックス index 以降の全要素を 1 つ後ろにずらす\n for (let i = nums.length - 1; i &gt; index; i--) {\n nums[i] = nums[i - 1];\n }\n // インデックス index の要素に num を代入する\n nums[index] = num;\n}\n</code></pre> array.dart<pre><code>/* 配列のインデックス index に要素 _num を挿入する */\nvoid insert(List&lt;int&gt; nums, int _num, int index) {\n // インデックス index 以降の全要素を 1 つ後ろにずらす\n for (var i = nums.length - 1; i &gt; index; i--) {\n nums[i] = nums[i - 1];\n }\n // インデックス index の要素に _num を代入する\n nums[index] = _num;\n}\n</code></pre> array.rs<pre><code>/* 配列のインデックス index に要素 num を挿入する */\nfn insert(nums: &amp;mut [i32], num: i32, index: usize) {\n // インデックス index 以降の全要素を 1 つ後ろにずらす\n for i in (index + 1..nums.len()).rev() {\n nums[i] = nums[i - 1];\n }\n // インデックス index の要素に num を代入する\n nums[index] = num;\n}\n</code></pre> array.c<pre><code>/* 配列のインデックス index に要素 num を挿入する */\nvoid insert(int *nums, int size, int num, int index) {\n // インデックス index 以降の全要素を 1 つ後ろにずらす\n for (int i = size - 1; i &gt; index; i--) {\n nums[i] = nums[i - 1];\n }\n // インデックス index の要素に num を代入する\n nums[index] = num;\n}\n</code></pre> array.kt<pre><code>/* 配列のインデックス index に要素 num を挿入する */\nfun insert(nums: IntArray, num: Int, index: Int) {\n // インデックス index 以降の全要素を 1 つ後ろにずらす\n for (i in nums.size - 1 downTo index + 1) {\n nums[i] = nums[i - 1]\n }\n // インデックス index の要素に num を代入する\n nums[index] = num\n}\n</code></pre> array.rb<pre><code>### 配列のインデックス index に要素 num を挿入する ###\ndef insert(nums, num, index)\n # インデックス index 以降の全要素を 1 つ後ろにずらす\n for i in (nums.length - 1).downto(index + 1)\n nums[i] = nums[i - 1]\n end\n\n # インデックス index の要素に num を代入する\n nums[index] = num\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.1   配列"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#4","level":3,"title":"4.   要素の削除","text":"<p>同様に、図 4-4 のようにインデックス \\(i\\) の要素を削除するには、インデックス \\(i\\) より後ろにある要素をすべて一つ前へずらします。</p> <p></p> <p> 図 4-4   配列からの要素の削除例 </p> <p>削除後、元の末尾の要素は「意味を持たなくなる」ので、わざわざ変更する必要はありません。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py<pre><code>def remove(nums: list[int], index: int):\n \"\"\"インデックス index の要素を削除する\"\"\"\n # インデックス index より後の全要素を 1 つ前にずらす\n for i in range(index, len(nums) - 1):\n nums[i] = nums[i + 1]\n</code></pre> array.cpp<pre><code>/* インデックス index の要素を削除する */\nvoid remove(int *nums, int size, int index) {\n // インデックス index より後の全要素を 1 つ前にずらす\n for (int i = index; i &lt; size - 1; i++) {\n nums[i] = nums[i + 1];\n }\n}\n</code></pre> array.java<pre><code>/* インデックス index の要素を削除する */\nvoid remove(int[] nums, int index) {\n // インデックス index より後の全要素を 1 つ前にずらす\n for (int i = index; i &lt; nums.length - 1; i++) {\n nums[i] = nums[i + 1];\n }\n}\n</code></pre> array.cs<pre><code>/* インデックス index の要素を削除する */\nvoid Remove(int[] nums, int index) {\n // インデックス index より後の全要素を 1 つ前にずらす\n for (int i = index; i &lt; nums.Length - 1; i++) {\n nums[i] = nums[i + 1];\n }\n}\n</code></pre> array.go<pre><code>/* インデックス index の要素を削除する */\nfunc remove(nums []int, index int) {\n // インデックス index より後の全要素を 1 つ前にずらす\n for i := index; i &lt; len(nums)-1; i++ {\n nums[i] = nums[i+1]\n }\n}\n</code></pre> array.swift<pre><code>/* インデックス index の要素を削除する */\nfunc remove(nums: inout [Int], index: Int) {\n // インデックス index より後の全要素を 1 つ前にずらす\n for i in nums.indices.dropFirst(index).dropLast() {\n nums[i] = nums[i + 1]\n }\n}\n</code></pre> array.js<pre><code>/* インデックス index の要素を削除する */\nfunction remove(nums, index) {\n // インデックス index より後の全要素を 1 つ前にずらす\n for (let i = index; i &lt; nums.length - 1; i++) {\n nums[i] = nums[i + 1];\n }\n}\n</code></pre> array.ts<pre><code>/* インデックス index の要素を削除する */\nfunction remove(nums: number[], index: number): void {\n // インデックス index より後の全要素を 1 つ前にずらす\n for (let i = index; i &lt; nums.length - 1; i++) {\n nums[i] = nums[i + 1];\n }\n}\n</code></pre> array.dart<pre><code>/* インデックス index の要素を削除する */\nvoid remove(List&lt;int&gt; nums, int index) {\n // インデックス index より後の全要素を 1 つ前にずらす\n for (var i = index; i &lt; nums.length - 1; i++) {\n nums[i] = nums[i + 1];\n }\n}\n</code></pre> array.rs<pre><code>/* インデックス index の要素を削除する */\nfn remove(nums: &amp;mut [i32], index: usize) {\n // インデックス index より後の全要素を 1 つ前にずらす\n for i in index..nums.len() - 1 {\n nums[i] = nums[i + 1];\n }\n}\n</code></pre> array.c<pre><code>/* インデックス index の要素を削除する */\n// 注意:stdio.h で remove という名前が使われている\nvoid removeItem(int *nums, int size, int index) {\n // インデックス index より後の全要素を 1 つ前にずらす\n for (int i = index; i &lt; size - 1; i++) {\n nums[i] = nums[i + 1];\n }\n}\n</code></pre> array.kt<pre><code>/* インデックス index の要素を削除する */\nfun remove(nums: IntArray, index: Int) {\n // インデックス index より後の全要素を 1 つ前にずらす\n for (i in index..&lt;nums.size - 1) {\n nums[i] = nums[i + 1]\n }\n}\n</code></pre> array.rb<pre><code>### インデックス index の要素を削除する ###\ndef remove(nums, index)\n # インデックス index より後の全要素を 1 つ前にずらす\n for i in index...(nums.length - 1)\n nums[i] = nums[i + 1]\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>配列への挿入と削除には、全体として次の欠点があります。</p> <ul> <li>時間計算量が大きい:配列への挿入と削除の平均時間計算量は、どちらも \\(O(n)\\) です。ここで \\(n\\) は配列の長さです。</li> <li>要素が失われる:配列の長さは変更できないため、要素を挿入すると、配列の長さを超えた要素は失われます。</li> <li>メモリが無駄になる:長めの配列を初期化し、前半だけを使えば、挿入時に失われる末尾の要素を「意味のない」ものにできます。ただし、この方法ではメモリ領域の一部が無駄になります。</li> </ul>","path":["第 4 章   配列と連結リスト","4.1   配列"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#5","level":3,"title":"5.   配列の走査","text":"<p>多くのプログラミング言語では、インデックスを使って配列を走査することも、各要素を直接取り出しながら走査することもできます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py<pre><code>def traverse(nums: list[int]):\n \"\"\"配列を走査する\"\"\"\n count = 0\n # インデックスを使って配列を走査する\n for i in range(len(nums)):\n count += nums[i]\n # 配列の要素を直接走査する\n for num in nums:\n count += num\n # データのインデックスと要素を同時に走査する\n for i, num in enumerate(nums):\n count += nums[i]\n count += num\n</code></pre> array.cpp<pre><code>/* 配列を走査する */\nvoid traverse(int *nums, int size) {\n int count = 0;\n // インデックスを使って配列を走査する\n for (int i = 0; i &lt; size; i++) {\n count += nums[i];\n }\n}\n</code></pre> array.java<pre><code>/* 配列を走査する */\nvoid traverse(int[] nums) {\n int count = 0;\n // インデックスを使って配列を走査する\n for (int i = 0; i &lt; nums.length; i++) {\n count += nums[i];\n }\n // 配列の要素を直接走査する\n for (int num : nums) {\n count += num;\n }\n}\n</code></pre> array.cs<pre><code>/* 配列を走査する */\nvoid Traverse(int[] nums) {\n int count = 0;\n // インデックスを使って配列を走査する\n for (int i = 0; i &lt; nums.Length; i++) {\n count += nums[i];\n }\n // 配列の要素を直接走査する\n foreach (int num in nums) {\n count += num;\n }\n}\n</code></pre> array.go<pre><code>/* 配列を走査する */\nfunc traverse(nums []int) {\n count := 0\n // インデックスを使って配列を走査する\n for i := 0; i &lt; len(nums); i++ {\n count += nums[i]\n }\n count = 0\n // 配列の要素を直接走査する\n for _, num := range nums {\n count += num\n }\n // データのインデックスと要素を同時に走査する\n for i, num := range nums {\n count += nums[i]\n count += num\n }\n}\n</code></pre> array.swift<pre><code>/* 配列を走査する */\nfunc traverse(nums: [Int]) {\n var count = 0\n // インデックスを使って配列を走査する\n for i in nums.indices {\n count += nums[i]\n }\n // 配列の要素を直接走査する\n for num in nums {\n count += num\n }\n // データのインデックスと要素を同時に走査する\n for (i, num) in nums.enumerated() {\n count += nums[i]\n count += num\n }\n}\n</code></pre> array.js<pre><code>/* 配列を走査する */\nfunction traverse(nums) {\n let count = 0;\n // インデックスを使って配列を走査する\n for (let i = 0; i &lt; nums.length; i++) {\n count += nums[i];\n }\n // 配列の要素を直接走査する\n for (const num of nums) {\n count += num;\n }\n}\n</code></pre> array.ts<pre><code>/* 配列を走査する */\nfunction traverse(nums: number[]): void {\n let count = 0;\n // インデックスを使って配列を走査する\n for (let i = 0; i &lt; nums.length; i++) {\n count += nums[i];\n }\n // 配列の要素を直接走査する\n for (const num of nums) {\n count += num;\n }\n}\n</code></pre> array.dart<pre><code>/* 配列の要素を走査する */\nvoid traverse(List&lt;int&gt; nums) {\n int count = 0;\n // インデックスを使って配列を走査する\n for (var i = 0; i &lt; nums.length; i++) {\n count += nums[i];\n }\n // 配列の要素を直接走査する\n for (int _num in nums) {\n count += _num;\n }\n // forEach メソッドで配列を走査する\n nums.forEach((_num) {\n count += _num;\n });\n}\n</code></pre> array.rs<pre><code>/* 配列を走査する */\nfn traverse(nums: &amp;[i32]) {\n let mut _count = 0;\n // インデックスを使って配列を走査する\n for i in 0..nums.len() {\n _count += nums[i];\n }\n // 配列の要素を直接走査する\n _count = 0;\n for &amp;num in nums {\n _count += num;\n }\n}\n</code></pre> array.c<pre><code>/* 配列を走査する */\nvoid traverse(int *nums, int size) {\n int count = 0;\n // インデックスを使って配列を走査する\n for (int i = 0; i &lt; size; i++) {\n count += nums[i];\n }\n}\n</code></pre> array.kt<pre><code>/* 配列を走査する */\nfun traverse(nums: IntArray) {\n var count = 0\n // インデックスを使って配列を走査する\n for (i in nums.indices) {\n count += nums[i]\n }\n // 配列の要素を直接走査する\n for (j in nums) {\n count += j\n }\n}\n</code></pre> array.rb<pre><code>### 配列を走査する ###\ndef traverse(nums)\n count = 0\n\n # インデックスを使って配列を走査する\n for i in 0...nums.length\n count += nums[i]\n end\n\n # 配列の要素を直接走査する\n for num in nums\n count += num\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.1   配列"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#6","level":3,"title":"6.   要素の探索","text":"<p>配列内で指定した要素を探すには、配列を走査し、各要素の値が一致するかを調べます。一致したら、そのインデックスを返します。</p> <p>配列は線形データ構造であるため、この探索を「線形探索」と呼びます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py<pre><code>def find(nums: list[int], target: int) -&gt; int:\n \"\"\"配列から指定した要素を探索する\"\"\"\n for i in range(len(nums)):\n if nums[i] == target:\n return i\n return -1\n</code></pre> array.cpp<pre><code>/* 配列から指定した要素を探索する */\nint find(int *nums, int size, int target) {\n for (int i = 0; i &lt; size; i++) {\n if (nums[i] == target)\n return i;\n }\n return -1;\n}\n</code></pre> array.java<pre><code>/* 配列から指定した要素を探索する */\nint find(int[] nums, int target) {\n for (int i = 0; i &lt; nums.length; i++) {\n if (nums[i] == target)\n return i;\n }\n return -1;\n}\n</code></pre> array.cs<pre><code>/* 配列から指定した要素を探索する */\nint Find(int[] nums, int target) {\n for (int i = 0; i &lt; nums.Length; i++) {\n if (nums[i] == target)\n return i;\n }\n return -1;\n}\n</code></pre> array.go<pre><code>/* 配列から指定した要素を探索する */\nfunc find(nums []int, target int) (index int) {\n index = -1\n for i := 0; i &lt; len(nums); i++ {\n if nums[i] == target {\n index = i\n break\n }\n }\n return\n}\n</code></pre> array.swift<pre><code>/* 配列から指定した要素を探索する */\nfunc find(nums: [Int], target: Int) -&gt; Int {\n for i in nums.indices {\n if nums[i] == target {\n return i\n }\n }\n return -1\n}\n</code></pre> array.js<pre><code>/* 配列から指定した要素を探索する */\nfunction find(nums, target) {\n for (let i = 0; i &lt; nums.length; i++) {\n if (nums[i] === target) return i;\n }\n return -1;\n}\n</code></pre> array.ts<pre><code>/* 配列から指定した要素を探索する */\nfunction find(nums: number[], target: number): number {\n for (let i = 0; i &lt; nums.length; i++) {\n if (nums[i] === target) {\n return i;\n }\n }\n return -1;\n}\n</code></pre> array.dart<pre><code>/* 配列から指定した要素を探索する */\nint find(List&lt;int&gt; nums, int target) {\n for (var i = 0; i &lt; nums.length; i++) {\n if (nums[i] == target) return i;\n }\n return -1;\n}\n</code></pre> array.rs<pre><code>/* 配列から指定した要素を探索する */\nfn find(nums: &amp;[i32], target: i32) -&gt; Option&lt;usize&gt; {\n for i in 0..nums.len() {\n if nums[i] == target {\n return Some(i);\n }\n }\n None\n}\n</code></pre> array.c<pre><code>/* 配列から指定した要素を探索する */\nint find(int *nums, int size, int target) {\n for (int i = 0; i &lt; size; i++) {\n if (nums[i] == target)\n return i;\n }\n return -1;\n}\n</code></pre> array.kt<pre><code>/* 配列から指定した要素を探索する */\nfun find(nums: IntArray, target: Int): Int {\n for (i in nums.indices) {\n if (nums[i] == target)\n return i\n }\n return -1\n}\n</code></pre> array.rb<pre><code>### 配列から指定した要素を探索する ###\ndef find(nums, target)\n for i in 0...nums.length\n return i if nums[i] == target\n end\n\n -1\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.1   配列"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#7","level":3,"title":"7.   配列の容量拡張","text":"<p>複雑なシステム環境では、配列の直後のメモリ領域が空いているとは限らないため、安全に容量を増やすことができません。そのため、多くのプログラミング言語では、配列の長さは変更できません。</p> <p>配列の容量を増やしたい場合は、より大きな配列を新しく作り、元の配列の要素を順にコピーする必要があります。これは \\(O(n)\\) の操作であり、配列が大きいと非常に時間がかかります。コードを次に示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array.py<pre><code>def extend(nums: list[int], enlarge: int) -&gt; list[int]:\n \"\"\"配列の長さを拡張する\"\"\"\n # 拡張後の長さの配列を初期化する\n res = [0] * (len(nums) + enlarge)\n # 元の配列の全要素を新しい配列にコピーする\n for i in range(len(nums)):\n res[i] = nums[i]\n # 拡張後の新しい配列を返す\n return res\n</code></pre> array.cpp<pre><code>/* 配列の長さを拡張する */\nint *extend(int *nums, int size, int enlarge) {\n // 拡張後の長さの配列を初期化する\n int *res = new int[size + enlarge];\n // 元の配列の全要素を新しい配列にコピーする\n for (int i = 0; i &lt; size; i++) {\n res[i] = nums[i];\n }\n // メモリを解放する\n delete[] nums;\n // 拡張後の新しい配列を返す\n return res;\n}\n</code></pre> array.java<pre><code>/* 配列の長さを拡張する */\nint[] extend(int[] nums, int enlarge) {\n // 拡張後の長さの配列を初期化する\n int[] res = new int[nums.length + enlarge];\n // 元の配列の全要素を新しい配列にコピーする\n for (int i = 0; i &lt; nums.length; i++) {\n res[i] = nums[i];\n }\n // 拡張後の新しい配列を返す\n return res;\n}\n</code></pre> array.cs<pre><code>/* 配列の長さを拡張する */\nint[] Extend(int[] nums, int enlarge) {\n // 拡張後の長さの配列を初期化する\n int[] res = new int[nums.Length + enlarge];\n // 元の配列の全要素を新しい配列にコピーする\n for (int i = 0; i &lt; nums.Length; i++) {\n res[i] = nums[i];\n }\n // 拡張後の新しい配列を返す\n return res;\n}\n</code></pre> array.go<pre><code>/* 配列の長さを拡張する */\nfunc extend(nums []int, enlarge int) []int {\n // 拡張後の長さの配列を初期化する\n res := make([]int, len(nums)+enlarge)\n // 元の配列の全要素を新しい配列にコピーする\n for i, num := range nums {\n res[i] = num\n }\n // 拡張後の新しい配列を返す\n return res\n}\n</code></pre> array.swift<pre><code>/* 配列の長さを拡張する */\nfunc extend(nums: [Int], enlarge: Int) -&gt; [Int] {\n // 拡張後の長さの配列を初期化する\n var res = Array(repeating: 0, count: nums.count + enlarge)\n // 元の配列の全要素を新しい配列にコピーする\n for i in nums.indices {\n res[i] = nums[i]\n }\n // 拡張後の新しい配列を返す\n return res\n}\n</code></pre> array.js<pre><code>/* 配列の長さを拡張する */\n// JavaScript の Array は動的配列なので、そのまま拡張できることに注意\n// 学習しやすいように、この関数では Array を長さが変わらない配列として扱う\nfunction extend(nums, enlarge) {\n // 拡張後の長さの配列を初期化する\n const res = new Array(nums.length + enlarge).fill(0);\n // 元の配列の全要素を新しい配列にコピーする\n for (let i = 0; i &lt; nums.length; i++) {\n res[i] = nums[i];\n }\n // 拡張後の新しい配列を返す\n return res;\n}\n</code></pre> array.ts<pre><code>/* 配列の長さを拡張する */\n// TypeScript の Array は動的配列なので、そのまま拡張できることに注意\n// 学習しやすいように、この関数では Array を長さが変わらない配列として扱う\nfunction extend(nums: number[], enlarge: number): number[] {\n // 拡張後の長さの配列を初期化する\n const res = new Array(nums.length + enlarge).fill(0);\n // 元の配列の全要素を新しい配列にコピーする\n for (let i = 0; i &lt; nums.length; i++) {\n res[i] = nums[i];\n }\n // 拡張後の新しい配列を返す\n return res;\n}\n</code></pre> array.dart<pre><code>/* 配列の長さを拡張する */\nList&lt;int&gt; extend(List&lt;int&gt; nums, int enlarge) {\n // 拡張後の長さの配列を初期化する\n List&lt;int&gt; res = List.filled(nums.length + enlarge, 0);\n // 元の配列の全要素を新しい配列にコピーする\n for (var i = 0; i &lt; nums.length; i++) {\n res[i] = nums[i];\n }\n // 拡張後の新しい配列を返す\n return res;\n}\n</code></pre> array.rs<pre><code>/* 配列の長さを拡張する */\nfn extend(nums: &amp;[i32], enlarge: usize) -&gt; Vec&lt;i32&gt; {\n // 拡張後の長さの配列を初期化する\n let mut res: Vec&lt;i32&gt; = vec![0; nums.len() + enlarge];\n // 元の配列の全要素を新しい領域にコピーする\n res[0..nums.len()].copy_from_slice(nums);\n\n // 拡張後の新しい配列を返す\n res\n}\n</code></pre> array.c<pre><code>/* 配列の長さを拡張する */\nint *extend(int *nums, int size, int enlarge) {\n // 拡張後の長さの配列を初期化する\n int *res = (int *)malloc(sizeof(int) * (size + enlarge));\n // 元の配列の全要素を新しい配列にコピーする\n for (int i = 0; i &lt; size; i++) {\n res[i] = nums[i];\n }\n // 拡張した領域を初期化する\n for (int i = size; i &lt; size + enlarge; i++) {\n res[i] = 0;\n }\n // 拡張後の新しい配列を返す\n return res;\n}\n</code></pre> array.kt<pre><code>/* 配列の長さを拡張する */\nfun extend(nums: IntArray, enlarge: Int): IntArray {\n // 拡張後の長さの配列を初期化する\n val res = IntArray(nums.size + enlarge)\n // 元の配列の全要素を新しい配列にコピーする\n for (i in nums.indices) {\n res[i] = nums[i]\n }\n // 拡張後の新しい配列を返す\n return res\n}\n</code></pre> array.rb<pre><code>### 配列の長さを拡張する ###\n# Ruby の Array は動的配列なので、そのまま拡張できることに注意\n# 学習しやすいように、この関数では Array を長さが変わらない配列として扱う\ndef extend(nums, enlarge)\n # 拡張後の長さの配列を初期化する\n res = Array.new(nums.length + enlarge, 0)\n\n # 元の配列の全要素を新しい配列にコピーする\n for i in 0...nums.length\n res[i] = nums[i]\n end\n\n # 拡張後の新しい配列を返す\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.1   配列"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#412","level":2,"title":"4.1.2   配列の利点と制約","text":"<p>配列は連続したメモリ領域に格納され、要素の型も同じです。この性質には豊富な事前情報が含まれており、システムはそれを利用して操作の効率を高められます。</p> <ul> <li>空間効率が高い:データに連続したメモリ領域を割り当てるため、構造を管理する追加のコストが不要です。</li> <li>ランダムアクセスに対応する:任意の要素に \\(O(1)\\) 時間でアクセスできます。</li> <li>キャッシュの局所性を活かせる:配列の要素にアクセスすると、コンピュータはその要素だけでなく周辺のデータもキャッシュに読み込みます。高速なキャッシュにより、その後の操作が速くなります。</li> </ul> <p>連続した領域への格納には、利点だけでなく、次の制約もあります。</p> <ul> <li>挿入と削除の効率が低い:要素が多い場合、挿入や削除の際に大量の要素を移動する必要があります。</li> <li>長さを変更できない:配列の長さは初期化後に固定されます。容量を増やすには全データを新しい配列へコピーする必要があり、大きなコストがかかります。</li> <li>空間が無駄になる:実際に必要な量より大きな配列を確保すると、余った空間は無駄になります。</li> </ul>","path":["第 4 章   配列と連結リスト","4.1   配列"],"tags":[]},{"location":"chapter_array_and_linkedlist/array/#413","level":2,"title":"4.1.3   配列の代表的な用途","text":"<p>配列は基本的で広く使われるデータ構造です。各種アルゴリズムに頻繁に使われるほか、複雑なデータ構造の実装にも使えます。</p> <ul> <li>ランダムアクセス:無作為にサンプルを抽出したい場合は、データを配列に格納し、乱数列を生成して、その値をインデックスに使えばランダムサンプリングができます。</li> <li>ソートと探索:配列はソートや探索のアルゴリズムで最もよく使われるデータ構造です。クイックソート、マージソート、二分探索などは、主に配列上で行います。</li> <li>参照表:要素やその対応関係をすばやく調べたい場合、配列を参照表として使えます。たとえば文字から ASCII コードへの対応を実装するには、文字の ASCII コード値をインデックスとし、配列の対応する位置に要素を格納します。</li> <li>機械学習:ニューラルネットワークでは、ベクトル、行列、テンソル間の線形代数演算を大量に使い、それらのデータは配列として構成されます。配列はニューラルネットワークのプログラミングで最もよく使われるデータ構造です。</li> <li>データ構造の実装:配列はスタック、キュー、ハッシュテーブル、ヒープ、グラフなどの実装に使えます。たとえば、グラフの隣接行列は実際には二次元配列です。</li> </ul>","path":["第 4 章   配列と連結リスト","4.1   配列"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/","level":1,"title":"4.6   練習問題","text":"","path":["第 4 章   配列と連結リスト","4.6   練習問題"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#461","level":2,"title":"4.6.1   知識の確認","text":"","path":["第 4 章   配列と連結リスト","4.6   練習問題"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#1","level":3,"title":"1.   配列と連結リストで要素を見つけるには","text":"<p>配列と単方向連結リストに、どちらも <code>[A, B, C, D, E]</code> が順に保存されています。4 番目の要素 <code>D</code> を読み取ります。</p> <ol> <li>配列では、どのインデックスを直接使えますか。</li> <li>単方向連結リストでは、先頭ノード <code>A</code> から <code>next</code> をたどると、どのノードを順に通りますか。</li> <li>読み取る要素の位置が後ろになるにつれ、各構造で必要な手順の数はどう変わりますか。位置を指定した読み取りを繰り返す用途にはどちらが適していますか。理由も説明してください。</li> </ol> 解答例 <ol> <li> <p>インデックスが 0 から始まるなら、4 番目の要素のインデックスは 3 なので、配列では <code>arr[3]</code> を直接読み取れます。</p> </li> <li> <p>単方向連結リストは先頭からたどる必要があります。訪問経路は <code>A → B → C → D</code> で、<code>next</code> に沿って 3 回進みます。</p> </li> <li> <p>配列は先頭アドレスとインデックスから要素の位置を直接求められるため、位置を指定したアクセスの時間計算量は \\(O(1)\\) です。 単方向連結リストの \\(k\\) 番目のノードにアクセスするには、先頭ノードから <code>next</code> に沿って \\(k-1\\) 回進む必要があり、 最悪の場合は \\(O(n)\\) 時間かかります。</p> <p>ここで比較しているのは位置を指定した読み取りだけであり、連結リストがすべての操作で遅いという意味ではありません。</p> </li> </ol>","path":["第 4 章   配列と連結リスト","4.6   練習問題"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#2","level":3,"title":"2.   配列と連結リストに要素を挿入するには","text":"<p>配列と単方向連結リストに、どちらも <code>A、B、C、D</code> が保存されています。<code>B</code> の後ろに <code>X</code> を挿入します。</p> <ul> <li>配列の容量は 5 で、現在の状態は <code>[A, B, C, D, _]</code> です。</li> <li>連結リストは <code>A → B → C → D</code> で、ノード <code>B</code> を指す参照をすでに取得しています。</li> </ul> <ol> <li>配列では、どの要素を移動する必要がありますか。挿入後の配列を書いてください。</li> <li>連結リストでは、<code>X.next</code> と <code>B.next</code> をどの順番で変更しますか。挿入後の連結リストを書いてください。</li> <li>挿入の効率を比較するとき、「ノード B の参照をすでに取得している」と明記するのはなぜですか。</li> </ol> 解答例 <ol> <li> <p>配列では、まず <code>D</code> を右へ一つ、次に <code>C</code> を右へ一つ移動し、最後に <code>X</code> をインデックス 2 に置きます。 結果は <code>[A, B, X, C, D]</code> です。</p> </li> <li> <p>元の <code>B.next</code> は <code>C</code> を指しています。まず <code>X.next = B.next</code> として <code>X</code> を <code>C</code> につなぎ、 次に <code>B.next = X</code> とします。結果は <code>A → B → X → C → D</code> です。 元の接続を保存せずに先に <code>B.next</code> を上書きすると、<code>C</code> を見つけられなくなる可能性があります。</p> </li> <li> <p><code>B</code> の位置が分かっていれば、連結リストへの挿入は二つの接続を変更するだけなので、\\(O(1)\\) 時間で完了します。 先頭から <code>B</code> を探す必要があれば、その探索自体に \\(O(n)\\) 時間かかる場合があります。</p> </li> </ol>","path":["第 4 章   配列と連結リスト","4.6   練習問題"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#3","level":3,"title":"3.   リストの容量が増える仕組み","text":"<p>配列で実装したリストの現在の内容は <code>[A, B, C]</code>、長さは <code>size = 3</code>、容量は <code>capacity = 4</code> です。 容量が足りなくなったら、新しい配列の容量を元の 2 倍にするものとします。</p> <ol> <li><code>D</code> を追加すると、リストの長さと容量はそれぞれいくつになりますか。容量の拡張は必要ですか。</li> <li>続いて <code>E</code> を追加するとき、容量はいくつになりますか。元の要素を何個コピーする必要がありますか。</li> <li>内部の配列の長さは変えられないのに、リストの容量が増えるように見えるのはなぜですか。</li> </ol> 解答例 <ol> <li> <p><code>D</code> は最後の空き位置に置けます。内容は <code>[A, B, C, D]</code>、 <code>size = 4</code>、<code>capacity = 4</code> となり、容量の拡張は不要です。</p> </li> <li> <p>さらに <code>E</code> を追加するときには空きがないため、容量 8 の新しい配列を作り、 元の <code>A、B、C、D</code> の計 4 要素をコピーしてから <code>E</code> を追加します。 このとき、<code>size = 5</code>、<code>capacity = 8</code> となります。</p> </li> <li> <p>元の配列自体が長くなったわけではありません。リストがより大きな配列を新しく作り、元の要素をコピーし、 内部の格納先を新しい配列に切り替えるため、利用者からは容量が増えたように見えます。</p> </li> </ol>","path":["第 4 章   配列と連結リスト","4.6   練習問題"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#462","level":2,"title":"4.6.2   プログラミング演習","text":"","path":["第 4 章   配列と連結リスト","4.6   練習問題"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#1-1","level":3,"title":"1.   配列で表した大きな整数に 1 を足す","text":"<p>配列 <code>digits</code> には、非負整数の各桁が左から右へ保存されています。例えば <code>[3, 0, 8]</code> は 308 を表します。 0 は <code>[0]</code> で表し、それ以外の入力では先頭の桁は 0 ではありません。</p> <p>十進数の筆算を模して、この整数に 1 を足し、結果を同じ配列の形式で返してください。 <code>digits</code> を直接変更して構いません。先頭に新しい繰り上がりが生じたら、より長い配列を返せます。</p> 解くためのヒント <ol> <li>筆算と同じように、配列の最後の桁から始めます</li> <li>現在の桁が 9 未満なら、1 を足してすぐに返せます</li> <li>現在の桁が 9 なら 0 に変えます。すべての桁が 9 なら、先頭に 1 を補う必要があります</li> </ol> <p>LeetCode</p>","path":["第 4 章   配列と連結リスト","4.6   練習問題"],"tags":[]},{"location":"chapter_array_and_linkedlist/exercises/#2_1","level":3,"title":"2.   単方向連結リストの反転","text":"<p>単方向連結リストの先頭ノード <code>head</code> が与えられます。各ノードは、値と次のノードを指す <code>next</code> を持ちます。</p> <p>反復によってノード間のすべての接続を反転し、反転後の先頭ノードを返してください。 新しい連結リストのノードは作らないでください。</p> 解くためのヒント <ol> <li>まず紙に、つながった三つのノードと二つのポインタ prev、cur を描いてみましょう</li> <li>cur.next を書き換える前に、元の次のノードを必ず nxt に保存します</li> <li>cur.next を反転したら、prev = cur、続いて cur = nxt として、元の連結リストの次のノードを処理します</li> </ol> <p>LeetCode</p>","path":["第 4 章   配列と連結リスト","4.6   練習問題"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/","level":1,"title":"4.2   連結リスト","text":"<p>メモリ空間はすべてのプログラムが利用する共通の資源です。複雑なシステムの実行環境では、空き領域がメモリの各所に散らばっていることがあります。配列には連続したメモリ領域が必要ですが、配列が非常に大きいと、それだけの連続領域を確保できないかもしれません。このようなとき、連結リストの柔軟性が活きてきます。</p> <p>連結リスト(linked list)は、各要素がノードオブジェクトであり、ノード同士が「参照」でつながる線形データ構造です。参照には次のノードのメモリアドレスが記録されており、それを使って現在のノードから次のノードへアクセスできます。</p> <p>この設計により、各ノードをメモリの各所に分散して格納でき、メモリアドレスが連続している必要はありません。</p> <p></p> <p> 図 4-5   連結リストの定義と格納方法 </p> <p>図 4-5 のように、連結リストを構成する単位はノード(node)オブジェクトです。各ノードは、ノードの「値」と次のノードを指す「参照」という二つのデータを持ちます。</p> <ul> <li>最初のノードを「先頭ノード」、最後のノードを「末尾ノード」と呼びます。</li> <li>末尾ノードは「空」を指します。これは Java、C++、Python でそれぞれ <code>null</code>、<code>nullptr</code>、<code>None</code> と表します。</li> <li>C、C++、Go、Rust などのポインタをサポートする言語では、上記の「参照」は「ポインタ」と読み替えます。</li> </ul> <p>次のコードのように、連結リストのノード <code>ListNode</code> は、値に加えて参照(ポインタ)も保存する必要があります。そのため、同じデータ量なら、連結リストは配列より多くのメモリを使います。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code>class ListNode:\n \"\"\"連結リストのノードクラス\"\"\"\n def __init__(self, val: int):\n self.val: int = val # ノードの値\n self.next: ListNode | None = None # 次のノードへの参照\n</code></pre> <pre><code>/* 連結リストのノード構造体 */\nstruct ListNode {\n int val; // ノードの値\n ListNode *next; // 次のノードへのポインタ\n ListNode(int x) : val(x), next(nullptr) {} // コンストラクタ\n};\n</code></pre> <pre><code>/* 連結リストのノードクラス */\nclass ListNode {\n int val; // ノードの値\n ListNode next; // 次のノードへの参照\n ListNode(int x) { val = x; } // コンストラクタ\n}\n</code></pre> <pre><code>/* 連結リストのノードクラス */\nclass ListNode(int x) { //コンストラクタ\n int val = x; // ノードの値\n ListNode? next; // 次のノードへの参照\n}\n</code></pre> <pre><code>/* 連結リストのノード構造体 */\ntype ListNode struct {\n Val int // ノードの値\n Next *ListNode // 次のノードへのポインタ\n}\n\n// NewListNode コンストラクタで、新しい連結リストを作る\nfunc NewListNode(val int) *ListNode {\n return &amp;ListNode{\n Val: val,\n Next: nil,\n }\n}\n</code></pre> <pre><code>/* 連結リストのノードクラス */\nclass ListNode {\n var val: Int // ノードの値\n var next: ListNode? // 次のノードへの参照\n\n init(x: Int) { // コンストラクタ\n val = x\n }\n}\n</code></pre> <pre><code>/* 連結リストのノードクラス */\nclass ListNode {\n constructor(val, next) {\n this.val = (val === undefined ? 0 : val); // ノードの値\n this.next = (next === undefined ? null : next); // 次のノードへの参照\n }\n}\n</code></pre> <pre><code>/* 連結リストのノードクラス */\nclass ListNode {\n val: number;\n next: ListNode | null;\n constructor(val?: number, next?: ListNode | null) {\n this.val = val === undefined ? 0 : val; // ノードの値\n this.next = next === undefined ? null : next; // 次のノードへの参照\n }\n}\n</code></pre> <pre><code>/* 連結リストのノードクラス */\nclass ListNode {\n int val; // ノードの値\n ListNode? next; // 次のノードへの参照\n ListNode(this.val, [this.next]); // コンストラクタ\n}\n</code></pre> <pre><code>use std::rc::Rc;\nuse std::cell::RefCell;\n/* 連結リストのノードクラス */\n#[derive(Debug)]\nstruct ListNode {\n val: i32, // ノードの値\n next: Option&lt;Rc&lt;RefCell&lt;ListNode&gt;&gt;&gt;, // 次のノードへのポインタ\n}\n</code></pre> <pre><code>/* 連結リストのノード構造体 */\ntypedef struct ListNode {\n int val; // ノードの値\n struct ListNode *next; // 次のノードへのポインタ\n} ListNode;\n\n/* コンストラクタ */\nListNode *newListNode(int val) {\n ListNode *node;\n node = (ListNode *) malloc(sizeof(ListNode));\n node-&gt;val = val;\n node-&gt;next = NULL;\n return node;\n}\n</code></pre> <pre><code>/* 連結リストのノードクラス */\n// コンストラクタ\nclass ListNode(x: Int) {\n val _val: Int = x // ノードの値\n var next: ListNode? = null // 次のノードへの参照\n}\n</code></pre> <pre><code># 連結リストのノードクラス\nclass ListNode\n attr_accessor :val # ノードの値\n attr_accessor :next # 次のノードへの参照\n\n def initialize(val=0, next_node=nil)\n @val = val\n @next = next_node\n end\nend\n</code></pre>","path":["第 4 章   配列と連結リスト","4.2   連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#421","level":2,"title":"4.2.1   連結リストの主な操作","text":"","path":["第 4 章   配列と連結リスト","4.2   連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#1","level":3,"title":"1.   連結リストの初期化","text":"<p>連結リストは二つの手順で作ります。まず各ノードオブジェクトを初期化し、次にノード間の参照関係を構築します。初期化後は、先頭ノードから <code>next</code> の参照をたどり、すべてのノードを順に訪問できます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py<pre><code># 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化\n# 各ノードを初期化\nn0 = ListNode(1)\nn1 = ListNode(3)\nn2 = ListNode(2)\nn3 = ListNode(5)\nn4 = ListNode(4)\n# ノード間の参照を設定\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n</code></pre> linked_list.cpp<pre><code>/* 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化 */\n// 各ノードを初期化\nListNode* n0 = new ListNode(1);\nListNode* n1 = new ListNode(3);\nListNode* n2 = new ListNode(2);\nListNode* n3 = new ListNode(5);\nListNode* n4 = new ListNode(4);\n// ノード間の参照を設定\nn0-&gt;next = n1;\nn1-&gt;next = n2;\nn2-&gt;next = n3;\nn3-&gt;next = n4;\n</code></pre> linked_list.java<pre><code>/* 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化 */\n// 各ノードを初期化\nListNode n0 = new ListNode(1);\nListNode n1 = new ListNode(3);\nListNode n2 = new ListNode(2);\nListNode n3 = new ListNode(5);\nListNode n4 = new ListNode(4);\n// ノード間の参照を設定\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n</code></pre> linked_list.cs<pre><code>/* 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化 */\n// 各ノードを初期化\nListNode n0 = new(1);\nListNode n1 = new(3);\nListNode n2 = new(2);\nListNode n3 = new(5);\nListNode n4 = new(4);\n// ノード間の参照を設定\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n</code></pre> linked_list.go<pre><code>/* 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化 */\n// 各ノードを初期化\nn0 := NewListNode(1)\nn1 := NewListNode(3)\nn2 := NewListNode(2)\nn3 := NewListNode(5)\nn4 := NewListNode(4)\n// ノード間の参照を設定\nn0.Next = n1\nn1.Next = n2\nn2.Next = n3\nn3.Next = n4\n</code></pre> linked_list.swift<pre><code>/* 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化 */\n// 各ノードを初期化\nlet n0 = ListNode(x: 1)\nlet n1 = ListNode(x: 3)\nlet n2 = ListNode(x: 2)\nlet n3 = ListNode(x: 5)\nlet n4 = ListNode(x: 4)\n// ノード間の参照を設定\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n</code></pre> linked_list.js<pre><code>/* 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化 */\n// 各ノードを初期化\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// ノード間の参照を設定\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n</code></pre> linked_list.ts<pre><code>/* 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化 */\n// 各ノードを初期化\nconst n0 = new ListNode(1);\nconst n1 = new ListNode(3);\nconst n2 = new ListNode(2);\nconst n3 = new ListNode(5);\nconst n4 = new ListNode(4);\n// ノード間の参照を設定\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n</code></pre> linked_list.dart<pre><code>/* 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化 */\\\n// 各ノードを初期化\nListNode n0 = ListNode(1);\nListNode n1 = ListNode(3);\nListNode n2 = ListNode(2);\nListNode n3 = ListNode(5);\nListNode n4 = ListNode(4);\n// ノード間の参照を設定\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n</code></pre> linked_list.rs<pre><code>/* 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化 */\n// 各ノードを初期化\nlet n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None }));\nlet n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None }));\nlet n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None }));\nlet n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None }));\nlet n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None }));\n\n// ノード間の参照を設定\nn0.borrow_mut().next = Some(n1.clone());\nn1.borrow_mut().next = Some(n2.clone());\nn2.borrow_mut().next = Some(n3.clone());\nn3.borrow_mut().next = Some(n4.clone());\n</code></pre> linked_list.c<pre><code>/* 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化 */\n// 各ノードを初期化\nListNode* n0 = newListNode(1);\nListNode* n1 = newListNode(3);\nListNode* n2 = newListNode(2);\nListNode* n3 = newListNode(5);\nListNode* n4 = newListNode(4);\n// ノード間の参照を設定\nn0-&gt;next = n1;\nn1-&gt;next = n2;\nn2-&gt;next = n3;\nn3-&gt;next = n4;\n</code></pre> linked_list.kt<pre><code>/* 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化 */\n// 各ノードを初期化\nval n0 = ListNode(1)\nval n1 = ListNode(3)\nval n2 = ListNode(2)\nval n3 = ListNode(5)\nval n4 = ListNode(4)\n// ノード間の参照を設定\nn0.next = n1;\nn1.next = n2;\nn2.next = n3;\nn3.next = n4;\n</code></pre> linked_list.rb<pre><code># 連結リスト 1 -&gt; 3 -&gt; 2 -&gt; 5 -&gt; 4 を初期化\n# 各ノードを初期化\nn0 = ListNode.new(1)\nn1 = ListNode.new(3)\nn2 = ListNode.new(2)\nn3 = ListNode.new(5)\nn4 = ListNode.new(4)\n# ノード間の参照を設定\nn0.next = n1\nn1.next = n2\nn2.next = n3\nn3.next = n4\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>配列は全体で一つの変数です。たとえば配列 <code>nums</code> は、<code>nums[0]</code> や <code>nums[1]</code> などの要素を含みます。一方、連結リストは複数の独立したノードオブジェクトからなります。通常は先頭ノードで連結リスト全体を表します。たとえば、上のコードの連結リストは、連結リスト <code>n0</code> と呼べます。</p>","path":["第 4 章   配列と連結リスト","4.2   連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#2","level":3,"title":"2.   ノードの挿入","text":"<p>連結リストへのノードの挿入は簡単です。図 4-6 のように、隣り合うノード <code>n0</code> と <code>n1</code> の間に新しいノード <code>P</code> を挿入するには、二つのノード参照(ポインタ)を変更するだけで済み、時間計算量は \\(O(1)\\) です。</p> <p>対して、配列への要素の挿入は時間計算量が \\(O(n)\\) であり、データ量が大きいと非効率になります。</p> <p></p> <p> 図 4-6   連結リストへのノードの挿入例 </p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py<pre><code>def insert(n0: ListNode, P: ListNode):\n \"\"\"連結リストのノード n0 の直後にノード P を挿入する\"\"\"\n n1 = n0.next\n P.next = n1\n n0.next = P\n</code></pre> linked_list.cpp<pre><code>/* 連結リストのノード n0 の直後にノード P を挿入する */\nvoid insert(ListNode *n0, ListNode *P) {\n ListNode *n1 = n0-&gt;next;\n P-&gt;next = n1;\n n0-&gt;next = P;\n}\n</code></pre> linked_list.java<pre><code>/* 連結リストのノード n0 の直後にノード P を挿入する */\nvoid insert(ListNode n0, ListNode P) {\n ListNode n1 = n0.next;\n P.next = n1;\n n0.next = P;\n}\n</code></pre> linked_list.cs<pre><code>/* 連結リストのノード n0 の直後にノード P を挿入する */\nvoid Insert(ListNode n0, ListNode P) {\n ListNode? n1 = n0.next;\n P.next = n1;\n n0.next = P;\n}\n</code></pre> linked_list.go<pre><code>/* 連結リストのノード n0 の直後にノード P を挿入する */\nfunc insertNode(n0 *ListNode, P *ListNode) {\n n1 := n0.Next\n P.Next = n1\n n0.Next = P\n}\n</code></pre> linked_list.swift<pre><code>/* 連結リストのノード n0 の直後にノード P を挿入する */\nfunc insert(n0: ListNode, P: ListNode) {\n let n1 = n0.next\n P.next = n1\n n0.next = P\n}\n</code></pre> linked_list.js<pre><code>/* 連結リストのノード n0 の直後にノード P を挿入する */\nfunction insert(n0, P) {\n const n1 = n0.next;\n P.next = n1;\n n0.next = P;\n}\n</code></pre> linked_list.ts<pre><code>/* 連結リストのノード n0 の直後にノード P を挿入する */\nfunction insert(n0: ListNode, P: ListNode): void {\n const n1 = n0.next;\n P.next = n1;\n n0.next = P;\n}\n</code></pre> linked_list.dart<pre><code>/* 連結リストのノード n0 の直後にノード P を挿入する */\nvoid insert(ListNode n0, ListNode P) {\n ListNode? n1 = n0.next;\n P.next = n1;\n n0.next = P;\n}\n</code></pre> linked_list.rs<pre><code>/* 連結リストのノード n0 の直後にノード P を挿入する */\n#[allow(non_snake_case)]\npub fn insert&lt;T&gt;(n0: &amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;, P: Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;) {\n let n1 = n0.borrow_mut().next.take();\n P.borrow_mut().next = n1;\n n0.borrow_mut().next = Some(P);\n}\n</code></pre> linked_list.c<pre><code>/* 連結リストのノード n0 の直後にノード P を挿入する */\nvoid insert(ListNode *n0, ListNode *P) {\n ListNode *n1 = n0-&gt;next;\n P-&gt;next = n1;\n n0-&gt;next = P;\n}\n</code></pre> linked_list.kt<pre><code>/* 連結リストのノード n0 の直後にノード P を挿入する */\nfun insert(n0: ListNode?, p: ListNode?) {\n val n1 = n0?.next\n p?.next = n1\n n0?.next = p\n}\n</code></pre> linked_list.rb<pre><code>### 連結リストのノード n0 の直後にノード _p を挿入する ###\n# Ruby では `p` は組み込み関数、`P` は定数なので、代わりに `_p` を使える\ndef insert(n0, _p)\n n1 = n0.next\n _p.next = n1\n n0.next = _p\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.2   連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#3","level":3,"title":"3.   ノードの削除","text":"<p>図 4-7 のように、連結リストからのノードの削除も簡単で、一つのノードの参照(ポインタ)を変更するだけです。</p> <p>削除後もノード <code>P</code> は <code>n1</code> を指していますが、この連結リストを走査しても <code>P</code> には到達できなくなることに注意してください。つまり、<code>P</code> はもはやこの連結リストに属していません。</p> <p></p> <p> 図 4-7   連結リストからのノードの削除 </p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py<pre><code>def remove(n0: ListNode):\n \"\"\"連結リストのノード n0 の直後のノードを削除する\"\"\"\n if not n0.next:\n return\n # n0 -&gt; P -&gt; n1\n P = n0.next\n n1 = P.next\n n0.next = n1\n</code></pre> linked_list.cpp<pre><code>/* 連結リストのノード n0 の直後のノードを削除する */\nvoid remove(ListNode *n0) {\n if (n0-&gt;next == nullptr)\n return;\n // n0 -&gt; P -&gt; n1\n ListNode *P = n0-&gt;next;\n ListNode *n1 = P-&gt;next;\n n0-&gt;next = n1;\n // メモリを解放する\n delete P;\n}\n</code></pre> linked_list.java<pre><code>/* 連結リストのノード n0 の直後のノードを削除する */\nvoid remove(ListNode n0) {\n if (n0.next == null)\n return;\n // n0 -&gt; P -&gt; n1\n ListNode P = n0.next;\n ListNode n1 = P.next;\n n0.next = n1;\n}\n</code></pre> linked_list.cs<pre><code>/* 連結リストのノード n0 の直後のノードを削除する */\nvoid Remove(ListNode n0) {\n if (n0.next == null)\n return;\n // n0 -&gt; P -&gt; n1\n ListNode P = n0.next;\n ListNode? n1 = P.next;\n n0.next = n1;\n}\n</code></pre> linked_list.go<pre><code>/* 連結リストのノード n0 の直後のノードを削除する */\nfunc removeItem(n0 *ListNode) {\n if n0.Next == nil {\n return\n }\n // n0 -&gt; P -&gt; n1\n P := n0.Next\n n1 := P.Next\n n0.Next = n1\n}\n</code></pre> linked_list.swift<pre><code>/* 連結リストのノード n0 の直後のノードを削除する */\nfunc remove(n0: ListNode) {\n if n0.next == nil {\n return\n }\n // n0 -&gt; P -&gt; n1\n let P = n0.next\n let n1 = P?.next\n n0.next = n1\n}\n</code></pre> linked_list.js<pre><code>/* 連結リストのノード n0 の直後のノードを削除する */\nfunction remove(n0) {\n if (!n0.next) return;\n // n0 -&gt; P -&gt; n1\n const P = n0.next;\n const n1 = P.next;\n n0.next = n1;\n}\n</code></pre> linked_list.ts<pre><code>/* 連結リストのノード n0 の直後のノードを削除する */\nfunction remove(n0: ListNode): void {\n if (!n0.next) {\n return;\n }\n // n0 -&gt; P -&gt; n1\n const P = n0.next;\n const n1 = P.next;\n n0.next = n1;\n}\n</code></pre> linked_list.dart<pre><code>/* 連結リストのノード n0 の直後のノードを削除する */\nvoid remove(ListNode n0) {\n if (n0.next == null) return;\n // n0 -&gt; P -&gt; n1\n ListNode P = n0.next!;\n ListNode? n1 = P.next;\n n0.next = n1;\n}\n</code></pre> linked_list.rs<pre><code>/* 連結リストのノード n0 の直後のノードを削除する */\n#[allow(non_snake_case)]\npub fn remove&lt;T&gt;(n0: &amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;) {\n // n0 -&gt; P -&gt; n1\n let P = n0.borrow_mut().next.take();\n if let Some(node) = P {\n let n1 = node.borrow_mut().next.take();\n n0.borrow_mut().next = n1;\n }\n}\n</code></pre> linked_list.c<pre><code>/* 連結リストのノード n0 の直後のノードを削除する */\n// 注意:stdio.h で remove という名前が使われている\nvoid removeItem(ListNode *n0) {\n if (!n0-&gt;next)\n return;\n // n0 -&gt; P -&gt; n1\n ListNode *P = n0-&gt;next;\n ListNode *n1 = P-&gt;next;\n n0-&gt;next = n1;\n // メモリを解放する\n free(P);\n}\n</code></pre> linked_list.kt<pre><code>/* 連結リストのノード n0 の直後のノードを削除する */\nfun remove(n0: ListNode?) {\n if (n0?.next == null)\n return\n // n0 -&gt; P -&gt; n1\n val p = n0.next\n val n1 = p?.next\n n0.next = n1\n}\n</code></pre> linked_list.rb<pre><code>### 連結リストのノード n0 の直後のノードを削除する ###\ndef remove(n0)\n return if n0.next.nil?\n\n # n0 -&gt; remove_node -&gt; n1\n remove_node = n0.next\n n1 = remove_node.next\n n0.next = n1\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.2   連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#4","level":3,"title":"4.   ノードへのアクセス","text":"<p>連結リストのノードへのアクセスは効率が低くなります。前の節で説明したように、配列では任意の要素に \\(O(1)\\) 時間でアクセスできます。連結リストでは、先頭ノードから順にたどり、目的のノードを見つける必要があります。つまり、\\(i\\) 番目のノードにアクセスするには \\(i - 1\\) 回ループする必要があり、時間計算量は \\(O(n)\\) です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py<pre><code>def access(head: ListNode, index: int) -&gt; ListNode | None:\n \"\"\"連結リストのインデックス index にあるノードにアクセスする\"\"\"\n for _ in range(index):\n if not head:\n return None\n head = head.next\n return head\n</code></pre> linked_list.cpp<pre><code>/* 連結リストのインデックス index にあるノードにアクセスする */\nListNode *access(ListNode *head, int index) {\n for (int i = 0; i &lt; index; i++) {\n if (head == nullptr)\n return nullptr;\n head = head-&gt;next;\n }\n return head;\n}\n</code></pre> linked_list.java<pre><code>/* 連結リストのインデックス index にあるノードにアクセスする */\nListNode access(ListNode head, int index) {\n for (int i = 0; i &lt; index; i++) {\n if (head == null)\n return null;\n head = head.next;\n }\n return head;\n}\n</code></pre> linked_list.cs<pre><code>/* 連結リストのインデックス index にあるノードにアクセスする */\nListNode? Access(ListNode? head, int index) {\n for (int i = 0; i &lt; index; i++) {\n if (head == null)\n return null;\n head = head.next;\n }\n return head;\n}\n</code></pre> linked_list.go<pre><code>/* 連結リストのインデックス index にあるノードにアクセスする */\nfunc access(head *ListNode, index int) *ListNode {\n for i := 0; i &lt; index; i++ {\n if head == nil {\n return nil\n }\n head = head.Next\n }\n return head\n}\n</code></pre> linked_list.swift<pre><code>/* 連結リストのインデックス index にあるノードにアクセスする */\nfunc access(head: ListNode, index: Int) -&gt; ListNode? {\n var head: ListNode? = head\n for _ in 0 ..&lt; index {\n if head == nil {\n return nil\n }\n head = head?.next\n }\n return head\n}\n</code></pre> linked_list.js<pre><code>/* 連結リストのインデックス index にあるノードにアクセスする */\nfunction access(head, index) {\n for (let i = 0; i &lt; index; i++) {\n if (!head) {\n return null;\n }\n head = head.next;\n }\n return head;\n}\n</code></pre> linked_list.ts<pre><code>/* 連結リストのインデックス index にあるノードにアクセスする */\nfunction access(head: ListNode | null, index: number): ListNode | null {\n for (let i = 0; i &lt; index; i++) {\n if (!head) {\n return null;\n }\n head = head.next;\n }\n return head;\n}\n</code></pre> linked_list.dart<pre><code>/* 連結リストのインデックス index にあるノードにアクセスする */\nListNode? access(ListNode? head, int index) {\n for (var i = 0; i &lt; index; i++) {\n if (head == null) return null;\n head = head.next;\n }\n return head;\n}\n</code></pre> linked_list.rs<pre><code>/* 連結リストのインデックス index にあるノードにアクセスする */\npub fn access&lt;T&gt;(head: Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;, index: i32) -&gt; Option&lt;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt; {\n fn dfs&lt;T&gt;(\n head: Option&lt;&amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;,\n index: i32,\n ) -&gt; Option&lt;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt; {\n if index &lt;= 0 {\n return head.cloned();\n }\n\n if let Some(node) = head {\n dfs(node.borrow().next.as_ref(), index - 1)\n } else {\n None\n }\n }\n\n dfs(Some(head).as_ref(), index)\n}\n</code></pre> linked_list.c<pre><code>/* 連結リストのインデックス index にあるノードにアクセスする */\nListNode *access(ListNode *head, int index) {\n for (int i = 0; i &lt; index; i++) {\n if (head == NULL)\n return NULL;\n head = head-&gt;next;\n }\n return head;\n}\n</code></pre> linked_list.kt<pre><code>/* 連結リストのインデックス index にあるノードにアクセスする */\nfun access(head: ListNode?, index: Int): ListNode? {\n var h = head\n for (i in 0..&lt;index) {\n if (h == null)\n return null\n h = h.next\n }\n return h\n}\n</code></pre> linked_list.rb<pre><code>### 連結リストのインデックス index にあるノードにアクセスする ###\ndef access(head, index)\n for i in 0...index\n return nil if head.nil?\n head = head.next\n end\n\n head\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.2   連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#5","level":3,"title":"5.   ノードの探索","text":"<p>連結リストを走査して値が <code>target</code> のノードを探し、そのノードのインデックスを返します。これも線形探索です。コードを次に示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linked_list.py<pre><code>def find(head: ListNode, target: int) -&gt; int:\n \"\"\"連結リストから値が target である最初のノードを探索する\"\"\"\n index = 0\n while head:\n if head.val == target:\n return index\n head = head.next\n index += 1\n return -1\n</code></pre> linked_list.cpp<pre><code>/* 連結リストから値が target である最初のノードを探索する */\nint find(ListNode *head, int target) {\n int index = 0;\n while (head != nullptr) {\n if (head-&gt;val == target)\n return index;\n head = head-&gt;next;\n index++;\n }\n return -1;\n}\n</code></pre> linked_list.java<pre><code>/* 連結リストから値が target である最初のノードを探索する */\nint find(ListNode head, int target) {\n int index = 0;\n while (head != null) {\n if (head.val == target)\n return index;\n head = head.next;\n index++;\n }\n return -1;\n}\n</code></pre> linked_list.cs<pre><code>/* 連結リストから値が target である最初のノードを探索する */\nint Find(ListNode? head, int target) {\n int index = 0;\n while (head != null) {\n if (head.val == target)\n return index;\n head = head.next;\n index++;\n }\n return -1;\n}\n</code></pre> linked_list.go<pre><code>/* 連結リストから値が target である最初のノードを探索する */\nfunc findNode(head *ListNode, target int) int {\n index := 0\n for head != nil {\n if head.Val == target {\n return index\n }\n head = head.Next\n index++\n }\n return -1\n}\n</code></pre> linked_list.swift<pre><code>/* 連結リストから値が target である最初のノードを探索する */\nfunc find(head: ListNode, target: Int) -&gt; Int {\n var head: ListNode? = head\n var index = 0\n while head != nil {\n if head?.val == target {\n return index\n }\n head = head?.next\n index += 1\n }\n return -1\n}\n</code></pre> linked_list.js<pre><code>/* 連結リストから値が target である最初のノードを探索する */\nfunction find(head, target) {\n let index = 0;\n while (head !== null) {\n if (head.val === target) {\n return index;\n }\n head = head.next;\n index += 1;\n }\n return -1;\n}\n</code></pre> linked_list.ts<pre><code>/* 連結リストから値が target である最初のノードを探索する */\nfunction find(head: ListNode | null, target: number): number {\n let index = 0;\n while (head !== null) {\n if (head.val === target) {\n return index;\n }\n head = head.next;\n index += 1;\n }\n return -1;\n}\n</code></pre> linked_list.dart<pre><code>/* 連結リストから値が target である最初のノードを探索する */\nint find(ListNode? head, int target) {\n int index = 0;\n while (head != null) {\n if (head.val == target) {\n return index;\n }\n head = head.next;\n index++;\n }\n return -1;\n}\n</code></pre> linked_list.rs<pre><code>/* 連結リストから値が target である最初のノードを探索する */\npub fn find&lt;T: PartialEq&gt;(head: Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;, target: T) -&gt; i32 {\n fn find&lt;T: PartialEq&gt;(head: Option&lt;&amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;, target: T, idx: i32) -&gt; i32 {\n if let Some(node) = head {\n if node.borrow().val == target {\n return idx;\n }\n return find(node.borrow().next.as_ref(), target, idx + 1);\n } else {\n -1\n }\n }\n\n find(Some(head).as_ref(), target, 0)\n}\n</code></pre> linked_list.c<pre><code>/* 連結リストから値が target である最初のノードを探索する */\nint find(ListNode *head, int target) {\n int index = 0;\n while (head) {\n if (head-&gt;val == target)\n return index;\n head = head-&gt;next;\n index++;\n }\n return -1;\n}\n</code></pre> linked_list.kt<pre><code>/* 連結リストから値が target である最初のノードを探索する */\nfun find(head: ListNode?, target: Int): Int {\n var index = 0\n var h = head\n while (h != null) {\n if (h._val == target)\n return index\n h = h.next\n index++\n }\n return -1\n}\n</code></pre> linked_list.rb<pre><code>### 連結リストから値が target である最初のノードを探索する ###\ndef find(head, target)\n index = 0\n while head\n return index if head.val == target\n head = head.next\n index += 1\n end\n\n -1\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.2   連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#422","level":2,"title":"4.2.2   配列と連結リストの比較","text":"<p>表 4-1 に、配列と連結リストの特徴と操作効率をまとめます。両者は対照的な格納戦略を取るため、性質や操作効率も対照的になります。</p> <p> 表 4-1   配列と連結リストの効率の比較 </p> 配列 連結リスト 格納方法 連続したメモリ領域 分散したメモリ領域 容量の拡張 長さは変更できない 柔軟に拡張できる メモリ効率 要素の使用量は少ないが、空間が無駄になることもある 要素のメモリ使用量が多い 要素へのアクセス \\(O(1)\\) \\(O(n)\\) 要素の追加 \\(O(n)\\) \\(O(1)\\) 要素の削除 \\(O(n)\\) \\(O(1)\\)","path":["第 4 章   配列と連結リスト","4.2   連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#423","level":2,"title":"4.2.3   連結リストの主な種類","text":"<p>図 4-8 のように、代表的な連結リストには 3 種類あります。</p> <ul> <li>単方向連結リスト:これまで説明した通常の連結リストです。各ノードは、値と次のノードへの参照の二つを持ちます。最初のノードを先頭ノード、最後のノードを末尾ノードと呼び、末尾ノードは空の <code>None</code> を指します。</li> <li>循環連結リスト:単方向連結リストの末尾ノードを先頭ノードに向け、末尾と先頭をつなぐと、循環連結リストになります。循環連結リストでは、任意のノードを先頭とみなせます。</li> <li>双方向連結リスト:単方向連結リストと異なり、両方向の参照を記録します。ノードには、後続ノード(次のノード)と先行ノード(前のノード)の両方への参照(ポインタ)を持たせます。単方向連結リストより柔軟で、どちらの方向にも走査できますが、その分メモリ使用量も増えます。</li> </ul> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code>class ListNode:\n \"\"\"双方向連結リストのノードクラス\"\"\"\n def __init__(self, val: int):\n self.val: int = val # ノードの値\n self.next: ListNode | None = None # 後続ノードへの参照\n self.prev: ListNode | None = None # 先行ノードへの参照\n</code></pre> <pre><code>/* 双方向連結リストのノード構造体 */\nstruct ListNode {\n int val; // ノードの値\n ListNode *next; // 後続ノードへのポインタ\n ListNode *prev; // 先行ノードへのポインタ\n ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // コンストラクタ\n};\n</code></pre> <pre><code>/* 双方向連結リストのノードクラス */\nclass ListNode {\n int val; // ノードの値\n ListNode next; // 後続ノードへの参照\n ListNode prev; // 先行ノードへの参照\n ListNode(int x) { val = x; } // コンストラクタ\n}\n</code></pre> <pre><code>/* 双方向連結リストのノードクラス */\nclass ListNode(int x) { // コンストラクタ\n int val = x; // ノードの値\n ListNode next; // 後続ノードへの参照\n ListNode prev; // 先行ノードへの参照\n}\n</code></pre> <pre><code>/* 双方向連結リストのノード構造体 */\ntype DoublyListNode struct {\n Val int // ノードの値\n Next *DoublyListNode // 後続ノードへのポインタ\n Prev *DoublyListNode // 先行ノードへのポインタ\n}\n\n// NewDoublyListNode で初期化\nfunc NewDoublyListNode(val int) *DoublyListNode {\n return &amp;DoublyListNode{\n Val: val,\n Next: nil,\n Prev: nil,\n }\n}\n</code></pre> <pre><code>/* 双方向連結リストのノードクラス */\nclass ListNode {\n var val: Int // ノードの値\n var next: ListNode? // 後続ノードへの参照\n var prev: ListNode? // 先行ノードへの参照\n\n init(x: Int) { // コンストラクタ\n val = x\n }\n}\n</code></pre> <pre><code>/* 双方向連結リストのノードクラス */\nclass ListNode {\n constructor(val, next, prev) {\n this.val = val === undefined ? 0 : val; // ノードの値\n this.next = next === undefined ? null : next; // 後続ノードへの参照\n this.prev = prev === undefined ? null : prev; // 先行ノードへの参照\n }\n}\n</code></pre> <pre><code>/* 双方向連結リストのノードクラス */\nclass ListNode {\n val: number;\n next: ListNode | null;\n prev: ListNode | null;\n constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {\n this.val = val === undefined ? 0 : val; // ノードの値\n this.next = next === undefined ? null : next; // 後続ノードへの参照\n this.prev = prev === undefined ? null : prev; // 先行ノードへの参照\n }\n}\n</code></pre> <pre><code>/* 双方向連結リストのノードクラス */\nclass ListNode {\n int val; // ノードの値\n ListNode? next; // 後続ノードへの参照\n ListNode? prev; // 先行ノードへの参照\n ListNode(this.val, [this.next, this.prev]); // コンストラクタ\n}\n</code></pre> <pre><code>use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* 双方向連結リストのノード型 */\n#[derive(Debug)]\nstruct ListNode {\n val: i32, // ノードの値\n next: Option&lt;Rc&lt;RefCell&lt;ListNode&gt;&gt;&gt;, // 後続ノードへのポインタ\n prev: Option&lt;Rc&lt;RefCell&lt;ListNode&gt;&gt;&gt;, // 先行ノードへのポインタ\n}\n\n/* コンストラクタ */\nimpl ListNode {\n fn new(val: i32) -&gt; Self {\n ListNode {\n val,\n next: None,\n prev: None,\n }\n }\n}\n</code></pre> <pre><code>/* 双方向連結リストのノード構造体 */\ntypedef struct ListNode {\n int val; // ノードの値\n struct ListNode *next; // 後続ノードへのポインタ\n struct ListNode *prev; // 先行ノードへのポインタ\n} ListNode;\n\n/* コンストラクタ */\nListNode *newListNode(int val) {\n ListNode *node;\n node = (ListNode *) malloc(sizeof(ListNode));\n node-&gt;val = val;\n node-&gt;next = NULL;\n node-&gt;prev = NULL;\n return node;\n}\n</code></pre> <pre><code>/* 双方向連結リストのノードクラス */\n// コンストラクタ\nclass ListNode(x: Int) {\n val _val: Int = x // ノードの値\n var next: ListNode? = null // 後続ノードへの参照\n var prev: ListNode? = null // 先行ノードへの参照\n}\n</code></pre> <pre><code># 双方向連結リストのノードクラス\nclass ListNode\n attr_accessor :val # ノードの値\n attr_accessor :next # 後続ノードへの参照\n attr_accessor :prev # 先行ノードへの参照\n\n def initialize(val=0, next_node=nil, prev_node=nil)\n @val = val\n @next = next_node\n @prev = prev_node\n end\nend\n</code></pre> <p></p> <p> 図 4-8   連結リストの主な種類 </p>","path":["第 4 章   配列と連結リスト","4.2   連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/linked_list/#424","level":2,"title":"4.2.4   連結リストの代表的な用途","text":"<p>単方向連結リストは、スタック、キュー、ハッシュテーブル、グラフなどの実装によく使われます。</p> <ul> <li>スタックとキュー:挿入と削除をどちらも同じ端で行うと、先入れ後出しの性質になり、スタックに対応します。一方の端で挿入し、もう一方の端で削除すると、先入れ先出しの性質になり、キューに対応します。</li> <li>ハッシュテーブル:チェイン法は、ハッシュ衝突を解決する代表的な方法の一つです。衝突したすべての要素を、一つの連結リストに格納します。</li> <li>グラフ:隣接リストは、グラフを表す代表的な方法です。各頂点に連結リストを対応させ、その各要素で、当該頂点につながる別の頂点を表します。</li> </ul> <p>双方向連結リストは、前後の要素をすばやく見つける必要がある場面でよく使われます。</p> <ul> <li>高度なデータ構造:たとえば赤黒木や B 木では、ノードの親にアクセスする必要があります。ノードに親への参照を保存すれば実現でき、双方向連結リストと似た考え方です。</li> <li>ブラウザの履歴:ユーザーが進むボタンや戻るボタンを押すと、Web ブラウザは前後に閲覧したページを知る必要があります。双方向連結リストの性質により、この操作を簡単に実現できます。</li> <li>LRU アルゴリズム:キャッシュの置換(LRU)アルゴリズムでは、最後に使われてから最も時間がたったデータをすばやく見つけ、ノードを高速に追加・削除する必要があります。双方向連結リストはこの用途に適しています。</li> </ul> <p>循環連結リストは、OS の資源スケジューリングなど、周期的な操作が必要な場面でよく使われます。</p> <ul> <li>ラウンドロビンスケジューリング:OS でよく使われる CPU スケジューリングの方法で、一群のプロセスを順に繰り返し処理します。各プロセスにタイムスライスを割り当て、それを使い切ると CPU は次のプロセスに切り替わります。この循環的な操作は、循環連結リストで実装できます。</li> <li>データバッファ:データバッファの実装にも循環連結リストを使うことがあります。たとえば音声や動画のプレイヤーでは、データストリームを複数のバッファブロックに分け、循環連結リストに格納して、途切れない再生を実現できます。</li> </ul>","path":["第 4 章   配列と連結リスト","4.2   連結リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/","level":1,"title":"4.3   リスト","text":"<p>リスト(list)は抽象的なデータ構造の概念で、順序を持つ要素の集合を表します。要素へのアクセス、更新、追加、削除、走査などの操作をサポートし、利用者は容量の制限を気にする必要がありません。リストは連結リストや配列を使って実装できます。</p> <ul> <li>連結リストは、要素の追加・削除・検索・更新をサポートし、動的に柔軟に拡張できるため、そのままリストとみなせます。</li> <li>配列も要素の追加・削除・検索・更新をサポートしますが、長さを変えられないため、長さに制限のあるリストとしかみなせません。</li> </ul> <p>配列でリストを実装すると、長さを変えられない性質が実用性を下げてしまいます。通常、保存するデータ量を事前に確定できず、適切な長さを選ぶのが難しいためです。短すぎれば必要な量を格納できず、長すぎればメモリが無駄になります。</p> <p>この問題を解決するため、動的配列(dynamic array)でリストを実装できます。動的配列は配列の利点を受け継ぎ、プログラムの実行中に容量を動的に拡張できます。</p> <p>実際、多くのプログラミング言語の標準ライブラリで提供されるリストは、動的配列で実装されています。Python の <code>list</code>、Java の <code>ArrayList</code>、C++ の <code>vector</code>、C# の <code>List</code> などがその例です。以下では、「リスト」と「動的配列」を同じ概念として扱います。</p>","path":["第 4 章   配列と連結リスト","4.3   リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#431","level":2,"title":"4.3.1   リストの主な操作","text":"","path":["第 4 章   配列と連結リスト","4.3   リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#1","level":3,"title":"1.   リストの初期化","text":"<p>通常は、「初期値なし」と「初期値あり」の二つの方法で初期化します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py<pre><code># リストを初期化\n# 初期値なし\nnums1: list[int] = []\n# 初期値あり\nnums: list[int] = [1, 3, 2, 5, 4]\n</code></pre> list.cpp<pre><code>/* リストを初期化 */\n// C++ の vector が、本節で扱う nums に相当することに注意\n// 初期値なし\nvector&lt;int&gt; nums1;\n// 初期値あり\nvector&lt;int&gt; nums = { 1, 3, 2, 5, 4 };\n</code></pre> list.java<pre><code>/* リストを初期化 */\n// 初期値なし\nList&lt;Integer&gt; nums1 = new ArrayList&lt;&gt;();\n// 初期値あり(配列には、int[] に対応するラッパー型の配列 Integer[] を使う必要がある)\nInteger[] numbers = new Integer[] { 1, 3, 2, 5, 4 };\nList&lt;Integer&gt; nums = new ArrayList&lt;&gt;(Arrays.asList(numbers));\n</code></pre> list.cs<pre><code>/* リストを初期化 */\n// 初期値なし\nList&lt;int&gt; nums1 = [];\n// 初期値あり\nint[] numbers = [1, 3, 2, 5, 4];\nList&lt;int&gt; nums = [.. numbers];\n</code></pre> list_test.go<pre><code>/* リストを初期化 */\n// 初期値なし\nnums1 := []int{}\n// 初期値あり\nnums := []int{1, 3, 2, 5, 4}\n</code></pre> list.swift<pre><code>/* リストを初期化 */\n// 初期値なし\nlet nums1: [Int] = []\n// 初期値あり\nvar nums = [1, 3, 2, 5, 4]\n</code></pre> list.js<pre><code>/* リストを初期化 */\n// 初期値なし\nconst nums1 = [];\n// 初期値あり\nconst nums = [1, 3, 2, 5, 4];\n</code></pre> list.ts<pre><code>/* リストを初期化 */\n// 初期値なし\nconst nums1: number[] = [];\n// 初期値あり\nconst nums: number[] = [1, 3, 2, 5, 4];\n</code></pre> list.dart<pre><code>/* リストを初期化 */\n// 初期値なし\nList&lt;int&gt; nums1 = [];\n// 初期値あり\nList&lt;int&gt; nums = [1, 3, 2, 5, 4];\n</code></pre> list.rs<pre><code>/* リストを初期化 */\n// 初期値なし\nlet nums1: Vec&lt;i32&gt; = Vec::new();\n// 初期値あり\nlet nums: Vec&lt;i32&gt; = vec![1, 3, 2, 5, 4];\n</code></pre> list.c<pre><code>// C には組み込みの動的配列がない\n</code></pre> list.kt<pre><code>/* リストを初期化 */\n// 初期値なし\nvar nums1 = listOf&lt;Int&gt;()\n// 初期値あり\nvar numbers = arrayOf(1, 3, 2, 5, 4)\nvar nums = numbers.toMutableList()\n</code></pre> list.rb<pre><code># リストを初期化\n# 初期値なし\nnums1 = []\n# 初期値あり\nnums = [1, 3, 2, 5, 4]\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.3   リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#2","level":3,"title":"2.   要素へのアクセス","text":"<p>リストの実体は配列なので、要素へのアクセスと更新を \\(O(1)\\) 時間で行え、非常に効率的です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py<pre><code># 要素にアクセス\nnum: int = nums[1] # インデックス 1 の要素にアクセス\n\n# 要素を更新\nnums[1] = 0 # インデックス 1 の要素を 0 に更新\n</code></pre> list.cpp<pre><code>/* 要素にアクセス */\nint num = nums[1]; // インデックス 1 の要素にアクセス\n\n/* 要素を更新 */\nnums[1] = 0; // インデックス 1 の要素を 0 に更新\n</code></pre> list.java<pre><code>/* 要素にアクセス */\nint num = nums.get(1); // インデックス 1 の要素にアクセス\n\n/* 要素を更新 */\nnums.set(1, 0); // インデックス 1 の要素を 0 に更新\n</code></pre> list.cs<pre><code>/* 要素にアクセス */\nint num = nums[1]; // インデックス 1 の要素にアクセス\n\n/* 要素を更新 */\nnums[1] = 0; // インデックス 1 の要素を 0 に更新\n</code></pre> list_test.go<pre><code>/* 要素にアクセス */\nnum := nums[1] // インデックス 1 の要素にアクセス\n\n/* 要素を更新 */\nnums[1] = 0 // インデックス 1 の要素を 0 に更新\n</code></pre> list.swift<pre><code>/* 要素にアクセス */\nlet num = nums[1] // インデックス 1 の要素にアクセス\n\n/* 要素を更新 */\nnums[1] = 0 // インデックス 1 の要素を 0 に更新\n</code></pre> list.js<pre><code>/* 要素にアクセス */\nconst num = nums[1]; // インデックス 1 の要素にアクセス\n\n/* 要素を更新 */\nnums[1] = 0; // インデックス 1 の要素を 0 に更新\n</code></pre> list.ts<pre><code>/* 要素にアクセス */\nconst num: number = nums[1]; // インデックス 1 の要素にアクセス\n\n/* 要素を更新 */\nnums[1] = 0; // インデックス 1 の要素を 0 に更新\n</code></pre> list.dart<pre><code>/* 要素にアクセス */\nint num = nums[1]; // インデックス 1 の要素にアクセス\n\n/* 要素を更新 */\nnums[1] = 0; // インデックス 1 の要素を 0 に更新\n</code></pre> list.rs<pre><code>/* 要素にアクセス */\nlet num: i32 = nums[1]; // インデックス 1 の要素にアクセス\n/* 要素を更新 */\nnums[1] = 0; // インデックス 1 の要素を 0 に更新\n</code></pre> list.c<pre><code>// C には組み込みの動的配列がない\n</code></pre> list.kt<pre><code>/* 要素にアクセス */\nval num = nums[1] // インデックス 1 の要素にアクセス\n/* 要素を更新 */\nnums[1] = 0 // インデックス 1 の要素を 0 に更新\n</code></pre> list.rb<pre><code># 要素にアクセス\nnum = nums[1] # インデックス 1 の要素にアクセス\n# 要素を更新\nnums[1] = 0 # インデックス 1 の要素を 0 に更新\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.3   リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#3","level":3,"title":"3.   要素の挿入と削除","text":"<p>配列と比べて、リストは自由に要素を追加・削除できます。末尾への追加の時間計算量は \\(O(1)\\) ですが、要素の挿入と削除の効率は配列と同じで、時間計算量は \\(O(n)\\) です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py<pre><code># リストを空にする\nnums.clear()\n\n# 末尾に要素を追加\nnums.append(1)\nnums.append(3)\nnums.append(2)\nnums.append(5)\nnums.append(4)\n\n# 途中に要素を挿入\nnums.insert(3, 6) # インデックス 3 に数値 6 を挿入\n\n# 要素を削除\nnums.pop(3) # インデックス 3 の要素を削除\n</code></pre> list.cpp<pre><code>/* リストを空にする */\nnums.clear();\n\n/* 末尾に要素を追加 */\nnums.push_back(1);\nnums.push_back(3);\nnums.push_back(2);\nnums.push_back(5);\nnums.push_back(4);\n\n/* 途中に要素を挿入 */\nnums.insert(nums.begin() + 3, 6); // インデックス 3 に数値 6 を挿入\n\n/* 要素を削除 */\nnums.erase(nums.begin() + 3); // インデックス 3 の要素を削除\n</code></pre> list.java<pre><code>/* リストを空にする */\nnums.clear();\n\n/* 末尾に要素を追加 */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* 途中に要素を挿入 */\nnums.add(3, 6); // インデックス 3 に数値 6 を挿入\n\n/* 要素を削除 */\nnums.remove(3); // インデックス 3 の要素を削除\n</code></pre> list.cs<pre><code>/* リストを空にする */\nnums.Clear();\n\n/* 末尾に要素を追加 */\nnums.Add(1);\nnums.Add(3);\nnums.Add(2);\nnums.Add(5);\nnums.Add(4);\n\n/* 途中に要素を挿入 */\nnums.Insert(3, 6); // インデックス 3 に数値 6 を挿入\n\n/* 要素を削除 */\nnums.RemoveAt(3); // インデックス 3 の要素を削除\n</code></pre> list_test.go<pre><code>/* リストを空にする */\nnums = nil\n\n/* 末尾に要素を追加 */\nnums = append(nums, 1)\nnums = append(nums, 3)\nnums = append(nums, 2)\nnums = append(nums, 5)\nnums = append(nums, 4)\n\n/* 途中に要素を挿入 */\nnums = append(nums[:3], append([]int{6}, nums[3:]...)...) // インデックス 3 に数値 6 を挿入\n\n/* 要素を削除 */\nnums = append(nums[:3], nums[4:]...) // インデックス 3 の要素を削除\n</code></pre> list.swift<pre><code>/* リストを空にする */\nnums.removeAll()\n\n/* 末尾に要素を追加 */\nnums.append(1)\nnums.append(3)\nnums.append(2)\nnums.append(5)\nnums.append(4)\n\n/* 途中に要素を挿入 */\nnums.insert(6, at: 3) // インデックス 3 に数値 6 を挿入\n\n/* 要素を削除 */\nnums.remove(at: 3) // インデックス 3 の要素を削除\n</code></pre> list.js<pre><code>/* リストを空にする */\nnums.length = 0;\n\n/* 末尾に要素を追加 */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* 途中に要素を挿入 */\nnums.splice(3, 0, 6); // インデックス 3 に数値 6 を挿入\n\n/* 要素を削除 */\nnums.splice(3, 1); // インデックス 3 の要素を削除\n</code></pre> list.ts<pre><code>/* リストを空にする */\nnums.length = 0;\n\n/* 末尾に要素を追加 */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* 途中に要素を挿入 */\nnums.splice(3, 0, 6); // インデックス 3 に数値 6 を挿入\n\n/* 要素を削除 */\nnums.splice(3, 1); // インデックス 3 の要素を削除\n</code></pre> list.dart<pre><code>/* リストを空にする */\nnums.clear();\n\n/* 末尾に要素を追加 */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* 途中に要素を挿入 */\nnums.insert(3, 6); // インデックス 3 に数値 6 を挿入\n\n/* 要素を削除 */\nnums.removeAt(3); // インデックス 3 の要素を削除\n</code></pre> list.rs<pre><code>/* リストを空にする */\nnums.clear();\n\n/* 末尾に要素を追加 */\nnums.push(1);\nnums.push(3);\nnums.push(2);\nnums.push(5);\nnums.push(4);\n\n/* 途中に要素を挿入 */\nnums.insert(3, 6); // インデックス 3 に数値 6 を挿入\n\n/* 要素を削除 */\nnums.remove(3); // インデックス 3 の要素を削除\n</code></pre> list.c<pre><code>// C には組み込みの動的配列がない\n</code></pre> list.kt<pre><code>/* リストを空にする */\nnums.clear();\n\n/* 末尾に要素を追加 */\nnums.add(1);\nnums.add(3);\nnums.add(2);\nnums.add(5);\nnums.add(4);\n\n/* 途中に要素を挿入 */\nnums.add(3, 6); // インデックス 3 に数値 6 を挿入\n\n/* 要素を削除 */\nnums.remove(3); // インデックス 3 の要素を削除\n</code></pre> list.rb<pre><code># リストを空にする\nnums.clear\n\n# 末尾に要素を追加\nnums &lt;&lt; 1\nnums &lt;&lt; 3\nnums &lt;&lt; 2\nnums &lt;&lt; 5\nnums &lt;&lt; 4\n\n# 途中に要素を挿入\nnums.insert(3, 6) # インデックス 3 に数値 6 を挿入\n\n# 要素を削除\nnums.delete_at(3) # インデックス 3 の要素を削除\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.3   リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#4","level":3,"title":"4.   リストの走査","text":"<p>配列と同様に、インデックスを使って走査することも、各要素を直接取り出して走査することもできます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py<pre><code># インデックスを使ってリストを走査\ncount = 0\nfor i in range(len(nums)):\n count += nums[i]\n\n# リストの要素を直接走査\nfor num in nums:\n count += num\n</code></pre> list.cpp<pre><code>/* インデックスを使ってリストを走査 */\nint count = 0;\nfor (int i = 0; i &lt; nums.size(); i++) {\n count += nums[i];\n}\n\n/* リストの要素を直接走査 */\ncount = 0;\nfor (int num : nums) {\n count += num;\n}\n</code></pre> list.java<pre><code>/* インデックスを使ってリストを走査 */\nint count = 0;\nfor (int i = 0; i &lt; nums.size(); i++) {\n count += nums.get(i);\n}\n\n/* リストの要素を直接走査 */\nfor (int num : nums) {\n count += num;\n}\n</code></pre> list.cs<pre><code>/* インデックスを使ってリストを走査 */\nint count = 0;\nfor (int i = 0; i &lt; nums.Count; i++) {\n count += nums[i];\n}\n\n/* リストの要素を直接走査 */\ncount = 0;\nforeach (int num in nums) {\n count += num;\n}\n</code></pre> list_test.go<pre><code>/* インデックスを使ってリストを走査 */\ncount := 0\nfor i := 0; i &lt; len(nums); i++ {\n count += nums[i]\n}\n\n/* リストの要素を直接走査 */\ncount = 0\nfor _, num := range nums {\n count += num\n}\n</code></pre> list.swift<pre><code>/* インデックスを使ってリストを走査 */\nvar count = 0\nfor i in nums.indices {\n count += nums[i]\n}\n\n/* リストの要素を直接走査 */\ncount = 0\nfor num in nums {\n count += num\n}\n</code></pre> list.js<pre><code>/* インデックスを使ってリストを走査 */\nlet count = 0;\nfor (let i = 0; i &lt; nums.length; i++) {\n count += nums[i];\n}\n\n/* リストの要素を直接走査 */\ncount = 0;\nfor (const num of nums) {\n count += num;\n}\n</code></pre> list.ts<pre><code>/* インデックスを使ってリストを走査 */\nlet count = 0;\nfor (let i = 0; i &lt; nums.length; i++) {\n count += nums[i];\n}\n\n/* リストの要素を直接走査 */\ncount = 0;\nfor (const num of nums) {\n count += num;\n}\n</code></pre> list.dart<pre><code>/* インデックスを使ってリストを走査 */\nint count = 0;\nfor (var i = 0; i &lt; nums.length; i++) {\n count += nums[i];\n}\n\n/* リストの要素を直接走査 */\ncount = 0;\nfor (var num in nums) {\n count += num;\n}\n</code></pre> list.rs<pre><code>// インデックスを使ってリストを走査\nlet mut _count = 0;\nfor i in 0..nums.len() {\n _count += nums[i];\n}\n\n// リストの要素を直接走査\n_count = 0;\nfor num in &amp;nums {\n _count += num;\n}\n</code></pre> list.c<pre><code>// C には組み込みの動的配列がない\n</code></pre> list.kt<pre><code>/* インデックスを使ってリストを走査 */\nvar count = 0\nfor (i in nums.indices) {\n count += nums[i]\n}\n\n/* リストの要素を直接走査 */\nfor (num in nums) {\n count += num\n}\n</code></pre> list.rb<pre><code># インデックスを使ってリストを走査\ncount = 0\nfor i in 0...nums.length\n count += nums[i]\nend\n\n# リストの要素を直接走査\ncount = 0\nfor num in nums\n count += num\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.3   リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#5","level":3,"title":"5.   リストの連結","text":"<p>新しいリスト <code>nums1</code> を、元のリストの末尾につなげられます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py<pre><code># 二つのリストを連結\nnums1: list[int] = [6, 8, 7, 10, 9]\nnums += nums1 # リスト nums1 を nums の後ろに連結\n</code></pre> list.cpp<pre><code>/* 二つのリストを連結 */\nvector&lt;int&gt; nums1 = { 6, 8, 7, 10, 9 };\n// リスト nums1 を nums の後ろに連結\nnums.insert(nums.end(), nums1.begin(), nums1.end());\n</code></pre> list.java<pre><code>/* 二つのリストを連結 */\nList&lt;Integer&gt; nums1 = new ArrayList&lt;&gt;(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 }));\nnums.addAll(nums1); // リスト nums1 を nums の後ろに連結\n</code></pre> list.cs<pre><code>/* 二つのリストを連結 */\nList&lt;int&gt; nums1 = [6, 8, 7, 10, 9];\nnums.AddRange(nums1); // リスト nums1 を nums の後ろに連結\n</code></pre> list_test.go<pre><code>/* 二つのリストを連結 */\nnums1 := []int{6, 8, 7, 10, 9}\nnums = append(nums, nums1...) // リスト nums1 を nums の後ろに連結\n</code></pre> list.swift<pre><code>/* 二つのリストを連結 */\nlet nums1 = [6, 8, 7, 10, 9]\nnums.append(contentsOf: nums1) // リスト nums1 を nums の後ろに連結\n</code></pre> list.js<pre><code>/* 二つのリストを連結 */\nconst nums1 = [6, 8, 7, 10, 9];\nnums.push(...nums1); // リスト nums1 を nums の後ろに連結\n</code></pre> list.ts<pre><code>/* 二つのリストを連結 */\nconst nums1: number[] = [6, 8, 7, 10, 9];\nnums.push(...nums1); // リスト nums1 を nums の後ろに連結\n</code></pre> list.dart<pre><code>/* 二つのリストを連結 */\nList&lt;int&gt; nums1 = [6, 8, 7, 10, 9];\nnums.addAll(nums1); // リスト nums1 を nums の後ろに連結\n</code></pre> list.rs<pre><code>/* 二つのリストを連結 */\nlet nums1: Vec&lt;i32&gt; = vec![6, 8, 7, 10, 9];\nnums.extend(nums1);\n</code></pre> list.c<pre><code>// C には組み込みの動的配列がない\n</code></pre> list.kt<pre><code>/* 二つのリストを連結 */\nval nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList()\nnums.addAll(nums1) // リスト nums1 を nums の後ろに連結\n</code></pre> list.rb<pre><code># 二つのリストを連結\nnums1 = [6, 8, 7, 10, 9]\nnums += nums1\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.3   リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#6","level":3,"title":"6.   リストのソート","text":"<p>リストをソートすると、配列を扱うアルゴリズムの問題でよく問われる「二分探索」や「二つのポインタを使う」アルゴリズムを利用できます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby list.py<pre><code># リストをソート\nnums.sort() # ソート後、リストの要素は昇順に並ぶ\n</code></pre> list.cpp<pre><code>/* リストをソート */\nsort(nums.begin(), nums.end()); // ソート後、リストの要素は昇順に並ぶ\n</code></pre> list.java<pre><code>/* リストをソート */\nCollections.sort(nums); // ソート後、リストの要素は昇順に並ぶ\n</code></pre> list.cs<pre><code>/* リストをソート */\nnums.Sort(); // ソート後、リストの要素は昇順に並ぶ\n</code></pre> list_test.go<pre><code>/* リストをソート */\nsort.Ints(nums) // ソート後、リストの要素は昇順に並ぶ\n</code></pre> list.swift<pre><code>/* リストをソート */\nnums.sort() // ソート後、リストの要素は昇順に並ぶ\n</code></pre> list.js<pre><code>/* リストをソート */\nnums.sort((a, b) =&gt; a - b); // ソート後、リストの要素は昇順に並ぶ\n</code></pre> list.ts<pre><code>/* リストをソート */\nnums.sort((a, b) =&gt; a - b); // ソート後、リストの要素は昇順に並ぶ\n</code></pre> list.dart<pre><code>/* リストをソート */\nnums.sort(); // ソート後、リストの要素は昇順に並ぶ\n</code></pre> list.rs<pre><code>/* リストをソート */\nnums.sort(); // ソート後、リストの要素は昇順に並ぶ\n</code></pre> list.c<pre><code>// C には組み込みの動的配列がない\n</code></pre> list.kt<pre><code>/* リストをソート */\nnums.sort() // ソート後、リストの要素は昇順に並ぶ\n</code></pre> list.rb<pre><code># リストをソート\nnums = nums.sort { |a, b| a &lt;=&gt; b } # ソート後、リストの要素は昇順に並ぶ\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.3   リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/list/#432","level":2,"title":"4.3.2   リストの実装","text":"<p>Java、C++、Python など、多くのプログラミング言語にはリストが用意されています。その実装は比較的複雑で、初期容量や拡張倍率などのパラメータも慎重に設計されています。興味があれば、ソースコードを読んで学んでみてください。</p> <p>リストの仕組みをより深く理解するため、次の三つを設計の要点とする簡単なリストを実装してみましょう。</p> <ul> <li>初期容量:配列の初期容量を適切に設定します。この例では 10 とします。</li> <li>要素数の記録:現在の要素数を記録する変数 <code>size</code> を用意し、挿入と削除のたびに更新します。この変数からリストの末尾を特定し、容量拡張が必要かどうか判断できます。</li> <li>容量拡張の仕組み:挿入時に容量がいっぱいなら、拡張する必要があります。まず拡張倍率に従って大きな配列を作り、現在の配列の全要素を順に移します。この例では、毎回、容量を元の 2 倍にします。</li> </ul> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_list.py<pre><code>class MyList:\n \"\"\"リストクラス\"\"\"\n\n def __init__(self):\n \"\"\"コンストラクタ\"\"\"\n self._capacity: int = 10 # リストの容量\n self._arr: list[int] = [0] * self._capacity # 配列(リストの要素を格納する)\n self._size: int = 0 # リストの長さ(現在の要素数)\n self._extend_ratio: int = 2 # リストの容量を拡張する際の倍率\n\n def size(self) -&gt; int:\n \"\"\"リストの長さ(現在の要素数)を取得する\"\"\"\n return self._size\n\n def capacity(self) -&gt; int:\n \"\"\"リストの容量を取得する\"\"\"\n return self._capacity\n\n def get(self, index: int) -&gt; int:\n \"\"\"要素にアクセスする\"\"\"\n # インデックスが範囲外の場合、例外を送出する。以下も同様\n if index &lt; 0 or index &gt;= self._size:\n raise IndexError(\"インデックスが範囲外です\")\n return self._arr[index]\n\n def set(self, num: int, index: int):\n \"\"\"要素を更新する\"\"\"\n if index &lt; 0 or index &gt;= self._size:\n raise IndexError(\"インデックスが範囲外です\")\n self._arr[index] = num\n\n def add(self, num: int):\n \"\"\"末尾に要素を追加する\"\"\"\n # 要素数が容量を超える場合、容量を拡張する\n if self.size() == self.capacity():\n self.extend_capacity()\n self._arr[self._size] = num\n self._size += 1\n\n def insert(self, num: int, index: int):\n \"\"\"途中に要素を挿入する\"\"\"\n if index &lt; 0 or index &gt;= self._size:\n raise IndexError(\"インデックスが範囲外です\")\n # 要素数が容量を超える場合、容量を拡張する\n if self._size == self.capacity():\n self.extend_capacity()\n # インデックス index 以降の全要素を後ろに 1 つずらす\n for j in range(self._size - 1, index - 1, -1):\n self._arr[j + 1] = self._arr[j]\n self._arr[index] = num\n # 要素数を更新する\n self._size += 1\n\n def remove(self, index: int) -&gt; int:\n \"\"\"要素を削除する\"\"\"\n if index &lt; 0 or index &gt;= self._size:\n raise IndexError(\"インデックスが範囲外です\")\n num = self._arr[index]\n # インデックス index より後の全要素を前に 1 つずらす\n for j in range(index, self._size - 1):\n self._arr[j] = self._arr[j + 1]\n # 要素数を更新する\n self._size -= 1\n # 削除した要素を返す\n return num\n\n def extend_capacity(self):\n \"\"\"リストの容量を拡張する\"\"\"\n # 元の配列の _extend_ratio 倍の長さの配列を新たに作成し、元の配列をコピーする\n self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1)\n # リストの容量を更新する\n self._capacity = len(self._arr)\n\n def to_array(self) -&gt; list[int]:\n \"\"\"有効な長さのリストを返す\"\"\"\n return self._arr[: self._size]\n</code></pre> my_list.cpp<pre><code>/* リストクラス */\nclass MyList {\n private:\n int *arr; // 配列(リストの要素を格納する)\n int arrCapacity = 10; // リストの容量\n int arrSize = 0; // リストの長さ(現在の要素数)\n int extendRatio = 2; // リストの容量を拡張する際の倍率\n\n public:\n /* コンストラクタ */\n MyList() {\n arr = new int[arrCapacity];\n }\n\n /* デストラクタ */\n ~MyList() {\n delete[] arr;\n }\n\n /* リストの長さ(現在の要素数)を取得する*/\n int size() {\n return arrSize;\n }\n\n /* リストの容量を取得する */\n int capacity() {\n return arrCapacity;\n }\n\n /* 要素にアクセスする */\n int get(int index) {\n // インデックスが範囲外の場合、例外を送出する。以下も同様\n if (index &lt; 0 || index &gt;= size())\n throw out_of_range(\"インデックスが範囲外です\");\n return arr[index];\n }\n\n /* 要素を更新する */\n void set(int index, int num) {\n if (index &lt; 0 || index &gt;= size())\n throw out_of_range(\"インデックスが範囲外です\");\n arr[index] = num;\n }\n\n /* 末尾に要素を追加する */\n void add(int num) {\n // 要素数が容量を超える場合、容量を拡張する\n if (size() == capacity())\n extendCapacity();\n arr[size()] = num;\n // 要素数を更新する\n arrSize++;\n }\n\n /* 途中に要素を挿入する */\n void insert(int index, int num) {\n if (index &lt; 0 || index &gt;= size())\n throw out_of_range(\"インデックスが範囲外です\");\n // 要素数が容量を超える場合、容量を拡張する\n if (size() == capacity())\n extendCapacity();\n // インデックス index 以降の全要素を後ろに 1 つずらす\n for (int j = size() - 1; j &gt;= index; j--) {\n arr[j + 1] = arr[j];\n }\n arr[index] = num;\n // 要素数を更新する\n arrSize++;\n }\n\n /* 要素を削除する */\n int remove(int index) {\n if (index &lt; 0 || index &gt;= size())\n throw out_of_range(\"インデックスが範囲外です\");\n int num = arr[index];\n // インデックス index より後の全要素を前に 1 つずらす\n for (int j = index; j &lt; size() - 1; j++) {\n arr[j] = arr[j + 1];\n }\n // 要素数を更新する\n arrSize--;\n // 削除した要素を返す\n return num;\n }\n\n /* リストの容量を拡張する */\n void extendCapacity() {\n // 元の配列の extendRatio 倍の長さの新しい配列を作成する\n int newCapacity = capacity() * extendRatio;\n int *tmp = arr;\n arr = new int[newCapacity];\n // 元の配列の全要素を新しい配列にコピーする\n for (int i = 0; i &lt; size(); i++) {\n arr[i] = tmp[i];\n }\n // メモリを解放する\n delete[] tmp;\n arrCapacity = newCapacity;\n }\n\n /* 表示用にリストを Vector に変換する */\n vector&lt;int&gt; toVector() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n vector&lt;int&gt; vec(size());\n for (int i = 0; i &lt; size(); i++) {\n vec[i] = arr[i];\n }\n return vec;\n }\n};\n</code></pre> my_list.java<pre><code>/* リストクラス */\nclass MyList {\n private int[] arr; // 配列(リストの要素を格納する)\n private int capacity = 10; // リストの容量\n private int size = 0; // リストの長さ(現在の要素数)\n private int extendRatio = 2; // リストの容量を拡張する際の倍率\n\n /* コンストラクタ */\n public MyList() {\n arr = new int[capacity];\n }\n\n /* リストの長さ(現在の要素数)を取得する */\n public int size() {\n return size;\n }\n\n /* リストの容量を取得する */\n public int capacity() {\n return capacity;\n }\n\n /* 要素にアクセスする */\n public int get(int index) {\n // インデックスが範囲外の場合、例外を送出する。以下も同様\n if (index &lt; 0 || index &gt;= size)\n throw new IndexOutOfBoundsException(\"インデックスが範囲外です\");\n return arr[index];\n }\n\n /* 要素を更新する */\n public void set(int index, int num) {\n if (index &lt; 0 || index &gt;= size)\n throw new IndexOutOfBoundsException(\"インデックスが範囲外です\");\n arr[index] = num;\n }\n\n /* 末尾に要素を追加する */\n public void add(int num) {\n // 要素数が容量を超える場合、容量を拡張する\n if (size == capacity())\n extendCapacity();\n arr[size] = num;\n // 要素数を更新する\n size++;\n }\n\n /* 途中に要素を挿入する */\n public void insert(int index, int num) {\n if (index &lt; 0 || index &gt;= size)\n throw new IndexOutOfBoundsException(\"インデックスが範囲外です\");\n // 要素数が容量を超える場合、容量を拡張する\n if (size == capacity())\n extendCapacity();\n // インデックス index 以降の全要素を後ろに 1 つずらす\n for (int j = size - 1; j &gt;= index; j--) {\n arr[j + 1] = arr[j];\n }\n arr[index] = num;\n // 要素数を更新する\n size++;\n }\n\n /* 要素を削除する */\n public int remove(int index) {\n if (index &lt; 0 || index &gt;= size)\n throw new IndexOutOfBoundsException(\"インデックスが範囲外です\");\n int num = arr[index];\n // インデックス index より後の全要素を前に 1 つずらす\n for (int j = index; j &lt; size - 1; j++) {\n arr[j] = arr[j + 1];\n }\n // 要素数を更新する\n size--;\n // 削除した要素を返す\n return num;\n }\n\n /* リストの容量を拡張する */\n public void extendCapacity() {\n // 元の配列の extendRatio 倍の長さの配列を新たに作成し、元の配列をコピーする\n arr = Arrays.copyOf(arr, capacity() * extendRatio);\n // リストの容量を更新する\n capacity = arr.length;\n }\n\n /* リストを配列に変換する */\n public int[] toArray() {\n int size = size();\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n int[] arr = new int[size];\n for (int i = 0; i &lt; size; i++) {\n arr[i] = get(i);\n }\n return arr;\n }\n}\n</code></pre> my_list.cs<pre><code>/* リストクラス */\nclass MyList {\n private int[] arr; // 配列(リストの要素を格納する)\n private int arrCapacity = 10; // リストの容量\n private int arrSize = 0; // リストの長さ(現在の要素数)\n private readonly int extendRatio = 2; // リストの容量を拡張する際の倍率\n\n /* コンストラクタ */\n public MyList() {\n arr = new int[arrCapacity];\n }\n\n /* リストの長さ(現在の要素数)を取得する*/\n public int Size() {\n return arrSize;\n }\n\n /* リストの容量を取得する */\n public int Capacity() {\n return arrCapacity;\n }\n\n /* 要素にアクセスする */\n public int Get(int index) {\n // インデックスが範囲外の場合、例外を送出する。以下も同様\n if (index &lt; 0 || index &gt;= arrSize)\n throw new IndexOutOfRangeException(\"インデックスが範囲外です\");\n return arr[index];\n }\n\n /* 要素を更新する */\n public void Set(int index, int num) {\n if (index &lt; 0 || index &gt;= arrSize)\n throw new IndexOutOfRangeException(\"インデックスが範囲外です\");\n arr[index] = num;\n }\n\n /* 末尾に要素を追加する */\n public void Add(int num) {\n // 要素数が容量を超える場合、容量を拡張する\n if (arrSize == arrCapacity)\n ExtendCapacity();\n arr[arrSize] = num;\n // 要素数を更新する\n arrSize++;\n }\n\n /* 途中に要素を挿入する */\n public void Insert(int index, int num) {\n if (index &lt; 0 || index &gt;= arrSize)\n throw new IndexOutOfRangeException(\"インデックスが範囲外です\");\n // 要素数が容量を超える場合、容量を拡張する\n if (arrSize == arrCapacity)\n ExtendCapacity();\n // インデックス index 以降の全要素を後ろに 1 つずらす\n for (int j = arrSize - 1; j &gt;= index; j--) {\n arr[j + 1] = arr[j];\n }\n arr[index] = num;\n // 要素数を更新する\n arrSize++;\n }\n\n /* 要素を削除する */\n public int Remove(int index) {\n if (index &lt; 0 || index &gt;= arrSize)\n throw new IndexOutOfRangeException(\"インデックスが範囲外です\");\n int num = arr[index];\n // インデックス index より後の全要素を前に 1 つずらす\n for (int j = index; j &lt; arrSize - 1; j++) {\n arr[j] = arr[j + 1];\n }\n // 要素数を更新する\n arrSize--;\n // 削除した要素を返す\n return num;\n }\n\n /* リストの容量を拡張する */\n public void ExtendCapacity() {\n // 長さ arrCapacity * extendRatio の配列を新たに作成し、元の配列をコピーする\n Array.Resize(ref arr, arrCapacity * extendRatio);\n // リストの容量を更新する\n arrCapacity = arr.Length;\n }\n\n /* リストを配列に変換する */\n public int[] ToArray() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n int[] arr = new int[arrSize];\n for (int i = 0; i &lt; arrSize; i++) {\n arr[i] = Get(i);\n }\n return arr;\n }\n}\n</code></pre> my_list.go<pre><code>/* リストクラス */\ntype myList struct {\n arrCapacity int\n arr []int\n arrSize int\n extendRatio int\n}\n\n/* コンストラクタ */\nfunc newMyList() *myList {\n return &amp;myList{\n arrCapacity: 10, // リストの容量\n arr: make([]int, 10), // 配列(リストの要素を格納する)\n arrSize: 0, // リストの長さ(現在の要素数)\n extendRatio: 2, // リストの容量を拡張する際の倍率\n }\n}\n\n/* リストの長さ(現在の要素数)を取得する */\nfunc (l *myList) size() int {\n return l.arrSize\n}\n\n/* リストの容量を取得する */\nfunc (l *myList) capacity() int {\n return l.arrCapacity\n}\n\n/* 要素にアクセスする */\nfunc (l *myList) get(index int) int {\n // インデックスが範囲外の場合、例外を送出する。以下も同様\n if index &lt; 0 || index &gt;= l.arrSize {\n panic(\"インデックスが範囲外です\")\n }\n return l.arr[index]\n}\n\n/* 要素を更新する */\nfunc (l *myList) set(num, index int) {\n if index &lt; 0 || index &gt;= l.arrSize {\n panic(\"インデックスが範囲外です\")\n }\n l.arr[index] = num\n}\n\n/* 末尾に要素を追加する */\nfunc (l *myList) add(num int) {\n // 要素数が容量を超える場合、容量を拡張する\n if l.arrSize == l.arrCapacity {\n l.extendCapacity()\n }\n l.arr[l.arrSize] = num\n // 要素数を更新する\n l.arrSize++\n}\n\n/* 途中に要素を挿入する */\nfunc (l *myList) insert(num, index int) {\n if index &lt; 0 || index &gt;= l.arrSize {\n panic(\"インデックスが範囲外です\")\n }\n // 要素数が容量を超える場合、容量を拡張する\n if l.arrSize == l.arrCapacity {\n l.extendCapacity()\n }\n // インデックス index 以降の全要素を後ろに 1 つずらす\n for j := l.arrSize - 1; j &gt;= index; j-- {\n l.arr[j+1] = l.arr[j]\n }\n l.arr[index] = num\n // 要素数を更新する\n l.arrSize++\n}\n\n/* 要素を削除する */\nfunc (l *myList) remove(index int) int {\n if index &lt; 0 || index &gt;= l.arrSize {\n panic(\"インデックスが範囲外です\")\n }\n num := l.arr[index]\n // インデックス index より後の全要素を前に 1 つずらす\n for j := index; j &lt; l.arrSize-1; j++ {\n l.arr[j] = l.arr[j+1]\n }\n // 要素数を更新する\n l.arrSize--\n // 削除した要素を返す\n return num\n}\n\n/* リストの容量を拡張する */\nfunc (l *myList) extendCapacity() {\n // 元の配列の extendRatio 倍の長さの配列を新たに作成し、元の配列をコピーする\n l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...)\n // リストの容量を更新する\n l.arrCapacity = len(l.arr)\n}\n\n/* 有効な長さのリストを返す */\nfunc (l *myList) toArray() []int {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n return l.arr[:l.arrSize]\n}\n</code></pre> my_list.swift<pre><code>/* リストクラス */\nclass MyList {\n private var arr: [Int] // 配列(リストの要素を格納する)\n private var _capacity: Int // リストの容量\n private var _size: Int // リストの長さ(現在の要素数)\n private let extendRatio: Int // リストの容量を拡張する際の倍率\n\n /* コンストラクタ */\n init() {\n _capacity = 10\n _size = 0\n extendRatio = 2\n arr = Array(repeating: 0, count: _capacity)\n }\n\n /* リストの長さ(現在の要素数)を取得する*/\n func size() -&gt; Int {\n _size\n }\n\n /* リストの容量を取得する */\n func capacity() -&gt; Int {\n _capacity\n }\n\n /* 要素にアクセスする */\n func get(index: Int) -&gt; Int {\n // インデックスが範囲外の場合、エラーを送出する。以下も同様\n if index &lt; 0 || index &gt;= size() {\n fatalError(\"インデックスが範囲外です\")\n }\n return arr[index]\n }\n\n /* 要素を更新する */\n func set(index: Int, num: Int) {\n if index &lt; 0 || index &gt;= size() {\n fatalError(\"インデックスが範囲外です\")\n }\n arr[index] = num\n }\n\n /* 末尾に要素を追加する */\n func add(num: Int) {\n // 要素数が容量を超える場合、容量を拡張する\n if size() == capacity() {\n extendCapacity()\n }\n arr[size()] = num\n // 要素数を更新する\n _size += 1\n }\n\n /* 途中に要素を挿入する */\n func insert(index: Int, num: Int) {\n if index &lt; 0 || index &gt;= size() {\n fatalError(\"インデックスが範囲外です\")\n }\n // 要素数が容量を超える場合、容量を拡張する\n if size() == capacity() {\n extendCapacity()\n }\n // インデックス index 以降の全要素を後ろに 1 つずらす\n for j in (index ..&lt; size()).reversed() {\n arr[j + 1] = arr[j]\n }\n arr[index] = num\n // 要素数を更新する\n _size += 1\n }\n\n /* 要素を削除する */\n @discardableResult\n func remove(index: Int) -&gt; Int {\n if index &lt; 0 || index &gt;= size() {\n fatalError(\"インデックスが範囲外です\")\n }\n let num = arr[index]\n // インデックス index より後の全要素を前に 1 つずらす\n for j in index ..&lt; (size() - 1) {\n arr[j] = arr[j + 1]\n }\n // 要素数を更新する\n _size -= 1\n // 削除した要素を返す\n return num\n }\n\n /* リストの容量を拡張する */\n func extendCapacity() {\n // 元の配列の extendRatio 倍の長さの配列を新たに作成し、元の配列をコピーする\n arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1))\n // リストの容量を更新する\n _capacity = arr.count\n }\n\n /* リストを配列に変換する */\n func toArray() -&gt; [Int] {\n Array(arr.prefix(size()))\n }\n}\n</code></pre> my_list.js<pre><code>/* リストクラス */\nclass MyList {\n #arr = new Array(); // 配列(リストの要素を格納する)\n #capacity = 10; // リストの容量\n #size = 0; // リストの長さ(現在の要素数)\n #extendRatio = 2; // リストの容量を拡張する際の倍率\n\n /* コンストラクタ */\n constructor() {\n this.#arr = new Array(this.#capacity);\n }\n\n /* リストの長さ(現在の要素数)を取得する*/\n size() {\n return this.#size;\n }\n\n /* リストの容量を取得する */\n capacity() {\n return this.#capacity;\n }\n\n /* 要素にアクセスする */\n get(index) {\n // インデックスが範囲外の場合、例外を送出する。以下も同様\n if (index &lt; 0 || index &gt;= this.#size) throw new Error('インデックスが範囲外です');\n return this.#arr[index];\n }\n\n /* 要素を更新する */\n set(index, num) {\n if (index &lt; 0 || index &gt;= this.#size) throw new Error('インデックスが範囲外です');\n this.#arr[index] = num;\n }\n\n /* 末尾に要素を追加する */\n add(num) {\n // 長さが容量に等しい場合、容量を拡張する必要がある\n if (this.#size === this.#capacity) {\n this.extendCapacity();\n }\n // 新しい要素をリストの末尾に追加する\n this.#arr[this.#size] = num;\n this.#size++;\n }\n\n /* 途中に要素を挿入する */\n insert(index, num) {\n if (index &lt; 0 || index &gt;= this.#size) throw new Error('インデックスが範囲外です');\n // 要素数が容量を超える場合、容量を拡張する\n if (this.#size === this.#capacity) {\n this.extendCapacity();\n }\n // インデックス index 以降の全要素を後ろに 1 つずらす\n for (let j = this.#size - 1; j &gt;= index; j--) {\n this.#arr[j + 1] = this.#arr[j];\n }\n // 要素数を更新する\n this.#arr[index] = num;\n this.#size++;\n }\n\n /* 要素を削除する */\n remove(index) {\n if (index &lt; 0 || index &gt;= this.#size) throw new Error('インデックスが範囲外です');\n let num = this.#arr[index];\n // インデックス index より後の全要素を前に 1 つずらす\n for (let j = index; j &lt; this.#size - 1; j++) {\n this.#arr[j] = this.#arr[j + 1];\n }\n // 要素数を更新する\n this.#size--;\n // 削除した要素を返す\n return num;\n }\n\n /* リストの容量を拡張する */\n extendCapacity() {\n // 元の配列の extendRatio 倍の長さの配列を新たに作成し、元の配列をコピーする\n this.#arr = this.#arr.concat(\n new Array(this.capacity() * (this.#extendRatio - 1))\n );\n // リストの容量を更新する\n this.#capacity = this.#arr.length;\n }\n\n /* リストを配列に変換する */\n toArray() {\n let size = this.size();\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n const arr = new Array(size);\n for (let i = 0; i &lt; size; i++) {\n arr[i] = this.get(i);\n }\n return arr;\n }\n}\n</code></pre> my_list.ts<pre><code>/* リストクラス */\nclass MyList {\n private arr: Array&lt;number&gt;; // 配列(リストの要素を格納する)\n private _capacity: number = 10; // リストの容量\n private _size: number = 0; // リストの長さ(現在の要素数)\n private extendRatio: number = 2; // リストの容量を拡張する際の倍率\n\n /* コンストラクタ */\n constructor() {\n this.arr = new Array(this._capacity);\n }\n\n /* リストの長さ(現在の要素数)を取得する*/\n public size(): number {\n return this._size;\n }\n\n /* リストの容量を取得する */\n public capacity(): number {\n return this._capacity;\n }\n\n /* 要素にアクセスする */\n public get(index: number): number {\n // インデックスが範囲外の場合、例外を送出する。以下も同様\n if (index &lt; 0 || index &gt;= this._size) throw new Error('インデックスが範囲外です');\n return this.arr[index];\n }\n\n /* 要素を更新する */\n public set(index: number, num: number): void {\n if (index &lt; 0 || index &gt;= this._size) throw new Error('インデックスが範囲外です');\n this.arr[index] = num;\n }\n\n /* 末尾に要素を追加する */\n public add(num: number): void {\n // 長さが容量に等しい場合、容量を拡張する必要がある\n if (this._size === this._capacity) this.extendCapacity();\n // 新しい要素をリストの末尾に追加する\n this.arr[this._size] = num;\n this._size++;\n }\n\n /* 途中に要素を挿入する */\n public insert(index: number, num: number): void {\n if (index &lt; 0 || index &gt;= this._size) throw new Error('インデックスが範囲外です');\n // 要素数が容量を超える場合、容量を拡張する\n if (this._size === this._capacity) {\n this.extendCapacity();\n }\n // インデックス index 以降の全要素を後ろに 1 つずらす\n for (let j = this._size - 1; j &gt;= index; j--) {\n this.arr[j + 1] = this.arr[j];\n }\n // 要素数を更新する\n this.arr[index] = num;\n this._size++;\n }\n\n /* 要素を削除する */\n public remove(index: number): number {\n if (index &lt; 0 || index &gt;= this._size) throw new Error('インデックスが範囲外です');\n let num = this.arr[index];\n // インデックス index より後の全要素を前に 1 つずらす\n for (let j = index; j &lt; this._size - 1; j++) {\n this.arr[j] = this.arr[j + 1];\n }\n // 要素数を更新する\n this._size--;\n // 削除した要素を返す\n return num;\n }\n\n /* リストの容量を拡張する */\n public extendCapacity(): void {\n // 長さ size の配列を新たに作成し、元の配列をコピーする\n this.arr = this.arr.concat(\n new Array(this.capacity() * (this.extendRatio - 1))\n );\n // リストの容量を更新する\n this._capacity = this.arr.length;\n }\n\n /* リストを配列に変換する */\n public toArray(): number[] {\n let size = this.size();\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n const arr = new Array(size);\n for (let i = 0; i &lt; size; i++) {\n arr[i] = this.get(i);\n }\n return arr;\n }\n}\n</code></pre> my_list.dart<pre><code>/* リストクラス */\nclass MyList {\n late List&lt;int&gt; _arr; // 配列(リストの要素を格納する)\n int _capacity = 10; // リストの容量\n int _size = 0; // リストの長さ(現在の要素数)\n int _extendRatio = 2; // リストの容量を拡張する際の倍率\n\n /* コンストラクタ */\n MyList() {\n _arr = List.filled(_capacity, 0);\n }\n\n /* リストの長さ(現在の要素数)を取得する*/\n int size() =&gt; _size;\n\n /* リストの容量を取得する */\n int capacity() =&gt; _capacity;\n\n /* 要素にアクセスする */\n int get(int index) {\n if (index &gt;= _size) throw RangeError('インデックスが範囲外です');\n return _arr[index];\n }\n\n /* 要素を更新する */\n void set(int index, int _num) {\n if (index &gt;= _size) throw RangeError('インデックスが範囲外です');\n _arr[index] = _num;\n }\n\n /* 末尾に要素を追加する */\n void add(int _num) {\n // 要素数が容量を超える場合、容量を拡張する\n if (_size == _capacity) extendCapacity();\n _arr[_size] = _num;\n // 要素数を更新する\n _size++;\n }\n\n /* 途中に要素を挿入する */\n void insert(int index, int _num) {\n if (index &gt;= _size) throw RangeError('インデックスが範囲外です');\n // 要素数が容量を超える場合、容量を拡張する\n if (_size == _capacity) extendCapacity();\n // インデックス index 以降の全要素を後ろに 1 つずらす\n for (var j = _size - 1; j &gt;= index; j--) {\n _arr[j + 1] = _arr[j];\n }\n _arr[index] = _num;\n // 要素数を更新する\n _size++;\n }\n\n /* 要素を削除する */\n int remove(int index) {\n if (index &gt;= _size) throw RangeError('インデックスが範囲外です');\n int _num = _arr[index];\n // インデックス index より後の全要素を前に 1 つずらす\n for (var j = index; j &lt; _size - 1; j++) {\n _arr[j] = _arr[j + 1];\n }\n // 要素数を更新する\n _size--;\n // 削除した要素を返す\n return _num;\n }\n\n /* リストの容量を拡張する */\n void extendCapacity() {\n // 元の配列の _extendRatio 倍の長さの新しい配列を作成する\n final _newNums = List.filled(_capacity * _extendRatio, 0);\n // 元の配列を新しい配列にコピーする\n List.copyRange(_newNums, 0, _arr);\n // _arr の参照を更新する\n _arr = _newNums;\n // リストの容量を更新する\n _capacity = _arr.length;\n }\n\n /* リストを配列に変換する */\n List&lt;int&gt; toArray() {\n List&lt;int&gt; arr = [];\n for (var i = 0; i &lt; _size; i++) {\n arr.add(get(i));\n }\n return arr;\n }\n}\n</code></pre> my_list.rs<pre><code>/* リストクラス */\n#[allow(dead_code)]\nstruct MyList {\n arr: Vec&lt;i32&gt;, // 配列(リストの要素を格納する)\n capacity: usize, // リストの容量\n size: usize, // リストの長さ(現在の要素数)\n extend_ratio: usize, // リストの容量を拡張する際の倍率\n}\n\n#[allow(unused, unused_comparisons)]\nimpl MyList {\n /* コンストラクタ */\n pub fn new(capacity: usize) -&gt; Self {\n let mut vec = vec![0; capacity];\n Self {\n arr: vec,\n capacity,\n size: 0,\n extend_ratio: 2,\n }\n }\n\n /* リストの長さ(現在の要素数)を取得する*/\n pub fn size(&amp;self) -&gt; usize {\n return self.size;\n }\n\n /* リストの容量を取得する */\n pub fn capacity(&amp;self) -&gt; usize {\n return self.capacity;\n }\n\n /* 要素にアクセスする */\n pub fn get(&amp;self, index: usize) -&gt; i32 {\n // インデックスが範囲外の場合、例外を送出する。以下も同様\n if index &gt;= self.size {\n panic!(\"インデックスが範囲外です\")\n };\n return self.arr[index];\n }\n\n /* 要素を更新する */\n pub fn set(&amp;mut self, index: usize, num: i32) {\n if index &gt;= self.size {\n panic!(\"インデックスが範囲外です\")\n };\n self.arr[index] = num;\n }\n\n /* 末尾に要素を追加する */\n pub fn add(&amp;mut self, num: i32) {\n // 要素数が容量を超える場合、容量を拡張する\n if self.size == self.capacity() {\n self.extend_capacity();\n }\n self.arr[self.size] = num;\n // 要素数を更新する\n self.size += 1;\n }\n\n /* 途中に要素を挿入する */\n pub fn insert(&amp;mut self, index: usize, num: i32) {\n if index &gt;= self.size() {\n panic!(\"インデックスが範囲外です\")\n };\n // 要素数が容量を超える場合、容量を拡張する\n if self.size == self.capacity() {\n self.extend_capacity();\n }\n // インデックス index 以降の全要素を後ろに 1 つずらす\n for j in (index..self.size).rev() {\n self.arr[j + 1] = self.arr[j];\n }\n self.arr[index] = num;\n // 要素数を更新する\n self.size += 1;\n }\n\n /* 要素を削除する */\n pub fn remove(&amp;mut self, index: usize) -&gt; i32 {\n if index &gt;= self.size() {\n panic!(\"インデックスが範囲外です\")\n };\n let num = self.arr[index];\n // インデックス index より後の全要素を前に 1 つずらす\n for j in index..self.size - 1 {\n self.arr[j] = self.arr[j + 1];\n }\n // 要素数を更新する\n self.size -= 1;\n // 削除した要素を返す\n return num;\n }\n\n /* リストの容量を拡張する */\n pub fn extend_capacity(&amp;mut self) {\n // 元の配列の extend_ratio 倍の長さの配列を新たに作成し、元の配列をコピーする\n let new_capacity = self.capacity * self.extend_ratio;\n self.arr.resize(new_capacity, 0);\n // リストの容量を更新する\n self.capacity = new_capacity;\n }\n\n /* リストを配列に変換する */\n pub fn to_array(&amp;self) -&gt; Vec&lt;i32&gt; {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n let mut arr = Vec::new();\n for i in 0..self.size {\n arr.push(self.get(i));\n }\n arr\n }\n}\n</code></pre> my_list.c<pre><code>/* リストクラス */\ntypedef struct {\n int *arr; // 配列(リストの要素を格納する)\n int capacity; // リストの容量\n int size; // リストのサイズ\n int extendRatio; // リストを拡張する際の容量の倍率\n} MyList;\n\n/* コンストラクタ */\nMyList *newMyList() {\n MyList *nums = malloc(sizeof(MyList));\n nums-&gt;capacity = 10;\n nums-&gt;arr = malloc(sizeof(int) * nums-&gt;capacity);\n nums-&gt;size = 0;\n nums-&gt;extendRatio = 2;\n return nums;\n}\n\n/* デストラクタ */\nvoid delMyList(MyList *nums) {\n free(nums-&gt;arr);\n free(nums);\n}\n\n/* リストの長さを取得する */\nint size(MyList *nums) {\n return nums-&gt;size;\n}\n\n/* リストの容量を取得する */\nint capacity(MyList *nums) {\n return nums-&gt;capacity;\n}\n\n/* 要素にアクセスする */\nint get(MyList *nums, int index) {\n assert(index &gt;= 0 &amp;&amp; index &lt; nums-&gt;size);\n return nums-&gt;arr[index];\n}\n\n/* 要素を更新する */\nvoid set(MyList *nums, int index, int num) {\n assert(index &gt;= 0 &amp;&amp; index &lt; nums-&gt;size);\n nums-&gt;arr[index] = num;\n}\n\n/* 末尾に要素を追加する */\nvoid add(MyList *nums, int num) {\n if (size(nums) == capacity(nums)) {\n extendCapacity(nums); // 容量を拡張する\n }\n nums-&gt;arr[size(nums)] = num;\n nums-&gt;size++;\n}\n\n/* 途中に要素を挿入する */\nvoid insert(MyList *nums, int index, int num) {\n assert(index &gt;= 0 &amp;&amp; index &lt; size(nums));\n // 要素数が容量を超える場合、容量を拡張する\n if (size(nums) == capacity(nums)) {\n extendCapacity(nums); // 容量を拡張する\n }\n for (int i = size(nums); i &gt; index; --i) {\n nums-&gt;arr[i] = nums-&gt;arr[i - 1];\n }\n nums-&gt;arr[index] = num;\n nums-&gt;size++;\n}\n\n/* 要素を削除する */\n// 注意:stdio.h で remove という名前が使われている\nint removeItem(MyList *nums, int index) {\n assert(index &gt;= 0 &amp;&amp; index &lt; size(nums));\n int num = nums-&gt;arr[index];\n for (int i = index; i &lt; size(nums) - 1; i++) {\n nums-&gt;arr[i] = nums-&gt;arr[i + 1];\n }\n nums-&gt;size--;\n return num;\n}\n\n/* リストの容量を拡張する */\nvoid extendCapacity(MyList *nums) {\n // 先に領域を確保する\n int newCapacity = capacity(nums) * nums-&gt;extendRatio;\n int *extend = (int *)malloc(sizeof(int) * newCapacity);\n int *temp = nums-&gt;arr;\n\n // 古いデータを新しい領域にコピーする\n for (int i = 0; i &lt; size(nums); i++)\n extend[i] = nums-&gt;arr[i];\n\n // 古いデータの領域を解放する\n free(temp);\n\n // 新しいデータを参照するよう更新する\n nums-&gt;arr = extend;\n nums-&gt;capacity = newCapacity;\n}\n\n/* 表示用にリストを Array に変換する */\nint *toArray(MyList *nums) {\n return nums-&gt;arr;\n}\n</code></pre> my_list.kt<pre><code>/* リストクラス */\nclass MyList {\n private var arr: IntArray = intArrayOf() // 配列(リストの要素を格納する)\n private var capacity: Int = 10 // リストの容量\n private var size: Int = 0 // リストの長さ(現在の要素数)\n private var extendRatio: Int = 2 // リストの容量を拡張する際の倍率\n\n /* コンストラクタ */\n init {\n arr = IntArray(capacity)\n }\n\n /* リストの長さ(現在の要素数)を取得する */\n fun size(): Int {\n return size\n }\n\n /* リストの容量を取得する */\n fun capacity(): Int {\n return capacity\n }\n\n /* 要素にアクセスする */\n fun get(index: Int): Int {\n // インデックスが範囲外の場合、例外を送出する。以下も同様\n if (index &lt; 0 || index &gt;= size)\n throw IndexOutOfBoundsException(\"インデックスが範囲外です\")\n return arr[index]\n }\n\n /* 要素を更新する */\n fun set(index: Int, num: Int) {\n if (index &lt; 0 || index &gt;= size)\n throw IndexOutOfBoundsException(\"インデックスが範囲外です\")\n arr[index] = num\n }\n\n /* 末尾に要素を追加する */\n fun add(num: Int) {\n // 要素数が容量を超える場合、容量を拡張する\n if (size == capacity())\n extendCapacity()\n arr[size] = num\n // 要素数を更新する\n size++\n }\n\n /* 途中に要素を挿入する */\n fun insert(index: Int, num: Int) {\n if (index &lt; 0 || index &gt;= size)\n throw IndexOutOfBoundsException(\"インデックスが範囲外です\")\n // 要素数が容量を超える場合、容量を拡張する\n if (size == capacity())\n extendCapacity()\n // インデックス index 以降の全要素を後ろに 1 つずらす\n for (j in size - 1 downTo index)\n arr[j + 1] = arr[j]\n arr[index] = num\n // 要素数を更新する\n size++\n }\n\n /* 要素を削除する */\n fun remove(index: Int): Int {\n if (index &lt; 0 || index &gt;= size)\n throw IndexOutOfBoundsException(\"インデックスが範囲外です\")\n val num = arr[index]\n // インデックス index より後の全要素を前に 1 つずらす\n for (j in index..&lt;size - 1)\n arr[j] = arr[j + 1]\n // 要素数を更新する\n size--\n // 削除した要素を返す\n return num\n }\n\n /* リストの容量を拡張する */\n fun extendCapacity() {\n // 元の配列の extendRatio 倍の長さの配列を新たに作成し、元の配列をコピーする\n arr = arr.copyOf(capacity() * extendRatio)\n // リストの容量を更新する\n capacity = arr.size\n }\n\n /* リストを配列に変換する */\n fun toArray(): IntArray {\n val size = size()\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n val arr = IntArray(size)\n for (i in 0..&lt;size) {\n arr[i] = get(i)\n }\n return arr\n }\n}\n</code></pre> my_list.rb<pre><code>### リストクラス ###\nclass MyList\n attr_reader :size # リストの長さ(現在の要素数)を取得する\n attr_reader :capacity # リストの容量を取得する\n\n ### コンストラクタ ###\n def initialize\n @capacity = 10\n @size = 0\n @extend_ratio = 2\n @arr = Array.new(capacity)\n end\n\n ### 要素にアクセスする ###\n def get(index)\n # インデックスが範囲外の場合、例外を送出する。以下も同様\n raise IndexError, \"インデックスが範囲外です\" if index &lt; 0 || index &gt;= size\n @arr[index]\n end\n\n ### 要素にアクセスする ###\n def set(index, num)\n raise IndexError, \"インデックスが範囲外です\" if index &lt; 0 || index &gt;= size\n @arr[index] = num\n end\n\n ### 末尾に要素を追加する ###\n def add(num)\n # 要素数が容量を超える場合、容量を拡張する\n extend_capacity if size == capacity\n @arr[size] = num\n\n # 要素数を更新する\n @size += 1\n end\n\n ### 途中に要素を挿入する ###\n def insert(index, num)\n raise IndexError, \"インデックスが範囲外です\" if index &lt; 0 || index &gt;= size\n\n # 要素数が容量を超える場合、容量を拡張する\n extend_capacity if size == capacity\n\n # インデックス index 以降の全要素を後ろに 1 つずらす\n for j in (size - 1).downto(index)\n @arr[j + 1] = @arr[j]\n end\n @arr[index] = num\n\n # 要素数を更新する\n @size += 1\n end\n\n ### 要素を削除する ###\n def remove(index)\n raise IndexError, \"インデックスが範囲外です\" if index &lt; 0 || index &gt;= size\n num = @arr[index]\n\n # インデックス index より後の全要素を前に 1 つずらす\n for j in index...size\n @arr[j] = @arr[j + 1]\n end\n\n # 要素数を更新する\n @size -= 1\n\n # 削除した要素を返す\n num\n end\n\n ### リストの容量を拡張する ###\n def extend_capacity\n # 元の配列の extend_ratio 倍の長さの配列を新たに作成し、元の配列をコピーする\n arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1))\n # リストの容量を更新する\n @capacity = arr.length\n end\n\n ### リストを配列に変換する ###\n def to_array\n sz = size\n # 有効な長さの範囲内にあるリストの要素だけを変換する\n arr = Array.new(sz)\n for i in 0...sz\n arr[i] = get(i)\n end\n arr\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 4 章   配列と連結リスト","4.3   リスト"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/","level":1,"title":"4.4   メモリとキャッシュ *","text":"<p>この章の最初の 2 節では、基本的で重要なデータ構造である配列と連結リストを取り上げました。これらはそれぞれ、「連続した格納」と「分散した格納」という二つの物理構造を表しています。</p> <p>実際には、物理構造によって、プログラムがメモリとキャッシュをどれだけ効率よく使えるかが大きく決まり、ひいてはアルゴリズムを実装したプログラム全体の性能にも影響します。</p>","path":["第 4 章   配列と連結リスト","4.4   メモリとキャッシュ *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#441","level":2,"title":"4.4.1   コンピュータの記憶装置","text":"<p>コンピュータには、ハードディスク(hard disk)、メモリ(random-access memory, RAM)、キャッシュ(cache memory)という 3 種類の記憶装置があります。表 4-2 に、コンピュータシステムにおけるそれぞれの役割と性能上の特徴を示します。</p> <p> 表 4-2   コンピュータの記憶装置 </p> ハードディスク メモリ キャッシュ 用途 OS、プログラム、ファイルなどのデータを長期間保存する 実行中のプログラムと処理中のデータを一時的に保存する 頻繁にアクセスするデータや命令を保存し、CPU がメモリにアクセスする回数を減らす 揮発性 電源を切ってもデータは失われない 電源を切るとデータが失われる 電源を切るとデータが失われる 容量 大きい、TB 単位 小さい、GB 単位 非常に小さい、MB 単位 速度 遅い、数百〜数千 MB/s 速い、数十 GB/s 非常に速い、数十〜数百 GB/s 価格(人民元) 安い、1 GB 当たり数角〜数元 高い、1 GB 当たり数十〜数百元 非常に高い、CPU に含めて価格が設定される <p>コンピュータの記憶システムは、図 4-9 のようなピラミッドとして捉えられます。頂点に近い記憶装置ほど高速で、容量が小さく、コストが高くなります。この階層的な設計は偶然生まれたものではなく、コンピュータ科学者や技術者が十分に考え抜いた結果です。</p> <ul> <li>ハードディスクをメモリで置き換えるのは困難です。まず、メモリのデータは電源を切ると失われるため、長期保存には適していません。また、メモリのコストはハードディスクの数十倍であり、一般消費者向けに普及させるのが難しくなります。</li> <li>キャッシュの大容量化と高速化は両立が困難です。L1、L2、L3 とキャッシュの容量が増えるにつれて物理的なサイズも大きくなり、CPU コアからの物理的な距離が遠くなります。その結果、データ転送にかかる時間が増え、要素へのアクセスの遅延も大きくなります。現在の技術では、キャッシュを階層化する構造が、容量、速度、コストの最もよいバランスを実現しています。</li> </ul> <p></p> <p> 図 4-9   コンピュータの記憶システム </p> <p>ヒント</p> <p>コンピュータの記憶階層は、速度、容量、コストの絶妙なバランスを表しています。実際、このようなトレードオフはあらゆる産業分野に存在し、それぞれの利点と制約の間で最適なバランスを見つけることが求められます。</p> <p>全体として、ハードディスクは大量のデータの長期保存に、メモリはプログラムの実行中に処理しているデータの一時保存に、キャッシュは頻繁にアクセスするデータや命令の保存に使われます。これらが連携してプログラムの実行効率を高め、コンピュータシステムの効率的な動作を支えます。</p> <p>図 4-10 のように、プログラムの実行時にはデータがハードディスクからメモリへ読み込まれ、CPU の計算に使われます。キャッシュは CPU の一部と考えることができます。メモリから適切にデータを読み込むことで、CPU に高速なデータ読み出しを提供し、プログラムの実行効率を大きく高め、比較的遅いメモリへの依存を減らします。</p> <p></p> <p> 図 4-10   ハードディスク、メモリ、キャッシュ間のデータの流れ </p>","path":["第 4 章   配列と連結リスト","4.4   メモリとキャッシュ *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#442","level":2,"title":"4.4.2   データ構造のメモリ効率","text":"<p>メモリ空間の利用という点では、配列と連結リストにはそれぞれ利点と制約があります。</p> <p>一つには、メモリは有限であり、同じメモリ領域を複数のプログラムで共有することはできないため、データ構造にはできるだけ効率よく空間を使ってほしいものです。配列の要素は隙間なく並んでおり、連結リストのノード間の参照(ポインタ)を保存する追加の空間が必要ないため、空間効率が高くなります。ただし、配列は十分な連続メモリ領域を一度に確保する必要があり、メモリを無駄にする可能性があります。また、配列の容量拡張には追加の時間と空間のコストがかかります。一方、連結リストは「ノード」単位でメモリを動的に確保・解放できるため、柔軟性に優れています。</p> <p>もう一つには、プログラムの実行中、メモリの確保と解放を繰り返すにつれて、空きメモリの断片化が進み、メモリの利用効率が低下します。配列は連続して格納されるため、比較的断片化を起こしにくくなります。対して連結リストの要素は分散して格納されるため、挿入と削除を頻繁に行うと、メモリの断片化が起きやすくなります。</p>","path":["第 4 章   配列と連結リスト","4.4   メモリとキャッシュ *"],"tags":[]},{"location":"chapter_array_and_linkedlist/ram_and_cache/#443","level":2,"title":"4.4.3   データ構造のキャッシュ効率","text":"<p>キャッシュはメモリより容量がはるかに小さいものの、非常に高速であり、プログラムの実行速度に決定的な役割を果たします。容量が限られているため、頻繁にアクセスするデータの一部しか保存できません。CPU がアクセスしようとしたデータがキャッシュにないと、キャッシュミス(cache miss)が発生し、CPU は必要なデータを比較的遅いメモリから読み込まなければなりません。</p> <p>当然、「キャッシュミス」が少ないほど CPU のデータ読み書きの効率が高くなり、プログラムの性能も向上します。CPU がキャッシュからデータを取得できた割合をキャッシュヒット率(cache hit rate)といい、通常はキャッシュの効率を測る指標として使います。</p> <p>効率をできるだけ高めるため、キャッシュは次のような仕組みでデータを読み込みます。</p> <ul> <li>キャッシュライン:キャッシュはデータを 1 バイトずつ保存・読み込みするのではなく、キャッシュライン単位で扱います。1 バイトずつ転送するよりも、キャッシュライン単位で転送する方が効率的です。</li> <li>プリフェッチ:プロセッサはデータへのアクセスパターン(順次アクセス、一定間隔で飛び飛びに行うアクセスなど)を予測し、そのパターンに基づいてデータをキャッシュに読み込み、ヒット率を高めます。</li> <li>空間的局所性:あるデータにアクセスすると、その近くのデータにも近いうちにアクセスする可能性があります。そのため、キャッシュはあるデータを読み込むときに周辺のデータも読み込み、ヒット率を高めます。</li> <li>時間的局所性:あるデータにアクセスすると、近い将来に再びアクセスする可能性が高くなります。キャッシュはこの性質を利用し、最近アクセスしたデータを保持してヒット率を高めます。</li> </ul> <p>実際、配列と連結リストではキャッシュの利用効率が異なります。主な違いは次のとおりです。</p> <ul> <li>使用する空間:連結リストの要素は配列の要素より多くの空間を使うため、キャッシュに収まる有効なデータの量が少なくなります。</li> <li>キャッシュライン:連結リストのデータはメモリの各所に分散していますが、キャッシュは「ライン単位で読み込む」ため、不要なデータを読み込む割合が高くなります。</li> <li>プリフェッチ:配列のデータアクセスパターンは連結リストより「予測しやすく」、次に読み込むデータをシステムが推測しやすくなります。</li> <li>空間的局所性:配列はまとまったメモリ領域に格納されるため、読み込んだデータの近くにあるデータにも、まもなくアクセスする可能性が高くなります。</li> </ul> <p>全体として、配列はキャッシュヒット率が高いため、通常は連結リストより操作効率に優れています。そのため、アルゴリズムの問題を解く際には、配列を使って実装したデータ構造が好まれる傾向にあります。</p> <p>ただし、キャッシュ効率が高いからといって、あらゆる場合に配列が連結リストより優れているわけではありません。実際の用途では、具体的な要件に応じてデータ構造を選ぶ必要があります。たとえば、「スタック」というデータ構造(次の章で詳しく説明します)は配列でも連結リストでも実装できますが、それぞれに適した場面は異なります。</p> <ul> <li>アルゴリズムの問題を解くときは、操作効率が高くランダムアクセスも可能な、配列によるスタックを選ぶことが多いです。その代わりに必要なのは、あらかじめ配列用のメモリ領域をある程度確保しておくことだけです。</li> <li>データ量が非常に大きく、変動が激しく、スタックの大きさを予測しにくい場合には、連結リストによるスタックが適しています。連結リストなら大量のデータをメモリの各所に分散して格納でき、配列の容量拡張に伴う追加のコストも避けられます。</li> </ul>","path":["第 4 章   配列と連結リスト","4.4   メモリとキャッシュ *"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/","level":1,"title":"4.5   まとめ","text":"","path":["第 4 章   配列と連結リスト","4.5   まとめ"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>配列と連結リストは二つの基本的なデータ構造であり、コンピュータのメモリにデータを格納する二つの方法、すなわち連続した領域への格納と分散した領域への格納をそれぞれ表します。両者の特徴は互いに補い合う関係にあります。</li> <li>配列はランダムアクセスをサポートし、メモリ使用量も少なく済みます。ただし、要素の挿入と削除の効率は低く、初期化後に長さを変えることはできません。</li> <li>連結リストは参照(ポインタ)の変更によりノードを効率よく挿入・削除でき、長さも柔軟に調整できます。ただし、ノードへのアクセス効率は低く、メモリ使用量が多くなります。代表的な種類には、単方向連結リスト、循環連結リスト、双方向連結リストがあります。</li> <li>リストは、挿入・削除・検索・更新に対応する、順序を持つ要素の集合です。通常は動的配列で実装されます。配列の利点を保ちながら、長さを柔軟に調整できます。</li> <li>リストの登場により配列の実用性は大幅に向上しましたが、メモリ領域の一部が無駄になる可能性もあります。</li> <li>プログラムの実行時、データは主にメモリに格納されます。配列はメモリ空間を効率よく使えますが、連結リストはメモリをより柔軟に使えます。</li> <li>キャッシュは、キャッシュライン、プリフェッチ、空間的局所性、時間的局所性などの仕組みでデータを読み込み、CPU に高速なデータアクセスを提供して、プログラムの実行効率を大幅に高めます。</li> <li>配列はキャッシュヒット率が高いため、通常は連結リストより効率的です。データ構造は、具体的な要件や場面に応じて適切に選ぶ必要があります。</li> </ul>","path":["第 4 章   配列と連結リスト","4.5   まとめ"],"tags":[]},{"location":"chapter_array_and_linkedlist/summary/#2-q-a","level":3,"title":"2.   Q &amp; A","text":"<p>Q:配列をスタックに置く場合とヒープに置く場合で、時間効率や空間効率に違いはありますか。</p> <p>スタック上でもヒープ上でも、配列は連続したメモリ領域に格納されるため、データ操作の効率は基本的に同じです。ただし、スタックとヒープにはそれぞれ特徴があり、次のような違いが生じます。</p> <ol> <li>確保と解放の効率:スタックは比較的小さなメモリ領域で、割り当てはコンパイラが自動的に行います。一方、ヒープは比較的大きく、コードから動的に確保できる反面、断片化しやすくなります。そのため、ヒープでの確保と解放は通常、スタックより遅くなります。</li> <li>サイズの制限:スタックのメモリは比較的小さく、ヒープの大きさは通常、利用可能なメモリに制限されます。そのため、大きな配列を格納するにはヒープの方が適しています。</li> <li>柔軟性:スタック上の配列はコンパイル時に大きさを決める必要がありますが、ヒープ上の配列は実行時に動的に決められます。</li> </ol> <p>Q:配列では要素が同じ型でなければならないのに、連結リストでは同じ型であることを強調しないのはなぜですか。</p> <p>連結リストはノードで構成され、ノード同士が参照(ポインタ)でつながっています。各ノードには <code>int</code>、<code>double</code>、<code>string</code>、<code>object</code> など、異なる型のデータを格納できます。</p> <p>一方、配列ではオフセットを計算して要素の位置を求めるため、要素が同じ型でなければなりません。たとえば配列に <code>int</code> と <code>long</code> の両方が含まれ、1 要素の大きさがそれぞれ 4 バイトと 8 バイトだとします。この場合は「要素の長さ」が 2 種類あるため、次の式でオフセットを計算できなくなります。</p> <pre><code># 要素のメモリアドレス = 配列のメモリアドレス(先頭要素のメモリアドレス) + 要素の長さ * 要素のインデックス\n</code></pre> <p>Q:ノード <code>P</code> を削除した後、<code>P.next</code> を <code>None</code> にする必要はありますか。</p> <p><code>P.next</code> は変更しなくてもかまいません。その連結リストから見ると、先頭ノードから末尾ノードまで走査しても、もはや <code>P</code> には出会いません。つまり、ノード <code>P</code> はすでに連結リストから削除されており、どこを指していてもその連結リストには影響しません。</p> <p>データ構造とアルゴリズムの問題を解くという観点では、プログラムのロジックが正しければ、参照を切らなくても問題ありません。標準ライブラリという観点では、切り離す方が安全で、ロジックも明確になります。参照を切らず、削除したノードが適切に回収されなかった場合、その後続ノードのメモリ回収に影響します。</p> <p>Q:連結リストの挿入と削除の時間計算量は \\(O(1)\\) ですが、その前に要素を探すのに \\(O(n)\\) の時間がかかります。それなら時間計算量は \\(O(n)\\) ではないのでしょうか。</p> <p>先に要素を探してから削除するなら、時間計算量は確かに \\(O(n)\\) です。ただし、連結リストでの挿入・削除が \\(O(1)\\) である利点は、ほかの用途で活かせます。たとえば、両端キューは連結リストでの実装に適しています。先頭ノードと末尾ノードを常に指すポインタ変数を保持すれば、各回の挿入と削除は \\(O(1)\\) になります。</p> <p>Q:「連結リストの定義と格納方法」の図にある、水色のノードへのポインタは、一つのメモリアドレスに相当する領域を占めるのでしょうか。それともノードの値と半分ずつ占めるのでしょうか。</p> <p>この図は定性的な模式図です。実際の大きさは、具体的な状況に応じて検討する必要があります。</p> <ul> <li>ノードの値が占める空間は、<code>int</code>、<code>long</code>、<code>double</code>、インスタンスオブジェクトなど、型によって異なります。</li> <li>ポインタ変数が占めるメモリの大きさは、使用する OS とコンパイル環境によって決まり、多くの場合は 8 バイトまたは 4 バイトです。</li> </ul> <p>Q:リストの末尾に要素を追加する操作は、常に \\(O(1)\\) ですか。</p> <p>追加によってリストの長さを超える場合は、まずリストの容量を拡張してから追加する必要があります。システムは新しいメモリ領域を確保し、元のリストの全要素を移すため、このときの時間計算量は \\(O(n)\\) になります。</p> <p>Q:「リストの登場により配列の実用性は大幅に向上しましたが、メモリ領域の一部が無駄になる可能性もあります」とあります。この無駄とは、容量、長さ、拡張倍率などの追加の変数が占めるメモリのことですか。</p> <p>ここでいう空間の無駄には、主に二つの意味があります。一つは、リストに設定する初期の長さが、実際に必要な量より大きい場合があることです。もう一つは、頻繁な容量拡張を避けるため、通常は \\(\\times 1.5\\) のように一定の係数を掛けて拡張することです。その結果、多くの空き領域が生まれ、通常はすべてを埋めることはできません。</p> <p>Q:Python で <code>n = [1, 2, 3]</code> と初期化すると、この 3 要素のアドレスは連続しています。しかし <code>m = [2, 1, 3]</code> と初期化すると、各要素の id は連続しておらず、それぞれ <code>n</code> の同じ要素と一致します。要素のアドレスが連続していなくても、<code>m</code> は配列なのでしょうか。</p> <p>リストの要素を連結リストのノードにして <code>n = [n1, n2, n3, n4, n5]</code> とした場合も、通常、この 5 個のノードオブジェクトはメモリの各所に分散して格納されます。それでも、リストのインデックスを指定すれば、\\(O(1)\\) 時間でノードのメモリアドレスを取得し、対応するノードにアクセスできます。これは、配列に格納されているのがノード自体ではなく、ノードへの参照だからです。</p> <p>多くの言語と異なり、Python では数値もオブジェクトとして扱われます。リストには数値自体ではなく、数値への参照が格納されます。そのため、二つの配列にある同じ数値の id が一致し、それらの数値のメモリアドレスが連続している必要もないのです。</p> <p>Q:C++ STL の <code>std::list</code> は双方向連結リストを実装していますが、アルゴリズムの本ではあまり直接使われていないようです。何か制約があるのでしょうか。</p> <p>一つには、アルゴリズムの実装では配列が好まれ、連結リストは必要な場合にだけ使うことが多いためです。主な理由は二つあります。</p> <ul> <li>空間コスト:各要素に追加で二つのポインタ(前の要素用と次の要素用)が必要なため、<code>std::list</code> は通常、<code>std::vector</code> より多くの空間を使います。</li> <li>キャッシュとの相性:データが連続して格納されていないため、<code>std::list</code> はキャッシュの利用効率が低くなります。一般に、<code>std::vector</code> の方が高い性能を得られます。</li> </ul> <p>もう一つには、連結リストを使う必要があるのは主に二分木やグラフの場合だという点があります。スタックやキューには、連結リストではなく、プログラミング言語が提供する <code>stack</code> や <code>queue</code> を使うことが多いです。</p> <p>Q:<code>res = [[0]] * n</code> で作った二次元リストでは、それぞれの <code>[0]</code> は独立していますか。</p> <p>独立していません。この二次元リストでは、すべての <code>[0]</code> が実際には同じオブジェクトへの参照です。一つの要素を変更すると、対応するすべての要素も変わります。</p> <p>二次元リストの各 <code>[0]</code> を独立させたい場合は、<code>res = [[0] for _ in range(n)]</code> とします。この方法では、独立した <code>[0]</code> というリストオブジェクトを \\(n\\) 個初期化します。</p> <p>Q:<code>res = [0] * n</code> で作ったリストでは、それぞれの整数 0 は独立していますか。</p> <p>このリストでは、すべての整数 0 が同じオブジェクトへの参照です。Python は小さな整数(通常は -5 から 256)をキャッシュプールで管理し、オブジェクトをできるだけ再利用して性能を高めているためです。</p> <p>同じオブジェクトを指していても、リストの各要素は独立に変更できます。Python の整数が「不変オブジェクト」だからです。要素を変更するときは、元のオブジェクト自体を変えるのではなく、別のオブジェクトへの参照に切り替えています。</p> <p>ただし、リストの要素が「可変オブジェクト」(リスト、辞書、クラスのインスタンスなど)の場合、その要素を変更するとオブジェクト自体が変わり、それを参照するすべての要素に同じ変更が反映されます。</p>","path":["第 4 章   配列と連結リスト","4.5   まとめ"],"tags":[]},{"location":"chapter_backtracking/","level":1,"title":"第 13 章   バックトラッキング","text":"<p>概要</p> <p>私たちは迷路の探索者のように、進む先で困難に出会うことがあります。</p> <p>バックトラッキングの力があれば、引き返して何度も試し、やがて光の差す出口を見つけられます。</p>","path":["第 13 章   バックトラッキング"],"tags":[]},{"location":"chapter_backtracking/#_1","level":2,"title":"この章の内容","text":"<ul> <li>13.1   バックトラッキングアルゴリズム</li> <li>13.2   順列列挙問題</li> <li>13.3   部分集合和問題</li> <li>13.4   n クイーン問題</li> <li>13.5   まとめ</li> <li>13.6   練習問題</li> </ul>","path":["第 13 章   バックトラッキング"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/","level":1,"title":"13.1   バックトラッキングアルゴリズム","text":"<p>バックトラッキングアルゴリズム(backtracking algorithm)は、総当たりで問題を解く方法です。初期状態から出発し、可能な解をすべて探索して、条件に合う解が見つかれば記録します。解が見つかるか、すべての選択肢を試しても解がないとわかるまで続けることが、その基本的な考え方です。</p> <p>通常は「深さ優先探索」で解の空間を走査します。「二分木」の章では、行きがけ順、通りがけ順、帰りがけ順の走査が、いずれも深さ優先探索に属すると説明しました。ここでは行きがけ順走査を使ってバックトラッキングの問題を作り、仕組みを段階的に理解していきます。</p> <p>例題 1</p> <p>二分木が与えられます。値が \\(7\\) のノードをすべて探して記録し、ノードのリストを返してください。</p> <p>この問題では、木を行きがけ順に走査し、現在のノードの値が \\(7\\) かを調べます。そうなら、そのノードの値を結果リスト <code>res</code> に追加します。処理の流れと実装を、図 13-1 と次のコードに示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_i_compact.py<pre><code>def pre_order(root: TreeNode):\n \"\"\"行きがけ順走査:例題 1\"\"\"\n if root is None:\n return\n if root.val == 7:\n # 解を記録する\n res.append(root)\n pre_order(root.left)\n pre_order(root.right)\n</code></pre> preorder_traversal_i_compact.cpp<pre><code>/* 行きがけ順走査:例題 1 */\nvoid preOrder(TreeNode *root) {\n if (root == nullptr) {\n return;\n }\n if (root-&gt;val == 7) {\n // 解を記録する\n res.push_back(root);\n }\n preOrder(root-&gt;left);\n preOrder(root-&gt;right);\n}\n</code></pre> preorder_traversal_i_compact.java<pre><code>/* 行きがけ順走査:例題 1 */\nvoid preOrder(TreeNode root) {\n if (root == null) {\n return;\n }\n if (root.val == 7) {\n // 解を記録する\n res.add(root);\n }\n preOrder(root.left);\n preOrder(root.right);\n}\n</code></pre> preorder_traversal_i_compact.cs<pre><code>/* 行きがけ順走査:例題 1 */\nvoid PreOrder(TreeNode? root) {\n if (root == null) {\n return;\n }\n if (root.val == 7) {\n // 解を記録する\n res.Add(root);\n }\n PreOrder(root.left);\n PreOrder(root.right);\n}\n</code></pre> preorder_traversal_i_compact.go<pre><code>/* 行きがけ順走査:例題 1 */\nfunc preOrderI(root *TreeNode, res *[]*TreeNode) {\n if root == nil {\n return\n }\n if (root.Val).(int) == 7 {\n // 解を記録する\n *res = append(*res, root)\n }\n preOrderI(root.Left, res)\n preOrderI(root.Right, res)\n}\n</code></pre> preorder_traversal_i_compact.swift<pre><code>/* 行きがけ順走査:例題 1 */\nfunc preOrder(root: TreeNode?) {\n guard let root = root else {\n return\n }\n if root.val == 7 {\n // 解を記録する\n res.append(root)\n }\n preOrder(root: root.left)\n preOrder(root: root.right)\n}\n</code></pre> preorder_traversal_i_compact.js<pre><code>/* 行きがけ順走査:例題 1 */\nfunction preOrder(root, res) {\n if (root === null) {\n return;\n }\n if (root.val === 7) {\n // 解を記録する\n res.push(root);\n }\n preOrder(root.left, res);\n preOrder(root.right, res);\n}\n</code></pre> preorder_traversal_i_compact.ts<pre><code>/* 行きがけ順走査:例題 1 */\nfunction preOrder(root: TreeNode | null, res: TreeNode[]): void {\n if (root === null) {\n return;\n }\n if (root.val === 7) {\n // 解を記録する\n res.push(root);\n }\n preOrder(root.left, res);\n preOrder(root.right, res);\n}\n</code></pre> preorder_traversal_i_compact.dart<pre><code>/* 行きがけ順走査:例題 1 */\nvoid preOrder(TreeNode? root, List&lt;TreeNode&gt; res) {\n if (root == null) {\n return;\n }\n if (root.val == 7) {\n // 解を記録する\n res.add(root);\n }\n preOrder(root.left, res);\n preOrder(root.right, res);\n}\n</code></pre> preorder_traversal_i_compact.rs<pre><code>/* 行きがけ順走査:例題 1 */\nfn pre_order(res: &amp;mut Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;, root: Option&lt;&amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;) {\n if root.is_none() {\n return;\n }\n if let Some(node) = root {\n if node.borrow().val == 7 {\n // 解を記録する\n res.push(node.clone());\n }\n pre_order(res, node.borrow().left.as_ref());\n pre_order(res, node.borrow().right.as_ref());\n }\n}\n</code></pre> preorder_traversal_i_compact.c<pre><code>/* 行きがけ順走査:例題 1 */\nvoid preOrder(TreeNode *root) {\n if (root == NULL) {\n return;\n }\n if (root-&gt;val == 7) {\n // 解を記録する\n res[resSize++] = root;\n }\n preOrder(root-&gt;left);\n preOrder(root-&gt;right);\n}\n</code></pre> preorder_traversal_i_compact.kt<pre><code>/* 行きがけ順走査:例題 1 */\nfun preOrder(root: TreeNode?) {\n if (root == null) {\n return\n }\n if (root._val == 7) {\n // 解を記録する\n res!!.add(root)\n }\n preOrder(root.left)\n preOrder(root.right)\n}\n</code></pre> preorder_traversal_i_compact.rb<pre><code>### 行きがけ順走査:例題 1 ###\ndef pre_order(root)\n return unless root\n\n # 解を記録する\n $res &lt;&lt; root if root.val == 7\n\n pre_order(root.left)\n pre_order(root.right)\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p></p> <p> 図 13-1   行きがけ順走査でノードを探す </p>","path":["第 13 章   バックトラッキング","13.1   バックトラッキングアルゴリズム"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1311","level":2,"title":"13.1.1   試行と後戻り","text":"<p>バックトラッキングと呼ぶのは、解の空間を探す際に「試行」と「後戻り」を行うためです。探索中に、それ以上進めない状態や、条件を満たす解が得られない状態に出会うと、直前の選択を取り消して元の状態に戻り、ほかの可能な選択肢を試します。</p> <p>例題 1 では、各ノードへの訪問が一つの「試行」に対応し、葉ノードの先へ進んだときや、親ノードへ戻るときの <code>return</code> が「後戻り」に対応します。</p> <p>ただし、後戻りは単に関数から戻ることだけではありません。これを説明するため、例題 1 を少し拡張します。</p> <p>例題 2</p> <p>二分木で値が \\(7\\) のノードをすべて探し、根ノードからそれらのノードまでの経路を返してください。</p> <p>例題 1 のコードに加え、リスト <code>path</code> で訪問したノードの経路を記録します。値が \\(7\\) のノードに到達したら、<code>path</code> をコピーして結果リスト <code>res</code> に追加します。走査が終わると、<code>res</code> にすべての解が保存されています。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_ii_compact.py<pre><code>def pre_order(root: TreeNode):\n \"\"\"行きがけ順走査:例題 2\"\"\"\n if root is None:\n return\n # 試行\n path.append(root)\n if root.val == 7:\n # 解を記録する\n res.append(list(path))\n pre_order(root.left)\n pre_order(root.right)\n # 復元\n path.pop()\n</code></pre> preorder_traversal_ii_compact.cpp<pre><code>/* 行きがけ順走査:例題 2 */\nvoid preOrder(TreeNode *root) {\n if (root == nullptr) {\n return;\n }\n // 試行\n path.push_back(root);\n if (root-&gt;val == 7) {\n // 解を記録する\n res.push_back(path);\n }\n preOrder(root-&gt;left);\n preOrder(root-&gt;right);\n // 復元\n path.pop_back();\n}\n</code></pre> preorder_traversal_ii_compact.java<pre><code>/* 行きがけ順走査:例題 2 */\nvoid preOrder(TreeNode root) {\n if (root == null) {\n return;\n }\n // 試行\n path.add(root);\n if (root.val == 7) {\n // 解を記録する\n res.add(new ArrayList&lt;&gt;(path));\n }\n preOrder(root.left);\n preOrder(root.right);\n // 復元\n path.remove(path.size() - 1);\n}\n</code></pre> preorder_traversal_ii_compact.cs<pre><code>/* 行きがけ順走査:例題 2 */\nvoid PreOrder(TreeNode? root) {\n if (root == null) {\n return;\n }\n // 試行\n path.Add(root);\n if (root.val == 7) {\n // 解を記録する\n res.Add(new List&lt;TreeNode&gt;(path));\n }\n PreOrder(root.left);\n PreOrder(root.right);\n // 復元\n path.RemoveAt(path.Count - 1);\n}\n</code></pre> preorder_traversal_ii_compact.go<pre><code>/* 行きがけ順走査:例題 2 */\nfunc preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {\n if root == nil {\n return\n }\n // 試行\n *path = append(*path, root)\n if root.Val.(int) == 7 {\n // 解を記録する\n *res = append(*res, append([]*TreeNode{}, *path...))\n }\n preOrderII(root.Left, res, path)\n preOrderII(root.Right, res, path)\n // 復元\n *path = (*path)[:len(*path)-1]\n}\n</code></pre> preorder_traversal_ii_compact.swift<pre><code>/* 行きがけ順走査:例題 2 */\nfunc preOrder(root: TreeNode?) {\n guard let root = root else {\n return\n }\n // 試行\n path.append(root)\n if root.val == 7 {\n // 解を記録する\n res.append(path)\n }\n preOrder(root: root.left)\n preOrder(root: root.right)\n // 復元\n path.removeLast()\n}\n</code></pre> preorder_traversal_ii_compact.js<pre><code>/* 行きがけ順走査:例題 2 */\nfunction preOrder(root, path, res) {\n if (root === null) {\n return;\n }\n // 試行\n path.push(root);\n if (root.val === 7) {\n // 解を記録する\n res.push([...path]);\n }\n preOrder(root.left, path, res);\n preOrder(root.right, path, res);\n // 復元\n path.pop();\n}\n</code></pre> preorder_traversal_ii_compact.ts<pre><code>/* 行きがけ順走査:例題 2 */\nfunction preOrder(\n root: TreeNode | null,\n path: TreeNode[],\n res: TreeNode[][]\n): void {\n if (root === null) {\n return;\n }\n // 試行\n path.push(root);\n if (root.val === 7) {\n // 解を記録する\n res.push([...path]);\n }\n preOrder(root.left, path, res);\n preOrder(root.right, path, res);\n // 復元\n path.pop();\n}\n</code></pre> preorder_traversal_ii_compact.dart<pre><code>/* 行きがけ順走査:例題 2 */\nvoid preOrder(\n TreeNode? root,\n List&lt;TreeNode&gt; path,\n List&lt;List&lt;TreeNode&gt;&gt; res,\n) {\n if (root == null) {\n return;\n }\n\n // 試行\n path.add(root);\n if (root.val == 7) {\n // 解を記録する\n res.add(List.from(path));\n }\n preOrder(root.left, path, res);\n preOrder(root.right, path, res);\n // 復元\n path.removeLast();\n}\n</code></pre> preorder_traversal_ii_compact.rs<pre><code>/* 行きがけ順走査:例題 2 */\nfn pre_order(\n res: &amp;mut Vec&lt;Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;&gt;,\n path: &amp;mut Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;,\n root: Option&lt;&amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;,\n) {\n if root.is_none() {\n return;\n }\n if let Some(node) = root {\n // 試行\n path.push(node.clone());\n if node.borrow().val == 7 {\n // 解を記録する\n res.push(path.clone());\n }\n pre_order(res, path, node.borrow().left.as_ref());\n pre_order(res, path, node.borrow().right.as_ref());\n // 復元\n path.pop();\n }\n}\n</code></pre> preorder_traversal_ii_compact.c<pre><code>/* 行きがけ順走査:例題 2 */\nvoid preOrder(TreeNode *root) {\n if (root == NULL) {\n return;\n }\n // 試行\n path[pathSize++] = root;\n if (root-&gt;val == 7) {\n // 解を記録する\n for (int i = 0; i &lt; pathSize; ++i) {\n res[resSize][i] = path[i];\n }\n resSize++;\n }\n preOrder(root-&gt;left);\n preOrder(root-&gt;right);\n // 復元\n pathSize--;\n}\n</code></pre> preorder_traversal_ii_compact.kt<pre><code>/* 行きがけ順走査:例題 2 */\nfun preOrder(root: TreeNode?) {\n if (root == null) {\n return\n }\n // 試行\n path!!.add(root)\n if (root._val == 7) {\n // 解を記録する\n res!!.add(path!!.toMutableList())\n }\n preOrder(root.left)\n preOrder(root.right)\n // 復元\n path!!.removeAt(path!!.size - 1)\n}\n</code></pre> preorder_traversal_ii_compact.rb<pre><code>### 行きがけ順走査:例題 2 ###\ndef pre_order(root)\n return unless root\n\n # 試行\n $path &lt;&lt; root\n\n # 解を記録する\n $res &lt;&lt; $path.dup if root.val == 7\n\n pre_order(root.left)\n pre_order(root.right)\n\n # 復元\n $path.pop\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>各「試行」では、現在のノードを <code>path</code> に追加して経路を記録します。「後戻り」の前には、そのノードを <code>path</code> から取り出し、今回の試行前の状態を復元する必要があります。</p> <p>図 13-2 の流れを見ると、試行と後戻りは「前進」と「取り消し」と考えられ、互いに逆の操作だとわかります。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt;&lt;11&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 13-2   試行と後戻り </p>","path":["第 13 章   バックトラッキング","13.1   バックトラッキングアルゴリズム"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1312","level":2,"title":"13.1.2   枝刈り","text":"<p>複雑なバックトラッキングの問題には、一つ以上の制約条件があります。制約条件は通常、「枝刈り」に使えます。</p> <p>例題 3</p> <p>二分木で値が \\(7\\) のノードをすべて探し、根ノードからそれらまでの経路を返してください。ただし、経路に値が \\(3\\) のノードを含めてはいけません。</p> <p>この制約を満たすため、枝刈りの操作を加えます。探索中に値が \\(3\\) のノードに出会ったら、そこで戻り、それ以上探索しません。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_iii_compact.py<pre><code>def pre_order(root: TreeNode):\n \"\"\"行きがけ順走査:例題 3\"\"\"\n # 枝刈り\n if root is None or root.val == 3:\n return\n # 試行\n path.append(root)\n if root.val == 7:\n # 解を記録する\n res.append(list(path))\n pre_order(root.left)\n pre_order(root.right)\n # 復元\n path.pop()\n</code></pre> preorder_traversal_iii_compact.cpp<pre><code>/* 行きがけ順走査:例題 3 */\nvoid preOrder(TreeNode *root) {\n // 枝刈り\n if (root == nullptr || root-&gt;val == 3) {\n return;\n }\n // 試行\n path.push_back(root);\n if (root-&gt;val == 7) {\n // 解を記録する\n res.push_back(path);\n }\n preOrder(root-&gt;left);\n preOrder(root-&gt;right);\n // 復元\n path.pop_back();\n}\n</code></pre> preorder_traversal_iii_compact.java<pre><code>/* 行きがけ順走査:例題 3 */\nvoid preOrder(TreeNode root) {\n // 枝刈り\n if (root == null || root.val == 3) {\n return;\n }\n // 試行\n path.add(root);\n if (root.val == 7) {\n // 解を記録する\n res.add(new ArrayList&lt;&gt;(path));\n }\n preOrder(root.left);\n preOrder(root.right);\n // 復元\n path.remove(path.size() - 1);\n}\n</code></pre> preorder_traversal_iii_compact.cs<pre><code>/* 行きがけ順走査:例題 3 */\nvoid PreOrder(TreeNode? root) {\n // 枝刈り\n if (root == null || root.val == 3) {\n return;\n }\n // 試行\n path.Add(root);\n if (root.val == 7) {\n // 解を記録する\n res.Add(new List&lt;TreeNode&gt;(path));\n }\n PreOrder(root.left);\n PreOrder(root.right);\n // 復元\n path.RemoveAt(path.Count - 1);\n}\n</code></pre> preorder_traversal_iii_compact.go<pre><code>/* 行きがけ順走査:例題 3 */\nfunc preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {\n // 枝刈り\n if root == nil || root.Val == 3 {\n return\n }\n // 試行\n *path = append(*path, root)\n if root.Val.(int) == 7 {\n // 解を記録する\n *res = append(*res, append([]*TreeNode{}, *path...))\n }\n preOrderIII(root.Left, res, path)\n preOrderIII(root.Right, res, path)\n // 復元\n *path = (*path)[:len(*path)-1]\n}\n</code></pre> preorder_traversal_iii_compact.swift<pre><code>/* 行きがけ順走査:例題 3 */\nfunc preOrder(root: TreeNode?) {\n // 枝刈り\n guard let root = root, root.val != 3 else {\n return\n }\n // 試行\n path.append(root)\n if root.val == 7 {\n // 解を記録する\n res.append(path)\n }\n preOrder(root: root.left)\n preOrder(root: root.right)\n // 復元\n path.removeLast()\n}\n</code></pre> preorder_traversal_iii_compact.js<pre><code>/* 行きがけ順走査:例題 3 */\nfunction preOrder(root, path, res) {\n // 枝刈り\n if (root === null || root.val === 3) {\n return;\n }\n // 試行\n path.push(root);\n if (root.val === 7) {\n // 解を記録する\n res.push([...path]);\n }\n preOrder(root.left, path, res);\n preOrder(root.right, path, res);\n // 復元\n path.pop();\n}\n</code></pre> preorder_traversal_iii_compact.ts<pre><code>/* 行きがけ順走査:例題 3 */\nfunction preOrder(\n root: TreeNode | null,\n path: TreeNode[],\n res: TreeNode[][]\n): void {\n // 枝刈り\n if (root === null || root.val === 3) {\n return;\n }\n // 試行\n path.push(root);\n if (root.val === 7) {\n // 解を記録する\n res.push([...path]);\n }\n preOrder(root.left, path, res);\n preOrder(root.right, path, res);\n // 復元\n path.pop();\n}\n</code></pre> preorder_traversal_iii_compact.dart<pre><code>/* 行きがけ順走査:例題 3 */\nvoid preOrder(\n TreeNode? root,\n List&lt;TreeNode&gt; path,\n List&lt;List&lt;TreeNode&gt;&gt; res,\n) {\n if (root == null || root.val == 3) {\n return;\n }\n\n // 試行\n path.add(root);\n if (root.val == 7) {\n // 解を記録する\n res.add(List.from(path));\n }\n preOrder(root.left, path, res);\n preOrder(root.right, path, res);\n // 復元\n path.removeLast();\n}\n</code></pre> preorder_traversal_iii_compact.rs<pre><code>/* 行きがけ順走査:例題 3 */\nfn pre_order(\n res: &amp;mut Vec&lt;Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;&gt;,\n path: &amp;mut Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;,\n root: Option&lt;&amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;,\n) {\n // 枝刈り\n if root.is_none() || root.as_ref().unwrap().borrow().val == 3 {\n return;\n }\n if let Some(node) = root {\n // 試行\n path.push(node.clone());\n if node.borrow().val == 7 {\n // 解を記録する\n res.push(path.clone());\n }\n pre_order(res, path, node.borrow().left.as_ref());\n pre_order(res, path, node.borrow().right.as_ref());\n // 復元\n path.pop();\n }\n}\n</code></pre> preorder_traversal_iii_compact.c<pre><code>/* 行きがけ順走査:例題 3 */\nvoid preOrder(TreeNode *root) {\n // 枝刈り\n if (root == NULL || root-&gt;val == 3) {\n return;\n }\n // 試行\n path[pathSize++] = root;\n if (root-&gt;val == 7) {\n // 解を記録する\n for (int i = 0; i &lt; pathSize; i++) {\n res[resSize][i] = path[i];\n }\n resSize++;\n }\n preOrder(root-&gt;left);\n preOrder(root-&gt;right);\n // 復元\n pathSize--;\n}\n</code></pre> preorder_traversal_iii_compact.kt<pre><code>/* 行きがけ順走査:例題 3 */\nfun preOrder(root: TreeNode?) {\n // 枝刈り\n if (root == null || root._val == 3) {\n return\n }\n // 試行\n path!!.add(root)\n if (root._val == 7) {\n // 解を記録する\n res!!.add(path!!.toMutableList())\n }\n preOrder(root.left)\n preOrder(root.right)\n // 復元\n path!!.removeAt(path!!.size - 1)\n}\n</code></pre> preorder_traversal_iii_compact.rb<pre><code>### 行きがけ順走査:例題 3 ###\ndef pre_order(root)\n # 枝刈り\n return if !root || root.val == 3\n\n # 試行\n $path.append(root)\n\n # 解を記録する\n $res &lt;&lt; $path.dup if root.val == 7\n\n pre_order(root.left)\n pre_order(root.right)\n\n # 復元\n $path.pop\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>「枝刈り」は仕組みをよく表す名前です。図 13-3 のように、制約を満たさない探索の枝を「刈り取る」ことで、無意味な試行を多く避け、探索効率を高めます。</p> <p></p> <p> 図 13-3   制約条件による枝刈り </p>","path":["第 13 章   バックトラッキング","13.1   バックトラッキングアルゴリズム"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1313","level":2,"title":"13.1.3   基本形のコード","text":"<p>ここからは、「試行・後戻り・枝刈り」という中心となる構造を取り出し、より汎用的なコードにしてみましょう。</p> <p>次の基本形では、<code>state</code> が問題の現在の状態を、<code>choices</code> がその状態で取れる選択肢を表します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code>def backtrack(state: State, choices: list[choice], res: list[state]):\n \"\"\"バックトラッキングの基本形\"\"\"\n # 解かどうかを判定\n if is_solution(state):\n # 解を記録\n record_solution(state, res)\n # これ以上探索しない\n return\n # すべての選択肢を調べる\n for choice in choices:\n # 枝刈り:選択が有効かを判定\n if is_valid(state, choice):\n # 試行:選択して状態を更新\n make_choice(state, choice)\n backtrack(state, choices, res)\n # 後戻り:選択を取り消し、前の状態に戻す\n undo_choice(state, choice)\n</code></pre> <pre><code>/* バックトラッキングの基本形 */\nvoid backtrack(State *state, vector&lt;Choice *&gt; &amp;choices, vector&lt;State *&gt; &amp;res) {\n // 解かどうかを判定\n if (isSolution(state)) {\n // 解を記録\n recordSolution(state, res);\n // これ以上探索しない\n return;\n }\n // すべての選択肢を調べる\n for (Choice choice : choices) {\n // 枝刈り:選択が有効かを判定\n if (isValid(state, choice)) {\n // 試行:選択して状態を更新\n makeChoice(state, choice);\n backtrack(state, choices, res);\n // 後戻り:選択を取り消し、前の状態に戻す\n undoChoice(state, choice);\n }\n }\n}\n</code></pre> <pre><code>/* バックトラッキングの基本形 */\nvoid backtrack(State state, List&lt;Choice&gt; choices, List&lt;State&gt; res) {\n // 解かどうかを判定\n if (isSolution(state)) {\n // 解を記録\n recordSolution(state, res);\n // これ以上探索しない\n return;\n }\n // すべての選択肢を調べる\n for (Choice choice : choices) {\n // 枝刈り:選択が有効かを判定\n if (isValid(state, choice)) {\n // 試行:選択して状態を更新\n makeChoice(state, choice);\n backtrack(state, choices, res);\n // 後戻り:選択を取り消し、前の状態に戻す\n undoChoice(state, choice);\n }\n }\n}\n</code></pre> <pre><code>/* バックトラッキングの基本形 */\nvoid Backtrack(State state, List&lt;Choice&gt; choices, List&lt;State&gt; res) {\n // 解かどうかを判定\n if (IsSolution(state)) {\n // 解を記録\n RecordSolution(state, res);\n // これ以上探索しない\n return;\n }\n // すべての選択肢を調べる\n foreach (Choice choice in choices) {\n // 枝刈り:選択が有効かを判定\n if (IsValid(state, choice)) {\n // 試行:選択して状態を更新\n MakeChoice(state, choice);\n Backtrack(state, choices, res);\n // 後戻り:選択を取り消し、前の状態に戻す\n UndoChoice(state, choice);\n }\n }\n}\n</code></pre> <pre><code>/* バックトラッキングの基本形 */\nfunc backtrack(state *State, choices []Choice, res *[]State) {\n // 解かどうかを判定\n if isSolution(state) {\n // 解を記録\n recordSolution(state, res)\n // これ以上探索しない\n return\n }\n // すべての選択肢を調べる\n for _, choice := range choices {\n // 枝刈り:選択が有効かを判定\n if isValid(state, choice) {\n // 試行:選択して状態を更新\n makeChoice(state, choice)\n backtrack(state, choices, res)\n // 後戻り:選択を取り消し、前の状態に戻す\n undoChoice(state, choice)\n }\n }\n}\n</code></pre> <pre><code>/* バックトラッキングの基本形 */\nfunc backtrack(state: inout State, choices: [Choice], res: inout [State]) {\n // 解かどうかを判定\n if isSolution(state: state) {\n // 解を記録\n recordSolution(state: state, res: &amp;res)\n // これ以上探索しない\n return\n }\n // すべての選択肢を調べる\n for choice in choices {\n // 枝刈り:選択が有効かを判定\n if isValid(state: state, choice: choice) {\n // 試行:選択して状態を更新\n makeChoice(state: &amp;state, choice: choice)\n backtrack(state: &amp;state, choices: choices, res: &amp;res)\n // 後戻り:選択を取り消し、前の状態に戻す\n undoChoice(state: &amp;state, choice: choice)\n }\n }\n}\n</code></pre> <pre><code>/* バックトラッキングの基本形 */\nfunction backtrack(state, choices, res) {\n // 解かどうかを判定\n if (isSolution(state)) {\n // 解を記録\n recordSolution(state, res);\n // これ以上探索しない\n return;\n }\n // すべての選択肢を調べる\n for (let choice of choices) {\n // 枝刈り:選択が有効かを判定\n if (isValid(state, choice)) {\n // 試行:選択して状態を更新\n makeChoice(state, choice);\n backtrack(state, choices, res);\n // 後戻り:選択を取り消し、前の状態に戻す\n undoChoice(state, choice);\n }\n }\n}\n</code></pre> <pre><code>/* バックトラッキングの基本形 */\nfunction backtrack(state: State, choices: Choice[], res: State[]): void {\n // 解かどうかを判定\n if (isSolution(state)) {\n // 解を記録\n recordSolution(state, res);\n // これ以上探索しない\n return;\n }\n // すべての選択肢を調べる\n for (let choice of choices) {\n // 枝刈り:選択が有効かを判定\n if (isValid(state, choice)) {\n // 試行:選択して状態を更新\n makeChoice(state, choice);\n backtrack(state, choices, res);\n // 後戻り:選択を取り消し、前の状態に戻す\n undoChoice(state, choice);\n }\n }\n}\n</code></pre> <pre><code>/* バックトラッキングの基本形 */\nvoid backtrack(State state, List&lt;Choice&gt;, List&lt;State&gt; res) {\n // 解かどうかを判定\n if (isSolution(state)) {\n // 解を記録\n recordSolution(state, res);\n // これ以上探索しない\n return;\n }\n // すべての選択肢を調べる\n for (Choice choice in choices) {\n // 枝刈り:選択が有効かを判定\n if (isValid(state, choice)) {\n // 試行:選択して状態を更新\n makeChoice(state, choice);\n backtrack(state, choices, res);\n // 後戻り:選択を取り消し、前の状態に戻す\n undoChoice(state, choice);\n }\n }\n}\n</code></pre> <pre><code>/* バックトラッキングの基本形 */\nfn backtrack(state: &amp;mut State, choices: &amp;Vec&lt;Choice&gt;, res: &amp;mut Vec&lt;State&gt;) {\n // 解かどうかを判定\n if is_solution(state) {\n // 解を記録\n record_solution(state, res);\n // これ以上探索しない\n return;\n }\n // すべての選択肢を調べる\n for choice in choices {\n // 枝刈り:選択が有効かを判定\n if is_valid(state, choice) {\n // 試行:選択して状態を更新\n make_choice(state, choice);\n backtrack(state, choices, res);\n // 後戻り:選択を取り消し、前の状態に戻す\n undo_choice(state, choice);\n }\n }\n}\n</code></pre> <pre><code>/* バックトラッキングの基本形 */\nvoid backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) {\n // 解かどうかを判定\n if (isSolution(state)) {\n // 解を記録\n recordSolution(state, res, numRes);\n // これ以上探索しない\n return;\n }\n // すべての選択肢を調べる\n for (int i = 0; i &lt; numChoices; i++) {\n // 枝刈り:選択が有効かを判定\n if (isValid(state, &amp;choices[i])) {\n // 試行:選択して状態を更新\n makeChoice(state, &amp;choices[i]);\n backtrack(state, choices, numChoices, res, numRes);\n // 後戻り:選択を取り消し、前の状態に戻す\n undoChoice(state, &amp;choices[i]);\n }\n }\n}\n</code></pre> <pre><code>/* バックトラッキングの基本形 */\nfun backtrack(state: State?, choices: List&lt;Choice?&gt;, res: List&lt;State?&gt;?) {\n // 解かどうかを判定\n if (isSolution(state)) {\n // 解を記録\n recordSolution(state, res)\n // これ以上探索しない\n return\n }\n // すべての選択肢を調べる\n for (choice in choices) {\n // 枝刈り:選択が有効かを判定\n if (isValid(state, choice)) {\n // 試行:選択して状態を更新\n makeChoice(state, choice)\n backtrack(state, choices, res)\n // 後戻り:選択を取り消し、前の状態に戻す\n undoChoice(state, choice)\n }\n }\n}\n</code></pre> <pre><code>### バックトラッキングの基本形 ###\ndef backtrack(state, choices, res)\n # 解かどうかを判定\n if is_solution?(state)\n # 解を記録\n record_solution(state, res)\n return\n end\n\n # すべての選択肢を調べる\n for choice in choices\n # 枝刈り:選択が有効かを判定\n if is_valid?(state, choice)\n # 試行:選択して状態を更新\n make_choice(state, choice)\n backtrack(state, choices, res)\n # 後戻り:選択を取り消し、前の状態に戻す\n undo_choice(state, choice)\n end\n end\nend\n</code></pre> <p>次に、この基本形を使って例題 3 を解きます。状態 <code>state</code> はノードをたどった経路、選択肢 <code>choices</code> は現在のノードの左右の子ノード、結果 <code>res</code> は経路のリストです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby preorder_traversal_iii_template.py<pre><code>def is_solution(state: list[TreeNode]) -&gt; bool:\n \"\"\"現在の状態が解かを判定\"\"\"\n return state and state[-1].val == 7\n\ndef record_solution(state: list[TreeNode], res: list[list[TreeNode]]):\n \"\"\"解を記録する\"\"\"\n res.append(list(state))\n\ndef is_valid(state: list[TreeNode], choice: TreeNode) -&gt; bool:\n \"\"\"現在の状態でこの選択が有効か判定\"\"\"\n return choice is not None and choice.val != 3\n\ndef make_choice(state: list[TreeNode], choice: TreeNode):\n \"\"\"状態を更新する\"\"\"\n state.append(choice)\n\ndef undo_choice(state: list[TreeNode], choice: TreeNode):\n \"\"\"状態を復元する\"\"\"\n state.pop()\n\ndef backtrack(\n state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]\n):\n \"\"\"バックトラッキング:例題 3\"\"\"\n # 解かを確認\n if is_solution(state):\n # 解を記録する\n record_solution(state, res)\n # すべての選択肢を走査する\n for choice in choices:\n # 枝刈り:選択が有効か確認\n if is_valid(state, choice):\n # 試行:選択を行い、状態を更新する\n make_choice(state, choice)\n # 次の選択に進む\n backtrack(state, [choice.left, choice.right], res)\n # 復元:選択を取り消し、以前の状態に戻す\n undo_choice(state, choice)\n</code></pre> preorder_traversal_iii_template.cpp<pre><code>/* 現在の状態が解かを判定 */\nbool isSolution(vector&lt;TreeNode *&gt; &amp;state) {\n return !state.empty() &amp;&amp; state.back()-&gt;val == 7;\n}\n\n/* 解を記録する */\nvoid recordSolution(vector&lt;TreeNode *&gt; &amp;state, vector&lt;vector&lt;TreeNode *&gt;&gt; &amp;res) {\n res.push_back(state);\n}\n\n/* 現在の状態でこの選択が有効か判定 */\nbool isValid(vector&lt;TreeNode *&gt; &amp;state, TreeNode *choice) {\n return choice != nullptr &amp;&amp; choice-&gt;val != 3;\n}\n\n/* 状態を更新する */\nvoid makeChoice(vector&lt;TreeNode *&gt; &amp;state, TreeNode *choice) {\n state.push_back(choice);\n}\n\n/* 状態を復元する */\nvoid undoChoice(vector&lt;TreeNode *&gt; &amp;state, TreeNode *choice) {\n state.pop_back();\n}\n\n/* バックトラッキング:例題 3 */\nvoid backtrack(vector&lt;TreeNode *&gt; &amp;state, vector&lt;TreeNode *&gt; &amp;choices, vector&lt;vector&lt;TreeNode *&gt;&gt; &amp;res) {\n // 解かを確認\n if (isSolution(state)) {\n // 解を記録する\n recordSolution(state, res);\n }\n // すべての選択肢を走査する\n for (TreeNode *choice : choices) {\n // 枝刈り:選択が有効か確認\n if (isValid(state, choice)) {\n // 試行:選択を行い、状態を更新する\n makeChoice(state, choice);\n // 次の選択に進む\n vector&lt;TreeNode *&gt; nextChoices{choice-&gt;left, choice-&gt;right};\n backtrack(state, nextChoices, res);\n // 復元:選択を取り消し、以前の状態に戻す\n undoChoice(state, choice);\n }\n }\n}\n</code></pre> preorder_traversal_iii_template.java<pre><code>/* 現在の状態が解かを判定 */\nboolean isSolution(List&lt;TreeNode&gt; state) {\n return !state.isEmpty() &amp;&amp; state.get(state.size() - 1).val == 7;\n}\n\n/* 解を記録する */\nvoid recordSolution(List&lt;TreeNode&gt; state, List&lt;List&lt;TreeNode&gt;&gt; res) {\n res.add(new ArrayList&lt;&gt;(state));\n}\n\n/* 現在の状態でこの選択が有効か判定 */\nboolean isValid(List&lt;TreeNode&gt; state, TreeNode choice) {\n return choice != null &amp;&amp; choice.val != 3;\n}\n\n/* 状態を更新する */\nvoid makeChoice(List&lt;TreeNode&gt; state, TreeNode choice) {\n state.add(choice);\n}\n\n/* 状態を復元する */\nvoid undoChoice(List&lt;TreeNode&gt; state, TreeNode choice) {\n state.remove(state.size() - 1);\n}\n\n/* バックトラッキング:例題 3 */\nvoid backtrack(List&lt;TreeNode&gt; state, List&lt;TreeNode&gt; choices, List&lt;List&lt;TreeNode&gt;&gt; res) {\n // 解かを確認\n if (isSolution(state)) {\n // 解を記録する\n recordSolution(state, res);\n }\n // すべての選択肢を走査する\n for (TreeNode choice : choices) {\n // 枝刈り:選択が有効か確認\n if (isValid(state, choice)) {\n // 試行:選択を行い、状態を更新する\n makeChoice(state, choice);\n // 次の選択に進む\n backtrack(state, Arrays.asList(choice.left, choice.right), res);\n // 復元:選択を取り消し、以前の状態に戻す\n undoChoice(state, choice);\n }\n }\n}\n</code></pre> preorder_traversal_iii_template.cs<pre><code>/* 現在の状態が解かを判定 */\nbool IsSolution(List&lt;TreeNode&gt; state) {\n return state.Count != 0 &amp;&amp; state[^1].val == 7;\n}\n\n/* 解を記録する */\nvoid RecordSolution(List&lt;TreeNode&gt; state, List&lt;List&lt;TreeNode&gt;&gt; res) {\n res.Add(new List&lt;TreeNode&gt;(state));\n}\n\n/* 現在の状態でこの選択が有効か判定 */\nbool IsValid(List&lt;TreeNode&gt; state, TreeNode choice) {\n return choice != null &amp;&amp; choice.val != 3;\n}\n\n/* 状態を更新する */\nvoid MakeChoice(List&lt;TreeNode&gt; state, TreeNode choice) {\n state.Add(choice);\n}\n\n/* 状態を復元する */\nvoid UndoChoice(List&lt;TreeNode&gt; state, TreeNode choice) {\n state.RemoveAt(state.Count - 1);\n}\n\n/* バックトラッキング:例題 3 */\nvoid Backtrack(List&lt;TreeNode&gt; state, List&lt;TreeNode&gt; choices, List&lt;List&lt;TreeNode&gt;&gt; res) {\n // 解かを確認\n if (IsSolution(state)) {\n // 解を記録する\n RecordSolution(state, res);\n }\n // すべての選択肢を走査する\n foreach (TreeNode choice in choices) {\n // 枝刈り:選択が有効か確認\n if (IsValid(state, choice)) {\n // 試行:選択を行い、状態を更新する\n MakeChoice(state, choice);\n // 次の選択に進む\n Backtrack(state, [choice.left!, choice.right!], res);\n // 復元:選択を取り消し、以前の状態に戻す\n UndoChoice(state, choice);\n }\n }\n}\n</code></pre> preorder_traversal_iii_template.go<pre><code>/* 現在の状態が解かを判定 */\nfunc isSolution(state *[]*TreeNode) bool {\n return len(*state) != 0 &amp;&amp; (*state)[len(*state)-1].Val == 7\n}\n\n/* 解を記録する */\nfunc recordSolution(state *[]*TreeNode, res *[][]*TreeNode) {\n *res = append(*res, append([]*TreeNode{}, *state...))\n}\n\n/* 現在の状態でこの選択が有効か判定 */\nfunc isValid(state *[]*TreeNode, choice *TreeNode) bool {\n return choice != nil &amp;&amp; choice.Val != 3\n}\n\n/* 状態を更新する */\nfunc makeChoice(state *[]*TreeNode, choice *TreeNode) {\n *state = append(*state, choice)\n}\n\n/* 状態を復元する */\nfunc undoChoice(state *[]*TreeNode, choice *TreeNode) {\n *state = (*state)[:len(*state)-1]\n}\n\n/* バックトラッキング:例題 3 */\nfunc backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) {\n // 解かを確認\n if isSolution(state) {\n // 解を記録する\n recordSolution(state, res)\n }\n // すべての選択肢を走査する\n for _, choice := range *choices {\n // 枝刈り:選択が有効か確認\n if isValid(state, choice) {\n // 試行:選択を行い、状態を更新する\n makeChoice(state, choice)\n // 次の選択に進む\n temp := make([]*TreeNode, 0)\n temp = append(temp, choice.Left, choice.Right)\n backtrackIII(state, &amp;temp, res)\n // 復元:選択を取り消し、以前の状態に戻す\n undoChoice(state, choice)\n }\n }\n}\n</code></pre> preorder_traversal_iii_template.swift<pre><code>/* 現在の状態が解かを判定 */\nfunc isSolution(state: [TreeNode]) -&gt; Bool {\n !state.isEmpty &amp;&amp; state.last!.val == 7\n}\n\n/* 解を記録する */\nfunc recordSolution(state: [TreeNode], res: inout [[TreeNode]]) {\n res.append(state)\n}\n\n/* 現在の状態でこの選択が有効か判定 */\nfunc isValid(state: [TreeNode], choice: TreeNode?) -&gt; Bool {\n choice != nil &amp;&amp; choice!.val != 3\n}\n\n/* 状態を更新する */\nfunc makeChoice(state: inout [TreeNode], choice: TreeNode) {\n state.append(choice)\n}\n\n/* 状態を復元する */\nfunc undoChoice(state: inout [TreeNode], choice: TreeNode) {\n state.removeLast()\n}\n\n/* バックトラッキング:例題 3 */\nfunc backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) {\n // 解かを確認\n if isSolution(state: state) {\n recordSolution(state: state, res: &amp;res)\n }\n // すべての選択肢を走査する\n for choice in choices {\n // 枝刈り:選択が有効か確認\n if isValid(state: state, choice: choice) {\n // 試行:選択を行い、状態を更新する\n makeChoice(state: &amp;state, choice: choice)\n // 次の選択に進む\n backtrack(state: &amp;state, choices: [choice.left, choice.right].compactMap { $0 }, res: &amp;res)\n // 復元:選択を取り消し、以前の状態に戻す\n undoChoice(state: &amp;state, choice: choice)\n }\n }\n}\n</code></pre> preorder_traversal_iii_template.js<pre><code>/* 現在の状態が解かを判定 */\nfunction isSolution(state) {\n return state &amp;&amp; state[state.length - 1]?.val === 7;\n}\n\n/* 解を記録する */\nfunction recordSolution(state, res) {\n res.push([...state]);\n}\n\n/* 現在の状態でこの選択が有効か判定 */\nfunction isValid(state, choice) {\n return choice !== null &amp;&amp; choice.val !== 3;\n}\n\n/* 状態を更新する */\nfunction makeChoice(state, choice) {\n state.push(choice);\n}\n\n/* 状態を復元する */\nfunction undoChoice(state) {\n state.pop();\n}\n\n/* バックトラッキング:例題 3 */\nfunction backtrack(state, choices, res) {\n // 解かを確認\n if (isSolution(state)) {\n // 解を記録する\n recordSolution(state, res);\n }\n // すべての選択肢を走査する\n for (const choice of choices) {\n // 枝刈り:選択が有効か確認\n if (isValid(state, choice)) {\n // 試行:選択を行い、状態を更新する\n makeChoice(state, choice);\n // 次の選択に進む\n backtrack(state, [choice.left, choice.right], res);\n // 復元:選択を取り消し、以前の状態に戻す\n undoChoice(state);\n }\n }\n}\n</code></pre> preorder_traversal_iii_template.ts<pre><code>/* 現在の状態が解かを判定 */\nfunction isSolution(state: TreeNode[]): boolean {\n return state &amp;&amp; state[state.length - 1]?.val === 7;\n}\n\n/* 解を記録する */\nfunction recordSolution(state: TreeNode[], res: TreeNode[][]): void {\n res.push([...state]);\n}\n\n/* 現在の状態でこの選択が有効か判定 */\nfunction isValid(state: TreeNode[], choice: TreeNode): boolean {\n return choice !== null &amp;&amp; choice.val !== 3;\n}\n\n/* 状態を更新する */\nfunction makeChoice(state: TreeNode[], choice: TreeNode): void {\n state.push(choice);\n}\n\n/* 状態を復元する */\nfunction undoChoice(state: TreeNode[]): void {\n state.pop();\n}\n\n/* バックトラッキング:例題 3 */\nfunction backtrack(\n state: TreeNode[],\n choices: TreeNode[],\n res: TreeNode[][]\n): void {\n // 解かを確認\n if (isSolution(state)) {\n // 解を記録する\n recordSolution(state, res);\n }\n // すべての選択肢を走査する\n for (const choice of choices) {\n // 枝刈り:選択が有効か確認\n if (isValid(state, choice)) {\n // 試行:選択を行い、状態を更新する\n makeChoice(state, choice);\n // 次の選択に進む\n backtrack(state, [choice.left, choice.right], res);\n // 復元:選択を取り消し、以前の状態に戻す\n undoChoice(state);\n }\n }\n}\n</code></pre> preorder_traversal_iii_template.dart<pre><code>/* 現在の状態が解かを判定 */\nbool isSolution(List&lt;TreeNode&gt; state) {\n return state.isNotEmpty &amp;&amp; state.last.val == 7;\n}\n\n/* 解を記録する */\nvoid recordSolution(List&lt;TreeNode&gt; state, List&lt;List&lt;TreeNode&gt;&gt; res) {\n res.add(List.from(state));\n}\n\n/* 現在の状態でこの選択が有効か判定 */\nbool isValid(List&lt;TreeNode&gt; state, TreeNode? choice) {\n return choice != null &amp;&amp; choice.val != 3;\n}\n\n/* 状態を更新する */\nvoid makeChoice(List&lt;TreeNode&gt; state, TreeNode? choice) {\n state.add(choice!);\n}\n\n/* 状態を復元する */\nvoid undoChoice(List&lt;TreeNode&gt; state, TreeNode? choice) {\n state.removeLast();\n}\n\n/* バックトラッキング:例題 3 */\nvoid backtrack(\n List&lt;TreeNode&gt; state,\n List&lt;TreeNode?&gt; choices,\n List&lt;List&lt;TreeNode&gt;&gt; res,\n) {\n // 解かを確認\n if (isSolution(state)) {\n // 解を記録する\n recordSolution(state, res);\n }\n // すべての選択肢を走査する\n for (TreeNode? choice in choices) {\n // 枝刈り:選択が有効か確認\n if (isValid(state, choice)) {\n // 試行:選択を行い、状態を更新する\n makeChoice(state, choice);\n // 次の選択に進む\n backtrack(state, [choice!.left, choice.right], res);\n // 復元:選択を取り消し、以前の状態に戻す\n undoChoice(state, choice);\n }\n }\n}\n</code></pre> preorder_traversal_iii_template.rs<pre><code>/* 現在の状態が解かを判定 */\nfn is_solution(state: &amp;mut Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;) -&gt; bool {\n return !state.is_empty() &amp;&amp; state.last().unwrap().borrow().val == 7;\n}\n\n/* 解を記録する */\nfn record_solution(\n state: &amp;mut Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;,\n res: &amp;mut Vec&lt;Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;&gt;,\n) {\n res.push(state.clone());\n}\n\n/* 現在の状態でこの選択が有効か判定 */\nfn is_valid(_: &amp;mut Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;, choice: Option&lt;&amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;) -&gt; bool {\n return choice.is_some() &amp;&amp; choice.unwrap().borrow().val != 3;\n}\n\n/* 状態を更新する */\nfn make_choice(state: &amp;mut Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;, choice: Rc&lt;RefCell&lt;TreeNode&gt;&gt;) {\n state.push(choice);\n}\n\n/* 状態を復元する */\nfn undo_choice(state: &amp;mut Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;, _: Rc&lt;RefCell&lt;TreeNode&gt;&gt;) {\n state.pop();\n}\n\n/* バックトラッキング:例題 3 */\nfn backtrack(\n state: &amp;mut Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;,\n choices: &amp;Vec&lt;Option&lt;&amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;&gt;,\n res: &amp;mut Vec&lt;Vec&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;&gt;,\n) {\n // 解かを確認\n if is_solution(state) {\n // 解を記録する\n record_solution(state, res);\n }\n // すべての選択肢を走査する\n for &amp;choice in choices.iter() {\n // 枝刈り:選択が有効か確認\n if is_valid(state, choice) {\n // 試行:選択を行い、状態を更新する\n make_choice(state, choice.unwrap().clone());\n // 次の選択に進む\n backtrack(\n state,\n &amp;vec![\n choice.unwrap().borrow().left.as_ref(),\n choice.unwrap().borrow().right.as_ref(),\n ],\n res,\n );\n // 復元:選択を取り消し、以前の状態に戻す\n undo_choice(state, choice.unwrap().clone());\n }\n }\n}\n</code></pre> preorder_traversal_iii_template.c<pre><code>/* 現在の状態が解かを判定 */\nbool isSolution(void) {\n return pathSize &gt; 0 &amp;&amp; path[pathSize - 1]-&gt;val == 7;\n}\n\n/* 解を記録する */\nvoid recordSolution(void) {\n for (int i = 0; i &lt; pathSize; i++) {\n res[resSize][i] = path[i];\n }\n resSize++;\n}\n\n/* 現在の状態でこの選択が有効か判定 */\nbool isValid(TreeNode *choice) {\n return choice != NULL &amp;&amp; choice-&gt;val != 3;\n}\n\n/* 状態を更新する */\nvoid makeChoice(TreeNode *choice) {\n path[pathSize++] = choice;\n}\n\n/* 状態を復元する */\nvoid undoChoice(void) {\n pathSize--;\n}\n\n/* バックトラッキング:例題 3 */\nvoid backtrack(TreeNode *choices[2]) {\n // 解かを確認\n if (isSolution()) {\n // 解を記録する\n recordSolution();\n }\n // すべての選択肢を走査する\n for (int i = 0; i &lt; 2; i++) {\n TreeNode *choice = choices[i];\n // 枝刈り:選択が有効か確認\n if (isValid(choice)) {\n // 試行:選択を行い、状態を更新する\n makeChoice(choice);\n // 次の選択に進む\n TreeNode *nextChoices[2] = {choice-&gt;left, choice-&gt;right};\n backtrack(nextChoices);\n // 復元:選択を取り消し、以前の状態に戻す\n undoChoice();\n }\n }\n}\n</code></pre> preorder_traversal_iii_template.kt<pre><code>/* 現在の状態が解かを判定 */\nfun isSolution(state: MutableList&lt;TreeNode?&gt;): Boolean {\n return state.isNotEmpty() &amp;&amp; state[state.size - 1]?._val == 7\n}\n\n/* 解を記録する */\nfun recordSolution(state: MutableList&lt;TreeNode?&gt;?, res: MutableList&lt;MutableList&lt;TreeNode?&gt;?&gt;) {\n res.add(state!!.toMutableList())\n}\n\n/* 現在の状態でこの選択が有効か判定 */\nfun isValid(state: MutableList&lt;TreeNode?&gt;?, choice: TreeNode?): Boolean {\n return choice != null &amp;&amp; choice._val != 3\n}\n\n/* 状態を更新する */\nfun makeChoice(state: MutableList&lt;TreeNode?&gt;, choice: TreeNode?) {\n state.add(choice)\n}\n\n/* 状態を復元する */\nfun undoChoice(state: MutableList&lt;TreeNode?&gt;, choice: TreeNode?) {\n state.removeLast()\n}\n\n/* バックトラッキング:例題 3 */\nfun backtrack(\n state: MutableList&lt;TreeNode?&gt;,\n choices: MutableList&lt;TreeNode?&gt;,\n res: MutableList&lt;MutableList&lt;TreeNode?&gt;?&gt;\n) {\n // 解かを確認\n if (isSolution(state)) {\n // 解を記録する\n recordSolution(state, res)\n }\n // すべての選択肢を走査する\n for (choice in choices) {\n // 枝刈り:選択が有効か確認\n if (isValid(state, choice)) {\n // 試行:選択を行い、状態を更新する\n makeChoice(state, choice)\n // 次の選択に進む\n backtrack(state, mutableListOf(choice!!.left, choice.right), res)\n // 復元:選択を取り消し、以前の状態に戻す\n undoChoice(state, choice)\n }\n }\n}\n</code></pre> preorder_traversal_iii_template.rb<pre><code>### 現在の状態が解かを判定 ###\ndef is_solution?(state)\n !state.empty? &amp;&amp; state.last.val == 7\nend\n\n### 解を記録する ###\ndef record_solution(state, res)\n res &lt;&lt; state.dup\nend\n\n### 現在の状態でこの選択が有効か判定 ###\ndef is_valid?(state, choice)\n choice &amp;&amp; choice.val != 3\nend\n\n### 状態を更新する ###\ndef make_choice(state, choice)\n state &lt;&lt; choice\nend\n\n### 状態を復元する ###\ndef undo_choice(state, choice)\n state.pop\nend\n\n### バックトラッキング:例題 3 ###\ndef backtrack(state, choices, res)\n # 解かを確認\n record_solution(state, res) if is_solution?(state)\n\n # すべての選択肢を走査する\n for choice in choices\n # 枝刈り:選択が有効か確認\n if is_valid?(state, choice)\n # 試行:選択を行い、状態を更新する\n make_choice(state, choice)\n # 次の選択に進む\n backtrack(state, [choice.left, choice.right], res)\n # 復元:選択を取り消し、以前の状態に戻す\n undo_choice(state, choice)\n end\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>問題の条件から、値が \\(7\\) のノードが見つかっても探索を続ける必要があります。そのため、解を記録した直後の <code>return</code> 文を削除します。図 13-4 に、<code>return</code> を残す場合と削除する場合の探索の違いを示します。</p> <p></p> <p> 図 13-4   return を残す場合と削除する場合の探索の比較 </p> <p>行きがけ順走査を使った実装に比べると、基本形に沿ったコードは冗長に見えますが、汎用性に優れています。実際、多くのバックトラッキングの問題はこの基本形で解けます。問題に応じて <code>state</code> と <code>choices</code> を定義し、各メソッドを実装すればよいのです。</p>","path":["第 13 章   バックトラッキング","13.1   バックトラッキングアルゴリズム"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1314","level":2,"title":"13.1.4   主な用語","text":"<p>問題をより明確に分析できるよう、バックトラッキングの主な用語を整理します。表 13-1 に、例題 3 での対応も示します。</p> <p> 表 13-1   バックトラッキングの主な用語 </p> 用語 定義 例題 3 解(solution) 問題の特定の条件を満たす答え。一つの場合も複数の場合もある 根ノードからノード \\(7\\) までの、制約を満たすすべての経路 制約条件(constraint) 解が実行可能であるための条件。通常は枝刈りに使う 経路にノード \\(3\\) を含めない 状態(state) それまでの選択を含む、ある時点での問題の状況 現在までに訪問した経路、すなわちノードのリスト <code>path</code> 試行(attempt) 利用可能な選択肢で解の空間を探索する過程。選択、状態更新、解の判定を含む 左(右)の子ノードを再帰的に訪問し、<code>path</code> に追加して、値が \\(7\\) かを調べる 後戻り(backtracking) 制約を満たさない状態に出会ったとき、前の選択を取り消して一つ前の状態へ戻ること 葉ノードの先へ進んだとき、訪問を終えたとき、値が \\(3\\) のノードに出会ったときに探索を終えて関数から戻る 枝刈り(pruning) 問題の性質や制約を使って無意味な探索経路を避け、効率を高める方法 値が \\(3\\) のノードに出会ったら、それ以上探索しない <p>ヒント</p> <p>問題、解、状態などの概念は共通のもので、分割統治、バックトラッキング、動的計画法、貪欲法などにも登場します。</p>","path":["第 13 章   バックトラッキング","13.1   バックトラッキングアルゴリズム"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1315","level":2,"title":"13.1.5   利点と制約","text":"<p>バックトラッキングは本質的に深さ優先探索であり、条件を満たす解が見つかるまで、可能な候補をすべて試します。すべての解を見つけられることが利点であり、適切な枝刈りをすれば高い効率を得られます。</p> <p>ただし、大規模または複雑な問題では、実行時間が許容できないほど長くなる可能性があります。</p> <ul> <li>時間:状態空間のすべての可能性を調べる必要があることが多く、時間計算量は指数や階乗のオーダーになる場合があります。</li> <li>空間:再帰呼び出しでは現在の状態(経路や枝刈り用の補助変数など)を保持するため、深さが大きいと必要な空間も大きくなる場合があります。</li> </ul> <p>それでも、一部の探索問題や制約充足問題には、バックトラッキングが最も適した解法です。どの選択が有効な解につながるか予測できないため、すべての選択肢を調べる必要があるからです。この場合、重要なのは効率をどう高めるかであり、代表的な方法には二つあります。</p> <ul> <li>枝刈り:解が得られないと確実にわかる経路を探索せず、時間と空間を節約します。</li> <li>ヒューリスティック探索:戦略や推定値を取り入れ、有効な解が得られそうな経路を優先して探索します。</li> </ul>","path":["第 13 章   バックトラッキング","13.1   バックトラッキングアルゴリズム"],"tags":[]},{"location":"chapter_backtracking/backtracking_algorithm/#1316","level":2,"title":"13.1.6   バックトラッキングの代表的な問題","text":"<p>バックトラッキングは、多くの探索問題、制約充足問題、組合せ最適化問題に使えます。</p> <p>探索問題:特定の条件を満たす解を見つけることが目標です。</p> <ul> <li>順列列挙問題:集合が与えられ、その要素の可能な並べ方をすべて求めます。</li> <li>部分集合和問題:集合と目標の和が与えられ、和が目標に一致するすべての部分集合を求めます。</li> <li>ハノイの塔の問題:3 本の柱と大きさの異なる円盤があり、すべての円盤を一つの柱から別の柱へ移します。一度に動かせるのは 1 枚だけで、小さな円盤の上に大きな円盤を置いてはいけません。</li> </ul> <p>制約充足問題:すべての制約条件を満たす解を見つけることが目標です。</p> <ul> <li>\\(n\\) クイーン:\\(n \\times n\\) の盤面に \\(n\\) 個のクイーンを、互いに攻撃できないように配置します。</li> <li>数独:\\(9 \\times 9\\) のマスに \\(1\\) ~ \\(9\\) を入れ、各行、各列、各 \\(3 \\times 3\\) の領域で数字が重複しないようにします。</li> <li>グラフ彩色問題:無向グラフの各頂点を、隣接する頂点が同じ色にならないよう、最少の色数で塗ります。</li> </ul> <p>組合せ最適化問題:組合せの空間から、条件を満たす最適解を見つけることが目標です。</p> <ul> <li>0-1 ナップサック問題:価値と重さを持つ品物の集合とナップサックが与えられ、容量の制限内で総価値が最大になるよう品物を選びます。</li> <li>巡回セールスマン問題:グラフのある頂点から出発し、ほかのすべての頂点をちょうど 1 回ずつ訪問して出発点に戻る、最短の経路を求めます。</li> <li>最大クリーク問題:無向グラフから、任意の 2 頂点の間に辺がある完全部分グラフのうち、最大のものを求めます。</li> </ul> <p>ただし、多くの組合せ最適化問題では、バックトラッキングが最良の解法とは限りません。</p> <ul> <li>0-1 ナップサック問題は通常、時間効率の高い動的計画法で解きます。</li> <li>巡回セールスマン問題は有名な NP-Hard 問題で、遺伝的アルゴリズムや蟻コロニー最適化などがよく使われます。</li> <li>最大クリーク問題はグラフ理論の古典的な問題で、貪欲法などのヒューリスティックアルゴリズムで解けます。</li> </ul>","path":["第 13 章   バックトラッキング","13.1   バックトラッキングアルゴリズム"],"tags":[]},{"location":"chapter_backtracking/exercises/","level":1,"title":"13.6   練習問題","text":"","path":["第 13 章   バックトラッキング","13.6   練習問題"],"tags":[]},{"location":"chapter_backtracking/exercises/#1361","level":2,"title":"13.6.1   知識の確認","text":"","path":["第 13 章   バックトラッキング","13.6   練習問題"],"tags":[]},{"location":"chapter_backtracking/exercises/#1","level":3,"title":"1.   この順列アルゴリズムで結果が漏れないか","text":"<p>バックトラッキングのアルゴリズムが、<code>1、2、3</code> の順に選択を試し、全順列を生成しようとしています。数 <code>x</code> を選ぶたびに、次の処理を行います。</p> <ol> <li><code>x</code> を現在の経路の末尾に追加します。</li> <li><code>x</code> に「使用済み」の印を付けます。</li> <li>次の位置を再帰で埋めます。</li> </ol> <p>再帰から戻った後、生徒は経路の末尾から <code>x</code> を削除するだけで、次の数を試しました。</p> <ol> <li>最初にどの順列が得られますか。6 個すべての順列を得られますか。</li> <li>再帰で一つ上の層へ戻る前に、経路の末尾の数を削除するだけで十分ですか。不十分なら、ほかに何が必要ですか。理由も説明してください。</li> </ol> 解答例 <ol> <li> <p>最初に <code>[1, 2, 3]</code> が得られますが、全順列は得られません。戻ると経路は短くなりますが、 1、2、3 の印はすべて「使用済み」のままなので、その後の分岐で選べる数がなくなります。</p> </li> <li> <p>不十分です。末尾の <code>x</code> を削除した後、<code>x</code> の印を「未使用」に戻す必要があります。 現在の経路と使用済みの印が、一緒に探索状態を表しています。選択時に二か所を変更したので、戻るときも両方を元に戻して初めて、 ほかの分岐で <code>x</code> を再び選べます。</p> </li> </ol>","path":["第 13 章   バックトラッキング","13.6   練習問題"],"tags":[]},{"location":"chapter_backtracking/exercises/#2","level":3,"title":"2.   数を選ぶ順序は重要か","text":"<p>整列済み配列 <code>[2, 3, 5]</code> と目標値 5 が与えられ、各数は繰り返し選べます。 アルゴリズムでは、各探索経路の数が小さい順にだけ現れるものとします。</p> <ol> <li>どのような異なる組み合わせが得られますか。</li> <li>同じ数の組を異なる順番で繰り返し探索する必要がないのはなぜですか。「小さい順」という制約はどのような役割を果たしますか。</li> <li>現在の経路が <code>[3]</code> で、あと 2 必要なとき、次の候補は 3 です。この時点で、この層の残りの全候補を調べずに終えられるのはなぜですか。</li> </ol> 解答例 <ol> <li> <p>異なる組み合わせは <code>[2, 3]</code> と <code>[5]</code> です。</p> </li> <li> <p>この問題では <code>[2, 3]</code> と <code>[3, 2]</code> を同じ組み合わせとみなし、選んだ順序は答えに含めません。 経路の数を小さい順に限定すると、探索時に <code>[3, 2]</code> のような重複する組み合わせを直接除外できます。</p> </li> <li> <p>必要なのはあと 2 ですが、候補の 3 はすでに 2 より大きくなっています。配列は整列済みなので、 3 より後の候補はさらに大きく、いずれも現在の組み合わせには加えられません。そのため、この層の確認をそこで終えられます。</p> </li> </ol>","path":["第 13 章   バックトラッキング","13.6   練習問題"],"tags":[]},{"location":"chapter_backtracking/exercises/#3","level":3,"title":"3.   次のクイーンを置ける位置","text":"<p><code>4 × 4</code> の盤面に、行ごとにクイーンを置きます。行と列のインデックスはいずれも 0 から始まります。 すでに <code>(0, 1)</code> と <code>(1, 3)</code> にクイーンがあり、次のクイーンを第 2 行に置きます。</p> <ol> <li>「同じ列」に当たるために除外されるのは、どの列ですか。</li> <li>残った列のうち、「同じ対角線」に当たるために除外される位置はどこですか。</li> <li>第 2 行には、試せる位置がどこに残っていますか。</li> </ol> 解答例 <ol> <li> <p>第 1 列と第 3 列にはすでにクイーンがあるため、<code>(2, 1)</code> と <code>(2, 3)</code> は除外されます。</p> </li> <li> <p>残った位置のうち、<code>(2, 2)</code> は <code>(1, 3)</code> と同じ対角線上にあるため、除外されます。 <code>(2, 0)</code> は、既存の二つのクイーンのどちらとも同じ列や対角線上にはありません。</p> </li> <li> <p>第 2 行で試せる位置は <code>(2, 0)</code> だけです。</p> <p>この段階で分かるのは、現在の配置が規則に合っていることだけです。その後に盤面を完成できなければ、前へ戻って、それ以前の別の選択を試す必要があります。</p> </li> </ol>","path":["第 13 章   バックトラッキング","13.6   練習問題"],"tags":[]},{"location":"chapter_backtracking/exercises/#1362","level":2,"title":"13.6.2   プログラミング演習","text":"","path":["第 13 章   バックトラッキング","13.6   練習問題"],"tags":[]},{"location":"chapter_backtracking/exercises/#1_1","level":3,"title":"1.   重複しない要素の全順列","text":"<p>整数配列 <code>nums</code> には少なくとも一つの要素があり、各要素はすべて異なります。 各要素を一回ずつ使って作れる順序をすべて列挙し、それぞれを配列として返してください。 結果内の順列の並び順は問いません。 バックトラッキングを使い、各位置の要素を現在の順列にすでに選んだかどうかを、真偽値の配列で記録してください。</p> 解くためのヒント <ol> <li>再帰の深さは、順列の何番目の位置を埋めているかを表します</li> <li>各層では、まだ使っていない要素だけを試します</li> <li>経路の長さが <code>nums</code> の長さに達したら、そのコピーを答えに追加します</li> </ol> <p>LeetCode</p>","path":["第 13 章   バックトラッキング","13.6   練習問題"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/","level":1,"title":"13.4   n クイーン問題","text":"<p>問題</p> <p>チェスの規則では、クイーンは同じ行、列、斜めの線上にある駒を攻撃できます。\\(n\\) 個のクイーンと \\(n \\times n\\) の盤面が与えられたとき、どのクイーン同士も攻撃できない配置を求めてください。</p> <p>図 13-15 のように、\\(n = 4\\) の場合は二つの解があります。バックトラッキングの観点では、\\(n \\times n\\) の盤面の \\(n^2\\) 個のマスが、すべての選択肢 <code>choices</code> です。クイーンを一つずつ置くたびに盤面が変わり、その時々の盤面が状態 <code>state</code> になります。</p> <p></p> <p> 図 13-15   4 クイーン問題の解 </p> <p>図 13-16 に、三つの制約条件を示します。同じ行、同じ列、同じ対角線上に複数のクイーンを置いてはいけません。対角線には、主対角線方向 <code>\\</code> と副対角線方向 <code>/</code> の 2 種類があることに注意してください。</p> <p></p> <p> 図 13-16   n クイーン問題の制約条件 </p>","path":["第 13 章   バックトラッキング","13.4   n クイーン問題"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#1","level":3,"title":"1.   行ごとに配置する戦略","text":"<p>クイーンの個数と盤面の行数はどちらも \\(n\\) なので、各行にはクイーンをちょうど一つずつ置かなければならないことがわかります。</p> <p>そこで、先頭行から最後の行まで、各行に一つずつ配置する戦略を取れます。</p> <p>図 13-17 に、4 クイーン問題で行ごとに配置する過程を示します。図の大きさの都合で、先頭行の探索分岐は一つだけを展開し、列と対角線の制約を満たさない配置は枝刈りしています。</p> <p></p> <p> 図 13-17   行ごとに配置する戦略 </p> <p>本質的には、行ごとの配置も枝刈りとして働き、同じ行に複数のクイーンを置くすべての探索分岐を避けています。</p>","path":["第 13 章   バックトラッキング","13.4   n クイーン問題"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#2","level":3,"title":"2.   列と対角線による枝刈り","text":"<p>列の制約を満たすため、長さ \\(n\\) のブール配列 <code>cols</code> で、各列にクイーンがあるかを記録します。配置する前に <code>cols</code> を調べ、すでにクイーンがある列を枝刈りします。バックトラッキング中は、<code>cols</code> の状態を動的に更新します。</p> <p>ヒント</p> <p>行列の原点は左上にあり、行インデックスは上から下へ、列インデックスは左から右へ増えることに注意してください。</p> <p>では、対角線の制約はどう扱えばよいでしょうか。あるマスの行・列インデックスを \\((row, col)\\) とします。主対角線方向の線を一つ選ぶと、その線上のすべてのマスでは行インデックスから列インデックスを引いた値が等しく、\\(row - col\\) が一定です。</p> <p>つまり、二つのマスが \\(row_1 - col_1 = row_2 - col_2\\) を満たせば、同じ主対角線方向の線上にあります。この性質を使い、図 13-18 の配列 <code>diags1</code> に、各線上にクイーンがあるかを記録できます。</p> <p>同様に、副対角線方向の線上では、すべてのマスの \\(row + col\\) が一定です。配列 <code>diags2</code> を使って、この方向の制約も扱えます。</p> <p></p> <p> 図 13-18   列と対角線の制約の処理 </p>","path":["第 13 章   バックトラッキング","13.4   n クイーン問題"],"tags":[]},{"location":"chapter_backtracking/n_queens_problem/#3","level":3,"title":"3.   コードの実装","text":"<p>\\(n\\) 次の正方行列では、\\(row - col\\) の範囲は \\([-n + 1, n - 1]\\)、\\(row + col\\) の範囲は \\([0, 2n - 2]\\) です。したがって、どちらの方向の対角線も \\(2n - 1\\) 本あり、配列 <code>diags1</code> と <code>diags2</code> の長さはいずれも \\(2n - 1\\) です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby n_queens.py<pre><code>def backtrack(\n row: int,\n n: int,\n state: list[list[str]],\n res: list[list[list[str]]],\n cols: list[bool],\n diags1: list[bool],\n diags2: list[bool],\n):\n \"\"\"バックトラッキング:n クイーン\"\"\"\n # すべての行への配置が終わったら、解を記録する\n if row == n:\n res.append([list(row) for row in state])\n return\n # すべての列を走査する\n for col in range(n):\n # このマスに対応する主対角線と副対角線を求める\n diag1 = row - col + n - 1\n diag2 = row + col\n # 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if not cols[col] and not diags1[diag1] and not diags2[diag2]:\n # 試行:このマスにクイーンを配置する\n state[row][col] = \"Q\"\n cols[col] = diags1[diag1] = diags2[diag2] = True\n # 次の行に配置する\n backtrack(row + 1, n, state, res, cols, diags1, diags2)\n # 復元:このマスを空きマスに戻す\n state[row][col] = \"#\"\n cols[col] = diags1[diag1] = diags2[diag2] = False\n\ndef n_queens(n: int) -&gt; list[list[list[str]]]:\n \"\"\"n クイーン問題を解く\"\"\"\n # n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n state = [[\"#\" for _ in range(n)] for _ in range(n)]\n cols = [False] * n # 列にクイーンがあるかどうかを記録する\n diags1 = [False] * (2 * n - 1) # 主対角線にクイーンがあるかどうかを記録する\n diags2 = [False] * (2 * n - 1) # 副対角線にクイーンがあるかどうかを記録する\n res = []\n backtrack(0, n, state, res, cols, diags1, diags2)\n\n return res\n</code></pre> n_queens.cpp<pre><code>/* バックトラッキング:n クイーン */\nvoid backtrack(int row, int n, vector&lt;vector&lt;string&gt;&gt; &amp;state, vector&lt;vector&lt;vector&lt;string&gt;&gt;&gt; &amp;res, vector&lt;bool&gt; &amp;cols,\n vector&lt;bool&gt; &amp;diags1, vector&lt;bool&gt; &amp;diags2) {\n // すべての行への配置が終わったら、解を記録する\n if (row == n) {\n res.push_back(state);\n return;\n }\n // すべての列を走査する\n for (int col = 0; col &lt; n; col++) {\n // このマスに対応する主対角線と副対角線を求める\n int diag1 = row - col + n - 1;\n int diag2 = row + col;\n // 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if (!cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2]) {\n // 試行:このマスにクイーンを配置する\n state[row][col] = \"Q\";\n cols[col] = diags1[diag1] = diags2[diag2] = true;\n // 次の行に配置する\n backtrack(row + 1, n, state, res, cols, diags1, diags2);\n // 復元:このマスを空きマスに戻す\n state[row][col] = \"#\";\n cols[col] = diags1[diag1] = diags2[diag2] = false;\n }\n }\n}\n\n/* n クイーン問題を解く */\nvector&lt;vector&lt;vector&lt;string&gt;&gt;&gt; nQueens(int n) {\n // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n vector&lt;vector&lt;string&gt;&gt; state(n, vector&lt;string&gt;(n, \"#\"));\n vector&lt;bool&gt; cols(n, false); // 列にクイーンがあるかどうかを記録する\n vector&lt;bool&gt; diags1(2 * n - 1, false); // 主対角線にクイーンがあるかどうかを記録する\n vector&lt;bool&gt; diags2(2 * n - 1, false); // 副対角線にクイーンがあるかどうかを記録する\n vector&lt;vector&lt;vector&lt;string&gt;&gt;&gt; res;\n\n backtrack(0, n, state, res, cols, diags1, diags2);\n\n return res;\n}\n</code></pre> n_queens.java<pre><code>/* バックトラッキング:n クイーン */\nvoid backtrack(int row, int n, List&lt;List&lt;String&gt;&gt; state, List&lt;List&lt;List&lt;String&gt;&gt;&gt; res,\n boolean[] cols, boolean[] diags1, boolean[] diags2) {\n // すべての行への配置が終わったら、解を記録する\n if (row == n) {\n List&lt;List&lt;String&gt;&gt; copyState = new ArrayList&lt;&gt;();\n for (List&lt;String&gt; sRow : state) {\n copyState.add(new ArrayList&lt;&gt;(sRow));\n }\n res.add(copyState);\n return;\n }\n // すべての列を走査する\n for (int col = 0; col &lt; n; col++) {\n // このマスに対応する主対角線と副対角線を求める\n int diag1 = row - col + n - 1;\n int diag2 = row + col;\n // 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if (!cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2]) {\n // 試行:このマスにクイーンを配置する\n state.get(row).set(col, \"Q\");\n cols[col] = diags1[diag1] = diags2[diag2] = true;\n // 次の行に配置する\n backtrack(row + 1, n, state, res, cols, diags1, diags2);\n // 復元:このマスを空きマスに戻す\n state.get(row).set(col, \"#\");\n cols[col] = diags1[diag1] = diags2[diag2] = false;\n }\n }\n}\n\n/* n クイーン問題を解く */\nList&lt;List&lt;List&lt;String&gt;&gt;&gt; nQueens(int n) {\n // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n List&lt;List&lt;String&gt;&gt; state = new ArrayList&lt;&gt;();\n for (int i = 0; i &lt; n; i++) {\n List&lt;String&gt; row = new ArrayList&lt;&gt;();\n for (int j = 0; j &lt; n; j++) {\n row.add(\"#\");\n }\n state.add(row);\n }\n boolean[] cols = new boolean[n]; // 列にクイーンがあるかどうかを記録する\n boolean[] diags1 = new boolean[2 * n - 1]; // 主対角線にクイーンがあるかどうかを記録する\n boolean[] diags2 = new boolean[2 * n - 1]; // 副対角線にクイーンがあるかどうかを記録する\n List&lt;List&lt;List&lt;String&gt;&gt;&gt; res = new ArrayList&lt;&gt;();\n\n backtrack(0, n, state, res, cols, diags1, diags2);\n\n return res;\n}\n</code></pre> n_queens.cs<pre><code>/* バックトラッキング:n クイーン */\nvoid Backtrack(int row, int n, List&lt;List&lt;string&gt;&gt; state, List&lt;List&lt;List&lt;string&gt;&gt;&gt; res,\n bool[] cols, bool[] diags1, bool[] diags2) {\n // すべての行への配置が終わったら、解を記録する\n if (row == n) {\n List&lt;List&lt;string&gt;&gt; copyState = [];\n foreach (List&lt;string&gt; sRow in state) {\n copyState.Add(new List&lt;string&gt;(sRow));\n }\n res.Add(copyState);\n return;\n }\n // すべての列を走査する\n for (int col = 0; col &lt; n; col++) {\n // このマスに対応する主対角線と副対角線を求める\n int diag1 = row - col + n - 1;\n int diag2 = row + col;\n // 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if (!cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2]) {\n // 試行:このマスにクイーンを配置する\n state[row][col] = \"Q\";\n cols[col] = diags1[diag1] = diags2[diag2] = true;\n // 次の行に配置する\n Backtrack(row + 1, n, state, res, cols, diags1, diags2);\n // 復元:このマスを空きマスに戻す\n state[row][col] = \"#\";\n cols[col] = diags1[diag1] = diags2[diag2] = false;\n }\n }\n}\n\n/* n クイーン問題を解く */\nList&lt;List&lt;List&lt;string&gt;&gt;&gt; NQueens(int n) {\n // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n List&lt;List&lt;string&gt;&gt; state = [];\n for (int i = 0; i &lt; n; i++) {\n List&lt;string&gt; row = [];\n for (int j = 0; j &lt; n; j++) {\n row.Add(\"#\");\n }\n state.Add(row);\n }\n bool[] cols = new bool[n]; // 列にクイーンがあるかどうかを記録する\n bool[] diags1 = new bool[2 * n - 1]; // 主対角線にクイーンがあるかどうかを記録する\n bool[] diags2 = new bool[2 * n - 1]; // 副対角線にクイーンがあるかどうかを記録する\n List&lt;List&lt;List&lt;string&gt;&gt;&gt; res = [];\n\n Backtrack(0, n, state, res, cols, diags1, diags2);\n\n return res;\n}\n</code></pre> n_queens.go<pre><code>/* バックトラッキング:n クイーン */\nfunc backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) {\n // すべての行への配置が終わったら、解を記録する\n if row == n {\n newState := make([][]string, len(*state))\n for i, _ := range newState {\n newState[i] = make([]string, len((*state)[0]))\n copy(newState[i], (*state)[i])\n\n }\n *res = append(*res, newState)\n return\n }\n // すべての列を走査する\n for col := 0; col &lt; n; col++ {\n // このマスに対応する主対角線と副対角線を求める\n diag1 := row - col + n - 1\n diag2 := row + col\n // 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if !(*cols)[col] &amp;&amp; !(*diags1)[diag1] &amp;&amp; !(*diags2)[diag2] {\n // 試行:このマスにクイーンを配置する\n (*state)[row][col] = \"Q\"\n (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true\n // 次の行に配置する\n backtrack(row+1, n, state, res, cols, diags1, diags2)\n // 復元:このマスを空きマスに戻す\n (*state)[row][col] = \"#\"\n (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false\n }\n }\n}\n\n/* n クイーン問題を解く */\nfunc nQueens(n int) [][][]string {\n // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n state := make([][]string, n)\n for i := 0; i &lt; n; i++ {\n row := make([]string, n)\n for i := 0; i &lt; n; i++ {\n row[i] = \"#\"\n }\n state[i] = row\n }\n // 列にクイーンがあるかどうかを記録する\n cols := make([]bool, n)\n diags1 := make([]bool, 2*n-1)\n diags2 := make([]bool, 2*n-1)\n res := make([][][]string, 0)\n backtrack(0, n, &amp;state, &amp;res, &amp;cols, &amp;diags1, &amp;diags2)\n return res\n}\n</code></pre> n_queens.swift<pre><code>/* バックトラッキング:n クイーン */\nfunc backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) {\n // すべての行への配置が終わったら、解を記録する\n if row == n {\n res.append(state)\n return\n }\n // すべての列を走査する\n for col in 0 ..&lt; n {\n // このマスに対応する主対角線と副対角線を求める\n let diag1 = row - col + n - 1\n let diag2 = row + col\n // 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if !cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2] {\n // 試行:このマスにクイーンを配置する\n state[row][col] = \"Q\"\n cols[col] = true\n diags1[diag1] = true\n diags2[diag2] = true\n // 次の行に配置する\n backtrack(row: row + 1, n: n, state: &amp;state, res: &amp;res, cols: &amp;cols, diags1: &amp;diags1, diags2: &amp;diags2)\n // 復元:このマスを空きマスに戻す\n state[row][col] = \"#\"\n cols[col] = false\n diags1[diag1] = false\n diags2[diag2] = false\n }\n }\n}\n\n/* n クイーン問題を解く */\nfunc nQueens(n: Int) -&gt; [[[String]]] {\n // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n var state = Array(repeating: Array(repeating: \"#\", count: n), count: n)\n var cols = Array(repeating: false, count: n) // 列にクイーンがあるかどうかを記録する\n var diags1 = Array(repeating: false, count: 2 * n - 1) // 主対角線にクイーンがあるかどうかを記録する\n var diags2 = Array(repeating: false, count: 2 * n - 1) // 副対角線にクイーンがあるかどうかを記録する\n var res: [[[String]]] = []\n\n backtrack(row: 0, n: n, state: &amp;state, res: &amp;res, cols: &amp;cols, diags1: &amp;diags1, diags2: &amp;diags2)\n\n return res\n}\n</code></pre> n_queens.js<pre><code>/* バックトラッキング:n クイーン */\nfunction backtrack(row, n, state, res, cols, diags1, diags2) {\n // すべての行への配置が終わったら、解を記録する\n if (row === n) {\n res.push(state.map((row) =&gt; row.slice()));\n return;\n }\n // すべての列を走査する\n for (let col = 0; col &lt; n; col++) {\n // このマスに対応する主対角線と副対角線を求める\n const diag1 = row - col + n - 1;\n const diag2 = row + col;\n // 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if (!cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2]) {\n // 試行:このマスにクイーンを配置する\n state[row][col] = 'Q';\n cols[col] = diags1[diag1] = diags2[diag2] = true;\n // 次の行に配置する\n backtrack(row + 1, n, state, res, cols, diags1, diags2);\n // 復元:このマスを空きマスに戻す\n state[row][col] = '#';\n cols[col] = diags1[diag1] = diags2[diag2] = false;\n }\n }\n}\n\n/* n クイーン問題を解く */\nfunction nQueens(n) {\n // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n const state = Array.from({ length: n }, () =&gt; Array(n).fill('#'));\n const cols = Array(n).fill(false); // 列にクイーンがあるかどうかを記録する\n const diags1 = Array(2 * n - 1).fill(false); // 主対角線にクイーンがあるかどうかを記録する\n const diags2 = Array(2 * n - 1).fill(false); // 副対角線にクイーンがあるかどうかを記録する\n const res = [];\n\n backtrack(0, n, state, res, cols, diags1, diags2);\n return res;\n}\n</code></pre> n_queens.ts<pre><code>/* バックトラッキング:n クイーン */\nfunction backtrack(\n row: number,\n n: number,\n state: string[][],\n res: string[][][],\n cols: boolean[],\n diags1: boolean[],\n diags2: boolean[]\n): void {\n // すべての行への配置が終わったら、解を記録する\n if (row === n) {\n res.push(state.map((row) =&gt; row.slice()));\n return;\n }\n // すべての列を走査する\n for (let col = 0; col &lt; n; col++) {\n // このマスに対応する主対角線と副対角線を求める\n const diag1 = row - col + n - 1;\n const diag2 = row + col;\n // 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if (!cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2]) {\n // 試行:このマスにクイーンを配置する\n state[row][col] = 'Q';\n cols[col] = diags1[diag1] = diags2[diag2] = true;\n // 次の行に配置する\n backtrack(row + 1, n, state, res, cols, diags1, diags2);\n // 復元:このマスを空きマスに戻す\n state[row][col] = '#';\n cols[col] = diags1[diag1] = diags2[diag2] = false;\n }\n }\n}\n\n/* n クイーン問題を解く */\nfunction nQueens(n: number): string[][][] {\n // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n const state = Array.from({ length: n }, () =&gt; Array(n).fill('#'));\n const cols = Array(n).fill(false); // 列にクイーンがあるかどうかを記録する\n const diags1 = Array(2 * n - 1).fill(false); // 主対角線にクイーンがあるかどうかを記録する\n const diags2 = Array(2 * n - 1).fill(false); // 副対角線にクイーンがあるかどうかを記録する\n const res: string[][][] = [];\n\n backtrack(0, n, state, res, cols, diags1, diags2);\n return res;\n}\n</code></pre> n_queens.dart<pre><code>/* バックトラッキング:n クイーン */\nvoid backtrack(\n int row,\n int n,\n List&lt;List&lt;String&gt;&gt; state,\n List&lt;List&lt;List&lt;String&gt;&gt;&gt; res,\n List&lt;bool&gt; cols,\n List&lt;bool&gt; diags1,\n List&lt;bool&gt; diags2,\n) {\n // すべての行への配置が終わったら、解を記録する\n if (row == n) {\n List&lt;List&lt;String&gt;&gt; copyState = [];\n for (List&lt;String&gt; sRow in state) {\n copyState.add(List.from(sRow));\n }\n res.add(copyState);\n return;\n }\n // すべての列を走査する\n for (int col = 0; col &lt; n; col++) {\n // このマスに対応する主対角線と副対角線を求める\n int diag1 = row - col + n - 1;\n int diag2 = row + col;\n // 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if (!cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2]) {\n // 試行:このマスにクイーンを配置する\n state[row][col] = \"Q\";\n cols[col] = true;\n diags1[diag1] = true;\n diags2[diag2] = true;\n // 次の行に配置する\n backtrack(row + 1, n, state, res, cols, diags1, diags2);\n // 復元:このマスを空きマスに戻す\n state[row][col] = \"#\";\n cols[col] = false;\n diags1[diag1] = false;\n diags2[diag2] = false;\n }\n }\n}\n\n/* n クイーン問題を解く */\nList&lt;List&lt;List&lt;String&gt;&gt;&gt; nQueens(int n) {\n // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n List&lt;List&lt;String&gt;&gt; state = List.generate(n, (index) =&gt; List.filled(n, \"#\"));\n List&lt;bool&gt; cols = List.filled(n, false); // 列にクイーンがあるかどうかを記録する\n List&lt;bool&gt; diags1 = List.filled(2 * n - 1, false); // 主対角線にクイーンがあるかどうかを記録する\n List&lt;bool&gt; diags2 = List.filled(2 * n - 1, false); // 副対角線にクイーンがあるかどうかを記録する\n List&lt;List&lt;List&lt;String&gt;&gt;&gt; res = [];\n\n backtrack(0, n, state, res, cols, diags1, diags2);\n\n return res;\n}\n</code></pre> n_queens.rs<pre><code>/* バックトラッキング:n クイーン */\nfn backtrack(\n row: usize,\n n: usize,\n state: &amp;mut Vec&lt;Vec&lt;String&gt;&gt;,\n res: &amp;mut Vec&lt;Vec&lt;Vec&lt;String&gt;&gt;&gt;,\n cols: &amp;mut [bool],\n diags1: &amp;mut [bool],\n diags2: &amp;mut [bool],\n) {\n // すべての行への配置が終わったら、解を記録する\n if row == n {\n res.push(state.clone());\n return;\n }\n // すべての列を走査する\n for col in 0..n {\n // このマスに対応する主対角線と副対角線を求める\n let diag1 = row + n - 1 - col;\n let diag2 = row + col;\n // 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if !cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2] {\n // 試行:このマスにクイーンを配置する\n state[row][col] = \"Q\".into();\n (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true);\n // 次の行に配置する\n backtrack(row + 1, n, state, res, cols, diags1, diags2);\n // 復元:このマスを空きマスに戻す\n state[row][col] = \"#\".into();\n (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false);\n }\n }\n}\n\n/* n クイーン問題を解く */\nfn n_queens(n: usize) -&gt; Vec&lt;Vec&lt;Vec&lt;String&gt;&gt;&gt; {\n // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n let mut state: Vec&lt;Vec&lt;String&gt;&gt; = vec![vec![\"#\".to_string(); n]; n];\n let mut cols = vec![false; n]; // 列にクイーンがあるかどうかを記録する\n let mut diags1 = vec![false; 2 * n - 1]; // 主対角線にクイーンがあるかどうかを記録する\n let mut diags2 = vec![false; 2 * n - 1]; // 副対角線にクイーンがあるかどうかを記録する\n let mut res: Vec&lt;Vec&lt;Vec&lt;String&gt;&gt;&gt; = Vec::new();\n\n backtrack(\n 0,\n n,\n &amp;mut state,\n &amp;mut res,\n &amp;mut cols,\n &amp;mut diags1,\n &amp;mut diags2,\n );\n\n res\n}\n</code></pre> n_queens.c<pre><code>/* バックトラッキング:n クイーン */\nvoid backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE],\n bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) {\n // すべての行への配置が終わったら、解を記録する\n if (row == n) {\n res[*resSize] = (char **)malloc(sizeof(char *) * n);\n for (int i = 0; i &lt; n; ++i) {\n res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1));\n strcpy(res[*resSize][i], state[i]);\n }\n (*resSize)++;\n return;\n }\n // すべての列を走査する\n for (int col = 0; col &lt; n; col++) {\n // このマスに対応する主対角線と副対角線を求める\n int diag1 = row - col + n - 1;\n int diag2 = row + col;\n // 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if (!cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2]) {\n // 試行:このマスにクイーンを配置する\n state[row][col] = 'Q';\n cols[col] = diags1[diag1] = diags2[diag2] = true;\n // 次の行に配置する\n backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2);\n // 復元:このマスを空きマスに戻す\n state[row][col] = '#';\n cols[col] = diags1[diag1] = diags2[diag2] = false;\n }\n }\n}\n\n/* n クイーン問題を解く */\nchar ***nQueens(int n, int *returnSize) {\n char state[MAX_SIZE][MAX_SIZE];\n // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n for (int i = 0; i &lt; n; ++i) {\n for (int j = 0; j &lt; n; ++j) {\n state[i][j] = '#';\n }\n state[i][n] = '\\0';\n }\n bool cols[MAX_SIZE] = {false}; // 列にクイーンがあるかどうかを記録する\n bool diags1[2 * MAX_SIZE - 1] = {false}; // 主対角線にクイーンがあるかどうかを記録する\n bool diags2[2 * MAX_SIZE - 1] = {false}; // 副対角線にクイーンがあるかどうかを記録する\n\n char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE);\n *returnSize = 0;\n backtrack(0, n, state, res, returnSize, cols, diags1, diags2);\n return res;\n}\n</code></pre> n_queens.kt<pre><code>/* バックトラッキング:n クイーン */\nfun backtrack(\n row: Int,\n n: Int,\n state: MutableList&lt;MutableList&lt;String&gt;&gt;,\n res: MutableList&lt;MutableList&lt;MutableList&lt;String&gt;&gt;?&gt;,\n cols: BooleanArray,\n diags1: BooleanArray,\n diags2: BooleanArray\n) {\n // すべての行への配置が終わったら、解を記録する\n if (row == n) {\n val copyState = mutableListOf&lt;MutableList&lt;String&gt;&gt;()\n for (sRow in state) {\n copyState.add(sRow.toMutableList())\n }\n res.add(copyState)\n return\n }\n // すべての列を走査する\n for (col in 0..&lt;n) {\n // このマスに対応する主対角線と副対角線を求める\n val diag1 = row - col + n - 1\n val diag2 = row + col\n // 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if (!cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2]) {\n // 試行:このマスにクイーンを配置する\n state[row][col] = \"Q\"\n diags2[diag2] = true\n diags1[diag1] = diags2[diag2]\n cols[col] = diags1[diag1]\n // 次の行に配置する\n backtrack(row + 1, n, state, res, cols, diags1, diags2)\n // 復元:このマスを空きマスに戻す\n state[row][col] = \"#\"\n diags2[diag2] = false\n diags1[diag1] = diags2[diag2]\n cols[col] = diags1[diag1]\n }\n }\n}\n\n/* n クイーン問題を解く */\nfun nQueens(n: Int): MutableList&lt;MutableList&lt;MutableList&lt;String&gt;&gt;?&gt; {\n // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n val state = mutableListOf&lt;MutableList&lt;String&gt;&gt;()\n for (i in 0..&lt;n) {\n val row = mutableListOf&lt;String&gt;()\n for (j in 0..&lt;n) {\n row.add(\"#\")\n }\n state.add(row)\n }\n val cols = BooleanArray(n) // 列にクイーンがあるかどうかを記録する\n val diags1 = BooleanArray(2 * n - 1) // 主対角線にクイーンがあるかどうかを記録する\n val diags2 = BooleanArray(2 * n - 1) // 副対角線にクイーンがあるかどうかを記録する\n val res = mutableListOf&lt;MutableList&lt;MutableList&lt;String&gt;&gt;?&gt;()\n\n backtrack(0, n, state, res, cols, diags1, diags2)\n\n return res\n}\n</code></pre> n_queens.rb<pre><code>### バックトラッキング:n クイーン ###\ndef backtrack(row, n, state, res, cols, diags1, diags2)\n # すべての行への配置が終わったら、解を記録する\n if row == n\n res &lt;&lt; state.map { |row| row.dup }\n return\n end\n\n # すべての列を走査する\n for col in 0...n\n # このマスに対応する主対角線と副対角線を求める\n diag1 = row - col + n - 1\n diag2 = row + col\n # 枝刈り:このマスと同じ列、主対角線、副対角線にクイーンがあってはならない\n if !cols[col] &amp;&amp; !diags1[diag1] &amp;&amp; !diags2[diag2]\n # 試行:このマスにクイーンを配置する\n state[row][col] = \"Q\"\n cols[col] = diags1[diag1] = diags2[diag2] = true\n # 次の行に配置する\n backtrack(row + 1, n, state, res, cols, diags1, diags2)\n # 復元:このマスを空きマスに戻す\n state[row][col] = \"#\"\n cols[col] = diags1[diag1] = diags2[diag2] = false\n end\n end\nend\n\n### n クイーン問題を解く ###\ndef n_queens(n)\n # n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す\n state = Array.new(n) { Array.new(n, \"#\") }\n cols = Array.new(n, false) # 列にクイーンがあるかどうかを記録する\n diags1 = Array.new(2 * n - 1, false) # 主対角線にクイーンがあるかどうかを記録する\n diags2 = Array.new(2 * n - 1, false) # 副対角線にクイーンがあるかどうかを記録する\n res = []\n backtrack(0, n, state, res, cols, diags1, diags2)\n\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>行ごとの配置を \\(n\\) 回行います。列の制約を考慮すると、先頭行から最後の行までの選択肢は \\(n\\)、\\(n-1\\)、\\(\\dots\\)、\\(2\\)、\\(1\\) 個なので、\\(O(n!)\\) 時間かかります。解を記録するときは行列 <code>state</code> をコピーして <code>res</code> に追加し、コピーには \\(O(n^2)\\) 時間かかります。したがって、全体の時間計算量は \\(O(n! \\cdot n^2)\\) です。実際には、対角線の制約による枝刈りでも探索空間が大きく減るため、この計算量から想定されるより効率よく探索できることが多くなります。</p> <p>配列 <code>state</code> は \\(O(n^2)\\)、配列 <code>cols</code>、<code>diags1</code>、<code>diags2</code> はそれぞれ \\(O(n)\\) の空間を使います。最大再帰深度は \\(n\\) なので、スタックフレームに \\(O(n)\\) の空間を使います。したがって、空間計算量は \\(O(n^2)\\) です。</p>","path":["第 13 章   バックトラッキング","13.4   n クイーン問題"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/","level":1,"title":"13.2   順列列挙問題","text":"<p>順列列挙問題は、バックトラッキングの典型的な応用です。配列や文字列などの集合が与えられ、その要素の可能な並べ方をすべて求める問題です。</p> <p>表 13-2 に、入力配列と、それに対応するすべての順列の例を示します。</p> <p> 表 13-2   順列列挙の例 </p> 入力配列 すべての順列 \\([1]\\) \\([1]\\) \\([1, 2]\\) \\([1, 2], [2, 1]\\) \\([1, 2, 3]\\) \\([1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]\\)","path":["第 13 章   バックトラッキング","13.2   順列列挙問題"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1321","level":2,"title":"13.2.1   同じ値の要素がない場合","text":"<p>問題</p> <p>重複した要素を含まない整数配列が与えられます。可能な順列をすべて返してください。</p> <p>バックトラッキングの観点からは、順列を作る過程を、一連の選択の結果と捉えられます。入力が \\([1, 2, 3]\\) なら、最初に \\(1\\)、次に \\(3\\)、最後に \\(2\\) を選ぶと、順列 \\([1, 3, 2]\\) になります。後戻りでは一つの選択を取り消し、ほかの選択肢を試します。</p> <p>コード上では、候補集合 <code>choices</code> は入力配列の全要素、状態 <code>state</code> は現在までに選んだ要素です。各要素は一度しか選べないため、<code>state</code> 内の要素は重複してはいけません。</p> <p>図 13-5 のように、探索過程を再帰木として展開でき、各ノードが現在の状態 <code>state</code> を表します。根から 3 回選択すると葉に達し、各葉が一つの順列に対応します。</p> <p></p> <p> 図 13-5   順列列挙の再帰木 </p>","path":["第 13 章   バックトラッキング","13.2   順列列挙問題"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1","level":3,"title":"1.   同じ要素を再選択する分岐の枝刈り","text":"<p>各要素を一度だけ選ぶため、ブール配列 <code>selected</code> を使います。<code>selected[i]</code> は <code>choices[i]</code> が選択済みかを表し、次のように枝刈りします。</p> <ul> <li><code>choices[i]</code> を選んだら、<code>selected[i]</code> を \\(\\text{True}\\) にして選択済みとします。</li> <li>選択肢 <code>choices</code> を走査するとき、選択済みのノードをすべて飛ばし、枝刈りします。</li> </ul> <p>図 13-6 のように、1 回目に 1、2 回目に 3、3 回目に 2 を選ぶ場合、2 回目には要素 1 の分岐を、3 回目には要素 1 と 3 の分岐を刈り取ります。</p> <p></p> <p> 図 13-6   順列列挙の枝刈りの例 </p> <p>この枝刈りにより、探索空間の大きさは \\(O(n^n)\\) から \\(O(n!)\\) に小さくなります。</p>","path":["第 13 章   バックトラッキング","13.2   順列列挙問題"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#2","level":3,"title":"2.   コードの実装","text":"<p>以上を整理できれば、基本形のコードを「穴埋め」できます。コード全体を短くするため、基本形の各関数を別々には定義せず、<code>backtrack()</code> 内に展開します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby permutations_i.py<pre><code>def backtrack(\n state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]\n):\n \"\"\"バックトラッキング:すべての順列 I\"\"\"\n # 状態の長さが要素数と等しくなったら、解を記録する\n if len(state) == len(choices):\n res.append(list(state))\n return\n # すべての選択肢を走査する\n for i, choice in enumerate(choices):\n # 枝刈り:同じ要素を繰り返し選んではならない\n if not selected[i]:\n # 試行:選択を行い、状態を更新する\n selected[i] = True\n state.append(choice)\n # 次の選択に進む\n backtrack(state, choices, selected, res)\n # 復元:選択を取り消し、以前の状態に戻す\n selected[i] = False\n state.pop()\n\ndef permutations_i(nums: list[int]) -&gt; list[list[int]]:\n \"\"\"すべての順列 I\"\"\"\n res = []\n backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)\n return res\n</code></pre> permutations_i.cpp<pre><code>/* バックトラッキング:すべての順列 I */\nvoid backtrack(vector&lt;int&gt; &amp;state, const vector&lt;int&gt; &amp;choices, vector&lt;bool&gt; &amp;selected, vector&lt;vector&lt;int&gt;&gt; &amp;res) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.size() == choices.size()) {\n res.push_back(state);\n return;\n }\n // すべての選択肢を走査する\n for (int i = 0; i &lt; choices.size(); i++) {\n int choice = choices[i];\n // 枝刈り:同じ要素を繰り返し選んではならない\n if (!selected[i]) {\n // 試行:選択を行い、状態を更新する\n selected[i] = true;\n state.push_back(choice);\n // 次の選択に進む\n backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.pop_back();\n }\n }\n}\n\n/* すべての順列 I */\nvector&lt;vector&lt;int&gt;&gt; permutationsI(vector&lt;int&gt; nums) {\n vector&lt;int&gt; state;\n vector&lt;bool&gt; selected(nums.size(), false);\n vector&lt;vector&lt;int&gt;&gt; res;\n backtrack(state, nums, selected, res);\n return res;\n}\n</code></pre> permutations_i.java<pre><code>/* バックトラッキング:すべての順列 I */\nvoid backtrack(List&lt;Integer&gt; state, int[] choices, boolean[] selected, List&lt;List&lt;Integer&gt;&gt; res) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.size() == choices.length) {\n res.add(new ArrayList&lt;Integer&gt;(state));\n return;\n }\n // すべての選択肢を走査する\n for (int i = 0; i &lt; choices.length; i++) {\n int choice = choices[i];\n // 枝刈り:同じ要素を繰り返し選んではならない\n if (!selected[i]) {\n // 試行:選択を行い、状態を更新する\n selected[i] = true;\n state.add(choice);\n // 次の選択に進む\n backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.remove(state.size() - 1);\n }\n }\n}\n\n/* すべての順列 I */\nList&lt;List&lt;Integer&gt;&gt; permutationsI(int[] nums) {\n List&lt;List&lt;Integer&gt;&gt; res = new ArrayList&lt;List&lt;Integer&gt;&gt;();\n backtrack(new ArrayList&lt;Integer&gt;(), nums, new boolean[nums.length], res);\n return res;\n}\n</code></pre> permutations_i.cs<pre><code>/* バックトラッキング:すべての順列 I */\nvoid Backtrack(List&lt;int&gt; state, int[] choices, bool[] selected, List&lt;List&lt;int&gt;&gt; res) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.Count == choices.Length) {\n res.Add(new List&lt;int&gt;(state));\n return;\n }\n // すべての選択肢を走査する\n for (int i = 0; i &lt; choices.Length; i++) {\n int choice = choices[i];\n // 枝刈り:同じ要素を繰り返し選んではならない\n if (!selected[i]) {\n // 試行:選択を行い、状態を更新する\n selected[i] = true;\n state.Add(choice);\n // 次の選択に進む\n Backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.RemoveAt(state.Count - 1);\n }\n }\n}\n\n/* すべての順列 I */\nList&lt;List&lt;int&gt;&gt; PermutationsI(int[] nums) {\n List&lt;List&lt;int&gt;&gt; res = [];\n Backtrack([], nums, new bool[nums.Length], res);\n return res;\n}\n</code></pre> permutations_i.go<pre><code>/* バックトラッキング:すべての順列 I */\nfunc backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if len(*state) == len(*choices) {\n newState := append([]int{}, *state...)\n *res = append(*res, newState)\n }\n // すべての選択肢を走査する\n for i := 0; i &lt; len(*choices); i++ {\n choice := (*choices)[i]\n // 枝刈り:同じ要素を繰り返し選んではならない\n if !(*selected)[i] {\n // 試行:選択を行い、状態を更新する\n (*selected)[i] = true\n *state = append(*state, choice)\n // 次の選択に進む\n backtrackI(state, choices, selected, res)\n // 復元:選択を取り消し、以前の状態に戻す\n (*selected)[i] = false\n *state = (*state)[:len(*state)-1]\n }\n }\n}\n\n/* すべての順列 I */\nfunc permutationsI(nums []int) [][]int {\n res := make([][]int, 0)\n state := make([]int, 0)\n selected := make([]bool, len(nums))\n backtrackI(&amp;state, &amp;nums, &amp;selected, &amp;res)\n return res\n}\n</code></pre> permutations_i.swift<pre><code>/* バックトラッキング:すべての順列 I */\nfunc backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if state.count == choices.count {\n res.append(state)\n return\n }\n // すべての選択肢を走査する\n for (i, choice) in choices.enumerated() {\n // 枝刈り:同じ要素を繰り返し選んではならない\n if !selected[i] {\n // 試行:選択を行い、状態を更新する\n selected[i] = true\n state.append(choice)\n // 次の選択に進む\n backtrack(state: &amp;state, choices: choices, selected: &amp;selected, res: &amp;res)\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false\n state.removeLast()\n }\n }\n}\n\n/* すべての順列 I */\nfunc permutationsI(nums: [Int]) -&gt; [[Int]] {\n var state: [Int] = []\n var selected = Array(repeating: false, count: nums.count)\n var res: [[Int]] = []\n backtrack(state: &amp;state, choices: nums, selected: &amp;selected, res: &amp;res)\n return res\n}\n</code></pre> permutations_i.js<pre><code>/* バックトラッキング:すべての順列 I */\nfunction backtrack(state, choices, selected, res) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.length === choices.length) {\n res.push([...state]);\n return;\n }\n // すべての選択肢を走査する\n choices.forEach((choice, i) =&gt; {\n // 枝刈り:同じ要素を繰り返し選んではならない\n if (!selected[i]) {\n // 試行:選択を行い、状態を更新する\n selected[i] = true;\n state.push(choice);\n // 次の選択に進む\n backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.pop();\n }\n });\n}\n\n/* すべての順列 I */\nfunction permutationsI(nums) {\n const res = [];\n backtrack([], nums, Array(nums.length).fill(false), res);\n return res;\n}\n</code></pre> permutations_i.ts<pre><code>/* バックトラッキング:すべての順列 I */\nfunction backtrack(\n state: number[],\n choices: number[],\n selected: boolean[],\n res: number[][]\n): void {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.length === choices.length) {\n res.push([...state]);\n return;\n }\n // すべての選択肢を走査する\n choices.forEach((choice, i) =&gt; {\n // 枝刈り:同じ要素を繰り返し選んではならない\n if (!selected[i]) {\n // 試行:選択を行い、状態を更新する\n selected[i] = true;\n state.push(choice);\n // 次の選択に進む\n backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.pop();\n }\n });\n}\n\n/* すべての順列 I */\nfunction permutationsI(nums: number[]): number[][] {\n const res: number[][] = [];\n backtrack([], nums, Array(nums.length).fill(false), res);\n return res;\n}\n</code></pre> permutations_i.dart<pre><code>/* バックトラッキング:すべての順列 I */\nvoid backtrack(\n List&lt;int&gt; state,\n List&lt;int&gt; choices,\n List&lt;bool&gt; selected,\n List&lt;List&lt;int&gt;&gt; res,\n) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.length == choices.length) {\n res.add(List.from(state));\n return;\n }\n // すべての選択肢を走査する\n for (int i = 0; i &lt; choices.length; i++) {\n int choice = choices[i];\n // 枝刈り:同じ要素を繰り返し選んではならない\n if (!selected[i]) {\n // 試行:選択を行い、状態を更新する\n selected[i] = true;\n state.add(choice);\n // 次の選択に進む\n backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.removeLast();\n }\n }\n}\n\n/* すべての順列 I */\nList&lt;List&lt;int&gt;&gt; permutationsI(List&lt;int&gt; nums) {\n List&lt;List&lt;int&gt;&gt; res = [];\n backtrack([], nums, List.filled(nums.length, false), res);\n return res;\n}\n</code></pre> permutations_i.rs<pre><code>/* バックトラッキング:すべての順列 I */\nfn backtrack(mut state: Vec&lt;i32&gt;, choices: &amp;[i32], selected: &amp;mut [bool], res: &amp;mut Vec&lt;Vec&lt;i32&gt;&gt;) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if state.len() == choices.len() {\n res.push(state);\n return;\n }\n // すべての選択肢を走査する\n for i in 0..choices.len() {\n let choice = choices[i];\n // 枝刈り:同じ要素を繰り返し選んではならない\n if !selected[i] {\n // 試行:選択を行い、状態を更新する\n selected[i] = true;\n state.push(choice);\n // 次の選択に進む\n backtrack(state.clone(), choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.pop();\n }\n }\n}\n\n/* すべての順列 I */\nfn permutations_i(nums: &amp;mut [i32]) -&gt; Vec&lt;Vec&lt;i32&gt;&gt; {\n let mut res = Vec::new(); // 状態(部分集合)\n backtrack(Vec::new(), nums, &amp;mut vec![false; nums.len()], &amp;mut res);\n res\n}\n</code></pre> permutations_i.c<pre><code>/* バックトラッキング:すべての順列 I */\nvoid backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (stateSize == choicesSize) {\n res[*resSize] = (int *)malloc(choicesSize * sizeof(int));\n for (int i = 0; i &lt; choicesSize; i++) {\n res[*resSize][i] = state[i];\n }\n (*resSize)++;\n return;\n }\n // すべての選択肢を走査する\n for (int i = 0; i &lt; choicesSize; i++) {\n int choice = choices[i];\n // 枝刈り:同じ要素を繰り返し選んではならない\n if (!selected[i]) {\n // 試行:選択を行い、状態を更新する\n selected[i] = true;\n state[stateSize] = choice;\n // 次の選択に進む\n backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n }\n }\n}\n\n/* すべての順列 I */\nint **permutationsI(int *nums, int numsSize, int *returnSize) {\n int *state = (int *)malloc(numsSize * sizeof(int));\n bool *selected = (bool *)malloc(numsSize * sizeof(bool));\n for (int i = 0; i &lt; numsSize; i++) {\n selected[i] = false;\n }\n int **res = (int **)malloc(MAX_SIZE * sizeof(int *));\n *returnSize = 0;\n\n backtrack(state, 0, nums, numsSize, selected, res, returnSize);\n\n free(state);\n free(selected);\n\n return res;\n}\n</code></pre> permutations_i.kt<pre><code>/* バックトラッキング:すべての順列 I */\nfun backtrack(\n state: MutableList&lt;Int&gt;,\n choices: IntArray,\n selected: BooleanArray,\n res: MutableList&lt;MutableList&lt;Int&gt;?&gt;\n) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.size == choices.size) {\n res.add(state.toMutableList())\n return\n }\n // すべての選択肢を走査する\n for (i in choices.indices) {\n val choice = choices[i]\n // 枝刈り:同じ要素を繰り返し選んではならない\n if (!selected[i]) {\n // 試行:選択を行い、状態を更新する\n selected[i] = true\n state.add(choice)\n // 次の選択に進む\n backtrack(state, choices, selected, res)\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false\n state.removeAt(state.size - 1)\n }\n }\n}\n\n/* すべての順列 I */\nfun permutationsI(nums: IntArray): MutableList&lt;MutableList&lt;Int&gt;?&gt; {\n val res = mutableListOf&lt;MutableList&lt;Int&gt;?&gt;()\n backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)\n return res\n}\n</code></pre> permutations_i.rb<pre><code>### バックトラッキング:すべての順列 I ###\ndef backtrack(state, choices, selected, res)\n # 状態の長さが要素数と等しくなったら、解を記録する\n if state.length == choices.length\n res &lt;&lt; state.dup\n return\n end\n\n # すべての選択肢を走査する\n choices.each_with_index do |choice, i|\n # 枝刈り:同じ要素を繰り返し選んではならない\n unless selected[i]\n # 試行:選択を行い、状態を更新する\n selected[i] = true\n state &lt;&lt; choice\n # 次の選択に進む\n backtrack(state, choices, selected, res)\n # 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false\n state.pop\n end\n end\nend\n\n### すべての順列 I ###\ndef permutations_i(nums)\n res = []\n backtrack([], nums, Array.new(nums.length, false), res)\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 13 章   バックトラッキング","13.2   順列列挙問題"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1322","level":2,"title":"13.2.2   同じ値の要素がある場合","text":"<p>問題</p> <p>整数配列が与えられます。配列には重複した要素が含まれる場合があります。重複しない順列をすべて返してください。</p> <p>入力が \\([1, 1, 2]\\) だとします。二つの \\(1\\) を区別するため、2 番目の \\(1\\) を \\(\\hat{1}\\) と書きます。</p> <p>図 13-7 のように、先ほどの方法で生成した順列の半分は重複しています。</p> <p></p> <p> 図 13-7   重複した順列 </p> <p>重複を除くには、結果をハッシュ集合に入れる方法がすぐに思い浮かびます。しかし、あまり洗練された方法ではありません。同じ順列を生成する分岐は不要なので、事前に見分けて枝刈りすべきです。そうすれば、効率をさらに高められます。</p>","path":["第 13 章   バックトラッキング","13.2   順列列挙問題"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#1_1","level":3,"title":"1.   同じ値の要素による分岐の枝刈り","text":"<p>図 13-8 では、1 回目に \\(1\\) を選ぶ場合と \\(\\hat{1}\\) を選ぶ場合は同等で、その下でできるすべての順列が重複します。したがって、\\(\\hat{1}\\) の分岐を枝刈りします。</p> <p>同様に、1 回目に \\(2\\) を選んだ場合も、2 回目の \\(1\\) と \\(\\hat{1}\\) は重複する分岐を作るため、2 回目の \\(\\hat{1}\\) を枝刈りします。</p> <p>本質的な目標は、ある回の選択において、同じ値を持つ複数の要素を一度だけ選ぶようにすることです。</p> <p></p> <p> 図 13-8   重複する順列の枝刈り </p>","path":["第 13 章   バックトラッキング","13.2   順列列挙問題"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#2_1","level":3,"title":"2.   コードの実装","text":"<p>前の問題のコードをもとに、各回の選択でハッシュ集合 <code>duplicated</code> を作ります。その回にすでに試した要素を記録し、重複する要素を枝刈りします。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby permutations_ii.py<pre><code>def backtrack(\n state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]\n):\n \"\"\"バックトラッキング:すべての順列 II\"\"\"\n # 状態の長さが要素数と等しくなったら、解を記録する\n if len(state) == len(choices):\n res.append(list(state))\n return\n # すべての選択肢を走査する\n duplicated = set[int]()\n for i, choice in enumerate(choices):\n # 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if not selected[i] and choice not in duplicated:\n # 試行:選択を行い、状態を更新する\n duplicated.add(choice) # 選択済みの要素の値を記録する\n selected[i] = True\n state.append(choice)\n # 次の選択に進む\n backtrack(state, choices, selected, res)\n # 復元:選択を取り消し、以前の状態に戻す\n selected[i] = False\n state.pop()\n\ndef permutations_ii(nums: list[int]) -&gt; list[list[int]]:\n \"\"\"すべての順列 II\"\"\"\n res = []\n backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res)\n return res\n</code></pre> permutations_ii.cpp<pre><code>/* バックトラッキング:すべての順列 II */\nvoid backtrack(vector&lt;int&gt; &amp;state, const vector&lt;int&gt; &amp;choices, vector&lt;bool&gt; &amp;selected, vector&lt;vector&lt;int&gt;&gt; &amp;res) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.size() == choices.size()) {\n res.push_back(state);\n return;\n }\n // すべての選択肢を走査する\n unordered_set&lt;int&gt; duplicated;\n for (int i = 0; i &lt; choices.size(); i++) {\n int choice = choices[i];\n // 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if (!selected[i] &amp;&amp; duplicated.find(choice) == duplicated.end()) {\n // 試行:選択を行い、状態を更新する\n duplicated.emplace(choice); // 選択済みの要素の値を記録する\n selected[i] = true;\n state.push_back(choice);\n // 次の選択に進む\n backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.pop_back();\n }\n }\n}\n\n/* すべての順列 II */\nvector&lt;vector&lt;int&gt;&gt; permutationsII(vector&lt;int&gt; nums) {\n vector&lt;int&gt; state;\n vector&lt;bool&gt; selected(nums.size(), false);\n vector&lt;vector&lt;int&gt;&gt; res;\n backtrack(state, nums, selected, res);\n return res;\n}\n</code></pre> permutations_ii.java<pre><code>/* バックトラッキング:すべての順列 II */\nvoid backtrack(List&lt;Integer&gt; state, int[] choices, boolean[] selected, List&lt;List&lt;Integer&gt;&gt; res) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.size() == choices.length) {\n res.add(new ArrayList&lt;Integer&gt;(state));\n return;\n }\n // すべての選択肢を走査する\n Set&lt;Integer&gt; duplicated = new HashSet&lt;Integer&gt;();\n for (int i = 0; i &lt; choices.length; i++) {\n int choice = choices[i];\n // 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if (!selected[i] &amp;&amp; !duplicated.contains(choice)) {\n // 試行:選択を行い、状態を更新する\n duplicated.add(choice); // 選択済みの要素の値を記録する\n selected[i] = true;\n state.add(choice);\n // 次の選択に進む\n backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.remove(state.size() - 1);\n }\n }\n}\n\n/* すべての順列 II */\nList&lt;List&lt;Integer&gt;&gt; permutationsII(int[] nums) {\n List&lt;List&lt;Integer&gt;&gt; res = new ArrayList&lt;List&lt;Integer&gt;&gt;();\n backtrack(new ArrayList&lt;Integer&gt;(), nums, new boolean[nums.length], res);\n return res;\n}\n</code></pre> permutations_ii.cs<pre><code>/* バックトラッキング:すべての順列 II */\nvoid Backtrack(List&lt;int&gt; state, int[] choices, bool[] selected, List&lt;List&lt;int&gt;&gt; res) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.Count == choices.Length) {\n res.Add(new List&lt;int&gt;(state));\n return;\n }\n // すべての選択肢を走査する\n HashSet&lt;int&gt; duplicated = [];\n for (int i = 0; i &lt; choices.Length; i++) {\n int choice = choices[i];\n // 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if (!selected[i] &amp;&amp; !duplicated.Contains(choice)) {\n // 試行:選択を行い、状態を更新する\n duplicated.Add(choice); // 選択済みの要素の値を記録する\n selected[i] = true;\n state.Add(choice);\n // 次の選択に進む\n Backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.RemoveAt(state.Count - 1);\n }\n }\n}\n\n/* すべての順列 II */\nList&lt;List&lt;int&gt;&gt; PermutationsII(int[] nums) {\n List&lt;List&lt;int&gt;&gt; res = [];\n Backtrack([], nums, new bool[nums.Length], res);\n return res;\n}\n</code></pre> permutations_ii.go<pre><code>/* バックトラッキング:すべての順列 II */\nfunc backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if len(*state) == len(*choices) {\n newState := append([]int{}, *state...)\n *res = append(*res, newState)\n }\n // すべての選択肢を走査する\n duplicated := make(map[int]struct{}, 0)\n for i := 0; i &lt; len(*choices); i++ {\n choice := (*choices)[i]\n // 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if _, ok := duplicated[choice]; !ok &amp;&amp; !(*selected)[i] {\n // 試行:選択を行い、状態を更新する\n // 選択済みの要素の値を記録する\n duplicated[choice] = struct{}{}\n (*selected)[i] = true\n *state = append(*state, choice)\n // 次の選択に進む\n backtrackII(state, choices, selected, res)\n // 復元:選択を取り消し、以前の状態に戻す\n (*selected)[i] = false\n *state = (*state)[:len(*state)-1]\n }\n }\n}\n\n/* すべての順列 II */\nfunc permutationsII(nums []int) [][]int {\n res := make([][]int, 0)\n state := make([]int, 0)\n selected := make([]bool, len(nums))\n backtrackII(&amp;state, &amp;nums, &amp;selected, &amp;res)\n return res\n}\n</code></pre> permutations_ii.swift<pre><code>/* バックトラッキング:すべての順列 II */\nfunc backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if state.count == choices.count {\n res.append(state)\n return\n }\n // すべての選択肢を走査する\n var duplicated: Set&lt;Int&gt; = []\n for (i, choice) in choices.enumerated() {\n // 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if !selected[i], !duplicated.contains(choice) {\n // 試行:選択を行い、状態を更新する\n duplicated.insert(choice) // 選択済みの要素の値を記録する\n selected[i] = true\n state.append(choice)\n // 次の選択に進む\n backtrack(state: &amp;state, choices: choices, selected: &amp;selected, res: &amp;res)\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false\n state.removeLast()\n }\n }\n}\n\n/* すべての順列 II */\nfunc permutationsII(nums: [Int]) -&gt; [[Int]] {\n var state: [Int] = []\n var selected = Array(repeating: false, count: nums.count)\n var res: [[Int]] = []\n backtrack(state: &amp;state, choices: nums, selected: &amp;selected, res: &amp;res)\n return res\n}\n</code></pre> permutations_ii.js<pre><code>/* バックトラッキング:すべての順列 II */\nfunction backtrack(state, choices, selected, res) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.length === choices.length) {\n res.push([...state]);\n return;\n }\n // すべての選択肢を走査する\n const duplicated = new Set();\n choices.forEach((choice, i) =&gt; {\n // 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if (!selected[i] &amp;&amp; !duplicated.has(choice)) {\n // 試行:選択を行い、状態を更新する\n duplicated.add(choice); // 選択済みの要素の値を記録する\n selected[i] = true;\n state.push(choice);\n // 次の選択に進む\n backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.pop();\n }\n });\n}\n\n/* すべての順列 II */\nfunction permutationsII(nums) {\n const res = [];\n backtrack([], nums, Array(nums.length).fill(false), res);\n return res;\n}\n</code></pre> permutations_ii.ts<pre><code>/* バックトラッキング:すべての順列 II */\nfunction backtrack(\n state: number[],\n choices: number[],\n selected: boolean[],\n res: number[][]\n): void {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.length === choices.length) {\n res.push([...state]);\n return;\n }\n // すべての選択肢を走査する\n const duplicated = new Set();\n choices.forEach((choice, i) =&gt; {\n // 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if (!selected[i] &amp;&amp; !duplicated.has(choice)) {\n // 試行:選択を行い、状態を更新する\n duplicated.add(choice); // 選択済みの要素の値を記録する\n selected[i] = true;\n state.push(choice);\n // 次の選択に進む\n backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.pop();\n }\n });\n}\n\n/* すべての順列 II */\nfunction permutationsII(nums: number[]): number[][] {\n const res: number[][] = [];\n backtrack([], nums, Array(nums.length).fill(false), res);\n return res;\n}\n</code></pre> permutations_ii.dart<pre><code>/* バックトラッキング:すべての順列 II */\nvoid backtrack(\n List&lt;int&gt; state,\n List&lt;int&gt; choices,\n List&lt;bool&gt; selected,\n List&lt;List&lt;int&gt;&gt; res,\n) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.length == choices.length) {\n res.add(List.from(state));\n return;\n }\n // すべての選択肢を走査する\n Set&lt;int&gt; duplicated = {};\n for (int i = 0; i &lt; choices.length; i++) {\n int choice = choices[i];\n // 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if (!selected[i] &amp;&amp; !duplicated.contains(choice)) {\n // 試行:選択を行い、状態を更新する\n duplicated.add(choice); // 選択済みの要素の値を記録する\n selected[i] = true;\n state.add(choice);\n // 次の選択に進む\n backtrack(state, choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.removeLast();\n }\n }\n}\n\n/* すべての順列 II */\nList&lt;List&lt;int&gt;&gt; permutationsII(List&lt;int&gt; nums) {\n List&lt;List&lt;int&gt;&gt; res = [];\n backtrack([], nums, List.filled(nums.length, false), res);\n return res;\n}\n</code></pre> permutations_ii.rs<pre><code>/* バックトラッキング:すべての順列 II */\nfn backtrack(mut state: Vec&lt;i32&gt;, choices: &amp;[i32], selected: &amp;mut [bool], res: &amp;mut Vec&lt;Vec&lt;i32&gt;&gt;) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if state.len() == choices.len() {\n res.push(state);\n return;\n }\n // すべての選択肢を走査する\n let mut duplicated = HashSet::&lt;i32&gt;::new();\n for i in 0..choices.len() {\n let choice = choices[i];\n // 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if !selected[i] &amp;&amp; !duplicated.contains(&amp;choice) {\n // 試行:選択を行い、状態を更新する\n duplicated.insert(choice); // 選択済みの要素の値を記録する\n selected[i] = true;\n state.push(choice);\n // 次の選択に進む\n backtrack(state.clone(), choices, selected, res);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n state.pop();\n }\n }\n}\n\n/* すべての順列 II */\nfn permutations_ii(nums: &amp;mut [i32]) -&gt; Vec&lt;Vec&lt;i32&gt;&gt; {\n let mut res = Vec::new();\n backtrack(Vec::new(), nums, &amp;mut vec![false; nums.len()], &amp;mut res);\n res\n}\n</code></pre> permutations_ii.c<pre><code>/* バックトラッキング:すべての順列 II */\nvoid backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (stateSize == choicesSize) {\n res[*resSize] = (int *)malloc(choicesSize * sizeof(int));\n for (int i = 0; i &lt; choicesSize; i++) {\n res[*resSize][i] = state[i];\n }\n (*resSize)++;\n return;\n }\n // すべての選択肢を走査する\n bool duplicated[MAX_SIZE] = {false};\n for (int i = 0; i &lt; choicesSize; i++) {\n int choice = choices[i];\n // 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if (!selected[i] &amp;&amp; !duplicated[choice]) {\n // 試行:選択を行い、状態を更新する\n duplicated[choice] = true; // 選択済みの要素の値を記録する\n selected[i] = true;\n state[stateSize] = choice;\n // 次の選択に進む\n backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false;\n }\n }\n}\n\n/* すべての順列 II */\nint **permutationsII(int *nums, int numsSize, int *returnSize) {\n int *state = (int *)malloc(numsSize * sizeof(int));\n bool *selected = (bool *)malloc(numsSize * sizeof(bool));\n for (int i = 0; i &lt; numsSize; i++) {\n selected[i] = false;\n }\n int **res = (int **)malloc(MAX_SIZE * sizeof(int *));\n *returnSize = 0;\n\n backtrack(state, 0, nums, numsSize, selected, res, returnSize);\n\n free(state);\n free(selected);\n\n return res;\n}\n</code></pre> permutations_ii.kt<pre><code>/* バックトラッキング:すべての順列 II */\nfun backtrack(\n state: MutableList&lt;Int&gt;,\n choices: IntArray,\n selected: BooleanArray,\n res: MutableList&lt;MutableList&lt;Int&gt;?&gt;\n) {\n // 状態の長さが要素数と等しくなったら、解を記録する\n if (state.size == choices.size) {\n res.add(state.toMutableList())\n return\n }\n // すべての選択肢を走査する\n val duplicated = HashSet&lt;Int&gt;()\n for (i in choices.indices) {\n val choice = choices[i]\n // 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if (!selected[i] &amp;&amp; !duplicated.contains(choice)) {\n // 試行:選択を行い、状態を更新する\n duplicated.add(choice) // 選択済みの要素の値を記録する\n selected[i] = true\n state.add(choice)\n // 次の選択に進む\n backtrack(state, choices, selected, res)\n // 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false\n state.removeAt(state.size - 1)\n }\n }\n}\n\n/* すべての順列 II */\nfun permutationsII(nums: IntArray): MutableList&lt;MutableList&lt;Int&gt;?&gt; {\n val res = mutableListOf&lt;MutableList&lt;Int&gt;?&gt;()\n backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)\n return res\n}\n</code></pre> permutations_ii.rb<pre><code>### バックトラッキング:すべての順列 II ###\ndef backtrack(state, choices, selected, res)\n # 状態の長さが要素数と等しくなったら、解を記録する\n if state.length == choices.length\n res &lt;&lt; state.dup\n return\n end\n\n # すべての選択肢を走査する\n duplicated = Set.new\n choices.each_with_index do |choice, i|\n # 枝刈り:同じ要素の再選択も、等しい値を持つ要素の重複選択も許可しない\n if !selected[i] &amp;&amp; !duplicated.include?(choice)\n # 試行:選択を行い、状態を更新する\n duplicated.add(choice)\n selected[i] = true\n state &lt;&lt; choice\n # 次の選択に進む\n backtrack(state, choices, selected, res)\n # 復元:選択を取り消し、以前の状態に戻す\n selected[i] = false\n state.pop\n end\n end\nend\n\n### すべての順列 II ###\ndef permutations_ii(nums)\n res = []\n backtrack([], nums, Array.new(nums.length, false), res)\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>要素がすべて異なるとすると、\\(n\\) 個の要素には \\(n!\\) 通り(階乗)の順列があります。結果の記録には長さ \\(n\\) のリストのコピーが必要で、\\(O(n)\\) 時間かかります。したがって、時間計算量は \\(O(n!n)\\) です。</p> <p>最大再帰深度は \\(n\\) で、スタックフレームに \\(O(n)\\) の空間を使います。<code>selected</code> は \\(O(n)\\)、同時に最大 \\(n\\) 個存在する <code>duplicated</code> は \\(O(n^2)\\) の空間を使います。したがって、空間計算量は \\(O(n^2)\\) です。</p>","path":["第 13 章   バックトラッキング","13.2   順列列挙問題"],"tags":[]},{"location":"chapter_backtracking/permutations_problem/#3","level":3,"title":"3.   二つの枝刈りの比較","text":"<p><code>selected</code> と <code>duplicated</code> はどちらも枝刈りに使いますが、目的は異なります。</p> <ul> <li>同じ要素の再選択を防ぐ枝刈り:探索全体で <code>selected</code> は一つだけです。現在の状態に含まれる要素を記録し、<code>state</code> に同じ要素が繰り返し入るのを防ぎます。</li> <li>同じ値の要素による分岐を防ぐ枝刈り:各回の選択(各 <code>backtrack</code> 呼び出し)が、それぞれ <code>duplicated</code> を持ちます。その回の走査(<code>for</code> ループ)ですでに選んだ要素を記録し、同じ値を一度だけ選ぶようにします。</li> </ul> <p>図 13-9 に、二つの枝刈り条件の作用範囲を示します。木の各ノードは一つの選択を表し、根から葉までの経路上のノードが一つの順列を構成していることに注意してください。</p> <p></p> <p> 図 13-9   二つの枝刈り条件の作用範囲 </p>","path":["第 13 章   バックトラッキング","13.2   順列列挙問題"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/","level":1,"title":"13.3   部分集合和問題","text":"","path":["第 13 章   バックトラッキング","13.3   部分集合和問題"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1331","level":2,"title":"13.3.1   重複した要素がない場合","text":"<p>問題</p> <p>正の整数の配列 <code>nums</code> と、目標となる正の整数 <code>target</code> が与えられます。要素の和が <code>target</code> になるすべての組み合わせを求めてください。配列に重複した要素はなく、各要素は何度でも選べます。組み合わせをリストで返し、重複した組み合わせは含めないでください。</p> <p>たとえば、入力集合が \\(\\{3, 4, 5\\}\\)、目標が \\(9\\) なら、解は \\(\\{3, 3, 3\\}, \\{4, 5\\}\\) です。次の 2 点に注意してください。</p> <ul> <li>入力集合の要素は、回数の制限なく繰り返し選べます。</li> <li>部分集合では要素の順序を区別しません。たとえば、\\(\\{4, 5\\}\\) と \\(\\{5, 4\\}\\) は同じ部分集合です。</li> </ul>","path":["第 13 章   バックトラッキング","13.3   部分集合和問題"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1","level":3,"title":"1.   順列列挙の解法を参考にする","text":"<p>順列列挙と同様に、部分集合を作る過程を一連の選択と考えられます。選ぶたびに「要素の和」を更新し、<code>target</code> と等しくなったら、その部分集合を結果リストに記録します。</p> <p>順列列挙と違い、この問題では同じ要素を何度でも選べるため、ブールリスト <code>selected</code> で選択済みかを記録する必要はありません。順列列挙のコードを少し修正すると、まず次の解法が得られます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_i_naive.py<pre><code>def backtrack(\n state: list[int],\n target: int,\n total: int,\n choices: list[int],\n res: list[list[int]],\n):\n \"\"\"バックトラッキング:部分集合和 I\"\"\"\n # 部分集合の和が target と等しくなったら、解を記録する\n if total == target:\n res.append(list(state))\n return\n # すべての選択肢を走査する\n for i in range(len(choices)):\n # 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if total + choices[i] &gt; target:\n continue\n # 試行:選択を行い、要素の和 total を更新する\n state.append(choices[i])\n # 次の選択に進む\n backtrack(state, target, total + choices[i], choices, res)\n # 復元:選択を取り消し、以前の状態に戻す\n state.pop()\n\ndef subset_sum_i_naive(nums: list[int], target: int) -&gt; list[list[int]]:\n \"\"\"部分集合和 I を解く(重複した部分集合を含む)\"\"\"\n state = [] # 状態(部分集合)\n total = 0 # 部分集合和\n res = [] # 結果リスト(部分集合のリスト)\n backtrack(state, target, total, nums, res)\n return res\n</code></pre> subset_sum_i_naive.cpp<pre><code>/* バックトラッキング:部分集合和 I */\nvoid backtrack(vector&lt;int&gt; &amp;state, int target, int total, vector&lt;int&gt; &amp;choices, vector&lt;vector&lt;int&gt;&gt; &amp;res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (total == target) {\n res.push_back(state);\n return;\n }\n // すべての選択肢を走査する\n for (size_t i = 0; i &lt; choices.size(); i++) {\n // 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if (total + choices[i] &gt; target) {\n continue;\n }\n // 試行:選択を行い、要素の和 total を更新する\n state.push_back(choices[i]);\n // 次の選択に進む\n backtrack(state, target, total + choices[i], choices, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop_back();\n }\n}\n\n/* 部分集合和 I を解く(重複した部分集合を含む) */\nvector&lt;vector&lt;int&gt;&gt; subsetSumINaive(vector&lt;int&gt; &amp;nums, int target) {\n vector&lt;int&gt; state; // 状態(部分集合)\n int total = 0; // 部分集合和\n vector&lt;vector&lt;int&gt;&gt; res; // 結果リスト(部分集合のリスト)\n backtrack(state, target, total, nums, res);\n return res;\n}\n</code></pre> subset_sum_i_naive.java<pre><code>/* バックトラッキング:部分集合和 I */\nvoid backtrack(List&lt;Integer&gt; state, int target, int total, int[] choices, List&lt;List&lt;Integer&gt;&gt; res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (total == target) {\n res.add(new ArrayList&lt;&gt;(state));\n return;\n }\n // すべての選択肢を走査する\n for (int i = 0; i &lt; choices.length; i++) {\n // 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if (total + choices[i] &gt; target) {\n continue;\n }\n // 試行:選択を行い、要素の和 total を更新する\n state.add(choices[i]);\n // 次の選択に進む\n backtrack(state, target, total + choices[i], choices, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.remove(state.size() - 1);\n }\n}\n\n/* 部分集合和 I を解く(重複した部分集合を含む) */\nList&lt;List&lt;Integer&gt;&gt; subsetSumINaive(int[] nums, int target) {\n List&lt;Integer&gt; state = new ArrayList&lt;&gt;(); // 状態(部分集合)\n int total = 0; // 部分集合和\n List&lt;List&lt;Integer&gt;&gt; res = new ArrayList&lt;&gt;(); // 結果リスト(部分集合のリスト)\n backtrack(state, target, total, nums, res);\n return res;\n}\n</code></pre> subset_sum_i_naive.cs<pre><code>/* バックトラッキング:部分集合和 I */\nvoid Backtrack(List&lt;int&gt; state, int target, int total, int[] choices, List&lt;List&lt;int&gt;&gt; res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (total == target) {\n res.Add(new List&lt;int&gt;(state));\n return;\n }\n // すべての選択肢を走査する\n for (int i = 0; i &lt; choices.Length; i++) {\n // 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if (total + choices[i] &gt; target) {\n continue;\n }\n // 試行:選択を行い、要素の和 total を更新する\n state.Add(choices[i]);\n // 次の選択に進む\n Backtrack(state, target, total + choices[i], choices, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.RemoveAt(state.Count - 1);\n }\n}\n\n/* 部分集合和 I を解く(重複した部分集合を含む) */\nList&lt;List&lt;int&gt;&gt; SubsetSumINaive(int[] nums, int target) {\n List&lt;int&gt; state = []; // 状態(部分集合)\n int total = 0; // 部分集合和\n List&lt;List&lt;int&gt;&gt; res = []; // 結果リスト(部分集合のリスト)\n Backtrack(state, target, total, nums, res);\n return res;\n}\n</code></pre> subset_sum_i_naive.go<pre><code>/* バックトラッキング:部分集合和 I */\nfunc backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if target == total {\n newState := append([]int{}, *state...)\n *res = append(*res, newState)\n return\n }\n // すべての選択肢を走査する\n for i := 0; i &lt; len(*choices); i++ {\n // 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if total+(*choices)[i] &gt; target {\n continue\n }\n // 試行:選択を行い、要素の和 total を更新する\n *state = append(*state, (*choices)[i])\n // 次の選択に進む\n backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res)\n // 復元:選択を取り消し、以前の状態に戻す\n *state = (*state)[:len(*state)-1]\n }\n}\n\n/* 部分集合和 I を解く(重複した部分集合を含む) */\nfunc subsetSumINaive(nums []int, target int) [][]int {\n state := make([]int, 0) // 状態(部分集合)\n total := 0 // 部分集合和\n res := make([][]int, 0) // 結果リスト(部分集合のリスト)\n backtrackSubsetSumINaive(total, target, &amp;state, &amp;nums, &amp;res)\n return res\n}\n</code></pre> subset_sum_i_naive.swift<pre><code>/* バックトラッキング:部分集合和 I */\nfunc backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if total == target {\n res.append(state)\n return\n }\n // すべての選択肢を走査する\n for i in choices.indices {\n // 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if total + choices[i] &gt; target {\n continue\n }\n // 試行:選択を行い、要素の和 total を更新する\n state.append(choices[i])\n // 次の選択に進む\n backtrack(state: &amp;state, target: target, total: total + choices[i], choices: choices, res: &amp;res)\n // 復元:選択を取り消し、以前の状態に戻す\n state.removeLast()\n }\n}\n\n/* 部分集合和 I を解く(重複した部分集合を含む) */\nfunc subsetSumINaive(nums: [Int], target: Int) -&gt; [[Int]] {\n var state: [Int] = [] // 状態(部分集合)\n let total = 0 // 部分集合和\n var res: [[Int]] = [] // 結果リスト(部分集合のリスト)\n backtrack(state: &amp;state, target: target, total: total, choices: nums, res: &amp;res)\n return res\n}\n</code></pre> subset_sum_i_naive.js<pre><code>/* バックトラッキング:部分集合和 I */\nfunction backtrack(state, target, total, choices, res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (total === target) {\n res.push([...state]);\n return;\n }\n // すべての選択肢を走査する\n for (let i = 0; i &lt; choices.length; i++) {\n // 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if (total + choices[i] &gt; target) {\n continue;\n }\n // 試行:選択を行い、要素の和 total を更新する\n state.push(choices[i]);\n // 次の選択に進む\n backtrack(state, target, total + choices[i], choices, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop();\n }\n}\n\n/* 部分集合和 I を解く(重複した部分集合を含む) */\nfunction subsetSumINaive(nums, target) {\n const state = []; // 状態(部分集合)\n const total = 0; // 部分集合和\n const res = []; // 結果リスト(部分集合のリスト)\n backtrack(state, target, total, nums, res);\n return res;\n}\n</code></pre> subset_sum_i_naive.ts<pre><code>/* バックトラッキング:部分集合和 I */\nfunction backtrack(\n state: number[],\n target: number,\n total: number,\n choices: number[],\n res: number[][]\n): void {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (total === target) {\n res.push([...state]);\n return;\n }\n // すべての選択肢を走査する\n for (let i = 0; i &lt; choices.length; i++) {\n // 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if (total + choices[i] &gt; target) {\n continue;\n }\n // 試行:選択を行い、要素の和 total を更新する\n state.push(choices[i]);\n // 次の選択に進む\n backtrack(state, target, total + choices[i], choices, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop();\n }\n}\n\n/* 部分集合和 I を解く(重複した部分集合を含む) */\nfunction subsetSumINaive(nums: number[], target: number): number[][] {\n const state = []; // 状態(部分集合)\n const total = 0; // 部分集合和\n const res = []; // 結果リスト(部分集合のリスト)\n backtrack(state, target, total, nums, res);\n return res;\n}\n</code></pre> subset_sum_i_naive.dart<pre><code>/* バックトラッキング:部分集合和 I */\nvoid backtrack(\n List&lt;int&gt; state,\n int target,\n int total,\n List&lt;int&gt; choices,\n List&lt;List&lt;int&gt;&gt; res,\n) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (total == target) {\n res.add(List.from(state));\n return;\n }\n // すべての選択肢を走査する\n for (int i = 0; i &lt; choices.length; i++) {\n // 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if (total + choices[i] &gt; target) {\n continue;\n }\n // 試行:選択を行い、要素の和 total を更新する\n state.add(choices[i]);\n // 次の選択に進む\n backtrack(state, target, total + choices[i], choices, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.removeLast();\n }\n}\n\n/* 部分集合和 I を解く(重複した部分集合を含む) */\nList&lt;List&lt;int&gt;&gt; subsetSumINaive(List&lt;int&gt; nums, int target) {\n List&lt;int&gt; state = []; // 状態(部分集合)\n int total = 0; // 要素の和\n List&lt;List&lt;int&gt;&gt; res = []; // 結果リスト(部分集合のリスト)\n backtrack(state, target, total, nums, res);\n return res;\n}\n</code></pre> subset_sum_i_naive.rs<pre><code>/* バックトラッキング:部分集合和 I */\nfn backtrack(\n state: &amp;mut Vec&lt;i32&gt;,\n target: i32,\n total: i32,\n choices: &amp;[i32],\n res: &amp;mut Vec&lt;Vec&lt;i32&gt;&gt;,\n) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if total == target {\n res.push(state.clone());\n return;\n }\n // すべての選択肢を走査する\n for i in 0..choices.len() {\n // 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if total + choices[i] &gt; target {\n continue;\n }\n // 試行:選択を行い、要素の和 total を更新する\n state.push(choices[i]);\n // 次の選択に進む\n backtrack(state, target, total + choices[i], choices, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop();\n }\n}\n\n/* 部分集合和 I を解く(重複した部分集合を含む) */\nfn subset_sum_i_naive(nums: &amp;[i32], target: i32) -&gt; Vec&lt;Vec&lt;i32&gt;&gt; {\n let mut state = Vec::new(); // 状態(部分集合)\n let total = 0; // 部分集合和\n let mut res = Vec::new(); // 結果リスト(部分集合のリスト)\n backtrack(&amp;mut state, target, total, nums, &amp;mut res);\n res\n}\n</code></pre> subset_sum_i_naive.c<pre><code>/* バックトラッキング:部分集合和 I */\nvoid backtrack(int target, int total, int *choices, int choicesSize) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (total == target) {\n for (int i = 0; i &lt; stateSize; i++) {\n res[resSize][i] = state[i];\n }\n resColSizes[resSize++] = stateSize;\n return;\n }\n // すべての選択肢を走査する\n for (int i = 0; i &lt; choicesSize; i++) {\n // 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if (total + choices[i] &gt; target) {\n continue;\n }\n // 試行:選択を行い、要素の和 total を更新する\n state[stateSize++] = choices[i];\n // 次の選択に進む\n backtrack(target, total + choices[i], choices, choicesSize);\n // 復元:選択を取り消し、以前の状態に戻す\n stateSize--;\n }\n}\n\n/* 部分集合和 I を解く(重複した部分集合を含む) */\nvoid subsetSumINaive(int *nums, int numsSize, int target) {\n resSize = 0; // 解の個数を0に初期化する\n backtrack(target, 0, nums, numsSize);\n}\n</code></pre> subset_sum_i_naive.kt<pre><code>/* バックトラッキング:部分集合和 I */\nfun backtrack(\n state: MutableList&lt;Int&gt;,\n target: Int,\n total: Int,\n choices: IntArray,\n res: MutableList&lt;MutableList&lt;Int&gt;?&gt;\n) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (total == target) {\n res.add(state.toMutableList())\n return\n }\n // すべての選択肢を走査する\n for (i in choices.indices) {\n // 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n if (total + choices[i] &gt; target) {\n continue\n }\n // 試行:選択を行い、要素の和 total を更新する\n state.add(choices[i])\n // 次の選択に進む\n backtrack(state, target, total + choices[i], choices, res)\n // 復元:選択を取り消し、以前の状態に戻す\n state.removeAt(state.size - 1)\n }\n}\n\n/* 部分集合和 I を解く(重複した部分集合を含む) */\nfun subsetSumINaive(nums: IntArray, target: Int): MutableList&lt;MutableList&lt;Int&gt;?&gt; {\n val state = mutableListOf&lt;Int&gt;() // 状態(部分集合)\n val total = 0 // 部分集合和\n val res = mutableListOf&lt;MutableList&lt;Int&gt;?&gt;() // 結果リスト(部分集合のリスト)\n backtrack(state, target, total, nums, res)\n return res\n}\n</code></pre> subset_sum_i_naive.rb<pre><code>### バックトラッキング:部分集合和 I ###\ndef backtrack(state, target, total, choices, res)\n # 部分集合の和が target と等しくなったら、解を記録する\n if total == target\n res &lt;&lt; state.dup\n return\n end\n\n # すべての選択肢を走査する\n for i in 0...choices.length\n # 枝刈り:部分集合の和が target を超える場合、この選択をスキップする\n next if total + choices[i] &gt; target\n # 試行:選択を行い、要素の和 total を更新する\n state &lt;&lt; choices[i]\n # 次の選択に進む\n backtrack(state, target, total + choices[i], choices, res)\n # 復元:選択を取り消し、以前の状態に戻す\n state.pop\n end\nend\n\n### 部分集合和 I を解く(重複した部分集合を含む)###\ndef subset_sum_i_naive(nums, target)\n state = [] # 状態(部分集合)\n total = 0 # 部分集合和\n res = [] # 結果リスト(部分集合のリスト)\n backtrack(state, target, total, nums, res)\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>このコードに配列 \\([3, 4, 5]\\) と目標 \\(9\\) を渡すと、結果は \\([3, 3, 3], [4, 5], [5, 4]\\) になります。和が \\(9\\) の部分集合はすべて見つかりましたが、\\([4, 5]\\) と \\([5, 4]\\) が重複しています。</p> <p>探索では選択順を区別する一方、部分集合では区別しないためです。図 13-10 のように、\\(4\\) の後に \\(5\\) を選ぶ場合と、\\(5\\) の後に \\(4\\) を選ぶ場合は、探索上は別の分岐でも、同じ部分集合になります。</p> <p></p> <p> 図 13-10   部分集合の探索と範囲超過による枝刈り </p> <p>重複した部分集合を除くため、結果リストから重複を取り除く方法が考えられます。しかし、次の二つの理由で非効率です。</p> <ul> <li>配列の要素が多いとき、特に <code>target</code> が大きいときは、探索中に大量の重複した部分集合が生成されます。</li> <li>部分集合(配列)が同じかを比べるには、まずソートし、その後、各要素を比較する必要があり、時間がかかります。</li> </ul>","path":["第 13 章   バックトラッキング","13.3   部分集合和問題"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#2","level":3,"title":"2.   重複する部分集合の枝刈り","text":"<p>探索中の枝刈りで重複を除くことを考えます。図 13-11 のように、重複は配列の要素を異なる順で選ぶことで生じます。たとえば、次のような場合です。</p> <ol> <li>1 回目と 2 回目に \\(3\\) と \\(4\\) を選ぶと、この二つを含むすべての部分集合 \\([3, 4, \\dots]\\) が生成されます。</li> <li>その後、1 回目に \\(4\\) を選ぶ場合は、2 回目の \\(3\\) を飛ばす必要があります。ここでできる \\([4, 3, \\dots]\\) は、手順 <code>1.</code> で生成した部分集合と完全に重複するためです。</li> </ol> <p>探索では各段階の選択肢を左から右へ順に試すため、右側の分岐ほど多く枝刈りされます。</p> <ol> <li>最初の 2 回で \\(3\\) と \\(5\\) を選び、部分集合 \\([3, 5, \\dots]\\) を生成します。</li> <li>最初の 2 回で \\(4\\) と \\(5\\) を選び、部分集合 \\([4, 5, \\dots]\\) を生成します。</li> <li>1 回目に \\(5\\) を選ぶ場合は、2 回目の \\(3\\) と \\(4\\) を飛ばす必要があります。\\([5, 3, \\dots]\\) と \\([5, 4, \\dots]\\) は、手順 <code>1.</code> と <code>2.</code> の部分集合と完全に重複するためです。</li> </ol> <p></p> <p> 図 13-11   選択順の違いによって重複する部分集合 </p> <p>まとめると、入力配列を \\([x_1, x_2, \\dots, x_n]\\)、選択の列を \\([x_{i_1}, x_{i_2}, \\dots, x_{i_m}]\\) としたとき、\\(i_1 \\leq i_2 \\leq \\dots \\leq i_m\\) を満たす必要があります。この条件を満たさない選択の列は重複を生むため、枝刈りすべきです。</p>","path":["第 13 章   バックトラッキング","13.3   部分集合和問題"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#3","level":3,"title":"3.   コードの実装","text":"<p>この枝刈りのため、走査の開始位置を表す変数 <code>start</code> を用意します。\\(x_{i}\\) を選んだら、次の回はインデックス \\(i\\) から走査を始めます。これで選択の列が \\(i_1 \\leq i_2 \\leq \\dots \\leq i_m\\) を満たし、部分集合の重複を防げます。</p> <p>さらに、コードに次の二つの改善を加えます。</p> <ul> <li>探索前に配列 <code>nums</code> をソートします。選択肢を走査するとき、部分集合の和が <code>target</code> を超えたら、ループをその場で終了します。後ろの要素はさらに大きいため、それらを選んでも和が必ず <code>target</code> を超えるからです。</li> <li>和を表す変数 <code>total</code> をなくし、<code>target</code> から減算することで和を管理します。<code>target</code> が \\(0\\) になったら解を記録します。</li> </ul> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_i.py<pre><code>def backtrack(\n state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]\n):\n \"\"\"バックトラッキング:部分集合和 I\"\"\"\n # 部分集合の和が target と等しくなったら、解を記録する\n if target == 0:\n res.append(list(state))\n return\n # すべての選択肢を走査する\n # 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for i in range(start, len(choices)):\n # 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n # 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if target - choices[i] &lt; 0:\n break\n # 試行:選択を行い、target, start を更新する\n state.append(choices[i])\n # 次の選択に進む\n backtrack(state, target - choices[i], choices, i, res)\n # 復元:選択を取り消し、以前の状態に戻す\n state.pop()\n\ndef subset_sum_i(nums: list[int], target: int) -&gt; list[list[int]]:\n \"\"\"部分集合和 I を解く\"\"\"\n state = [] # 状態(部分集合)\n nums.sort() # nums をソートする\n start = 0 # 走査の開始位置\n res = [] # 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res)\n return res\n</code></pre> subset_sum_i.cpp<pre><code>/* バックトラッキング:部分集合和 I */\nvoid backtrack(vector&lt;int&gt; &amp;state, int target, vector&lt;int&gt; &amp;choices, int start, vector&lt;vector&lt;int&gt;&gt; &amp;res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n res.push_back(state);\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for (int i = start; i &lt; choices.size(); i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 試行:選択を行い、target, start を更新する\n state.push_back(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop_back();\n }\n}\n\n/* 部分集合和 I を解く */\nvector&lt;vector&lt;int&gt;&gt; subsetSumI(vector&lt;int&gt; &amp;nums, int target) {\n vector&lt;int&gt; state; // 状態(部分集合)\n sort(nums.begin(), nums.end()); // nums をソートする\n int start = 0; // 走査の開始位置\n vector&lt;vector&lt;int&gt;&gt; res; // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_i.java<pre><code>/* バックトラッキング:部分集合和 I */\nvoid backtrack(List&lt;Integer&gt; state, int target, int[] choices, int start, List&lt;List&lt;Integer&gt;&gt; res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n res.add(new ArrayList&lt;&gt;(state));\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for (int i = start; i &lt; choices.length; i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 試行:選択を行い、target, start を更新する\n state.add(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.remove(state.size() - 1);\n }\n}\n\n/* 部分集合和 I を解く */\nList&lt;List&lt;Integer&gt;&gt; subsetSumI(int[] nums, int target) {\n List&lt;Integer&gt; state = new ArrayList&lt;&gt;(); // 状態(部分集合)\n Arrays.sort(nums); // nums をソートする\n int start = 0; // 走査の開始位置\n List&lt;List&lt;Integer&gt;&gt; res = new ArrayList&lt;&gt;(); // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_i.cs<pre><code>/* バックトラッキング:部分集合和 I */\nvoid Backtrack(List&lt;int&gt; state, int target, int[] choices, int start, List&lt;List&lt;int&gt;&gt; res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n res.Add(new List&lt;int&gt;(state));\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for (int i = start; i &lt; choices.Length; i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 試行:選択を行い、target, start を更新する\n state.Add(choices[i]);\n // 次の選択に進む\n Backtrack(state, target - choices[i], choices, i, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.RemoveAt(state.Count - 1);\n }\n}\n\n/* 部分集合和 I を解く */\nList&lt;List&lt;int&gt;&gt; SubsetSumI(int[] nums, int target) {\n List&lt;int&gt; state = []; // 状態(部分集合)\n Array.Sort(nums); // nums をソートする\n int start = 0; // 走査の開始位置\n List&lt;List&lt;int&gt;&gt; res = []; // 結果リスト(部分集合のリスト)\n Backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_i.go<pre><code>/* バックトラッキング:部分集合和 I */\nfunc backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if target == 0 {\n newState := append([]int{}, *state...)\n *res = append(*res, newState)\n return\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for i := start; i &lt; len(*choices); i++ {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if target-(*choices)[i] &lt; 0 {\n break\n }\n // 試行:選択を行い、target, start を更新する\n *state = append(*state, (*choices)[i])\n // 次の選択に進む\n backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res)\n // 復元:選択を取り消し、以前の状態に戻す\n *state = (*state)[:len(*state)-1]\n }\n}\n\n/* 部分集合和 I を解く */\nfunc subsetSumI(nums []int, target int) [][]int {\n state := make([]int, 0) // 状態(部分集合)\n sort.Ints(nums) // nums をソートする\n start := 0 // 走査の開始位置\n res := make([][]int, 0) // 結果リスト(部分集合のリスト)\n backtrackSubsetSumI(start, target, &amp;state, &amp;nums, &amp;res)\n return res\n}\n</code></pre> subset_sum_i.swift<pre><code>/* バックトラッキング:部分集合和 I */\nfunc backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if target == 0 {\n res.append(state)\n return\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for i in choices.indices.dropFirst(start) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if target - choices[i] &lt; 0 {\n break\n }\n // 試行:選択を行い、target, start を更新する\n state.append(choices[i])\n // 次の選択に進む\n backtrack(state: &amp;state, target: target - choices[i], choices: choices, start: i, res: &amp;res)\n // 復元:選択を取り消し、以前の状態に戻す\n state.removeLast()\n }\n}\n\n/* 部分集合和 I を解く */\nfunc subsetSumI(nums: [Int], target: Int) -&gt; [[Int]] {\n var state: [Int] = [] // 状態(部分集合)\n let nums = nums.sorted() // nums をソートする\n let start = 0 // 走査の開始位置\n var res: [[Int]] = [] // 結果リスト(部分集合のリスト)\n backtrack(state: &amp;state, target: target, choices: nums, start: start, res: &amp;res)\n return res\n}\n</code></pre> subset_sum_i.js<pre><code>/* バックトラッキング:部分集合和 I */\nfunction backtrack(state, target, choices, start, res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target === 0) {\n res.push([...state]);\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for (let i = start; i &lt; choices.length; i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 試行:選択を行い、target, start を更新する\n state.push(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop();\n }\n}\n\n/* 部分集合和 I を解く */\nfunction subsetSumI(nums, target) {\n const state = []; // 状態(部分集合)\n nums.sort((a, b) =&gt; a - b); // nums をソートする\n const start = 0; // 走査の開始位置\n const res = []; // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_i.ts<pre><code>/* バックトラッキング:部分集合和 I */\nfunction backtrack(\n state: number[],\n target: number,\n choices: number[],\n start: number,\n res: number[][]\n): void {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target === 0) {\n res.push([...state]);\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for (let i = start; i &lt; choices.length; i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 試行:選択を行い、target, start を更新する\n state.push(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop();\n }\n}\n\n/* 部分集合和 I を解く */\nfunction subsetSumI(nums: number[], target: number): number[][] {\n const state = []; // 状態(部分集合)\n nums.sort((a, b) =&gt; a - b); // nums をソートする\n const start = 0; // 走査の開始位置\n const res = []; // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_i.dart<pre><code>/* バックトラッキング:部分集合和 I */\nvoid backtrack(\n List&lt;int&gt; state,\n int target,\n List&lt;int&gt; choices,\n int start,\n List&lt;List&lt;int&gt;&gt; res,\n) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n res.add(List.from(state));\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for (int i = start; i &lt; choices.length; i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 試行:選択を行い、target, start を更新する\n state.add(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.removeLast();\n }\n}\n\n/* 部分集合和 I を解く */\nList&lt;List&lt;int&gt;&gt; subsetSumI(List&lt;int&gt; nums, int target) {\n List&lt;int&gt; state = []; // 状態(部分集合)\n nums.sort(); // nums をソートする\n int start = 0; // 走査の開始位置\n List&lt;List&lt;int&gt;&gt; res = []; // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_i.rs<pre><code>/* バックトラッキング:部分集合和 I */\nfn backtrack(\n state: &amp;mut Vec&lt;i32&gt;,\n target: i32,\n choices: &amp;[i32],\n start: usize,\n res: &amp;mut Vec&lt;Vec&lt;i32&gt;&gt;,\n) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if target == 0 {\n res.push(state.clone());\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for i in start..choices.len() {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if target - choices[i] &lt; 0 {\n break;\n }\n // 試行:選択を行い、target, start を更新する\n state.push(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop();\n }\n}\n\n/* 部分集合和 I を解く */\nfn subset_sum_i(nums: &amp;mut [i32], target: i32) -&gt; Vec&lt;Vec&lt;i32&gt;&gt; {\n let mut state = Vec::new(); // 状態(部分集合)\n nums.sort(); // nums をソートする\n let start = 0; // 走査の開始位置\n let mut res = Vec::new(); // 結果リスト(部分集合のリスト)\n backtrack(&amp;mut state, target, nums, start, &amp;mut res);\n res\n}\n</code></pre> subset_sum_i.c<pre><code>/* バックトラッキング:部分集合和 I */\nvoid backtrack(int target, int *choices, int choicesSize, int start) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n for (int i = 0; i &lt; stateSize; ++i) {\n res[resSize][i] = state[i];\n }\n resColSizes[resSize++] = stateSize;\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for (int i = start; i &lt; choicesSize; i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 試行:選択を行い、target, start を更新する\n state[stateSize] = choices[i];\n stateSize++;\n // 次の選択に進む\n backtrack(target - choices[i], choices, choicesSize, i);\n // 復元:選択を取り消し、以前の状態に戻す\n stateSize--;\n }\n}\n\n/* 部分集合和 I を解く */\nvoid subsetSumI(int *nums, int numsSize, int target) {\n qsort(nums, numsSize, sizeof(int), cmp); // nums をソートする\n int start = 0; // 走査の開始位置\n backtrack(target, nums, numsSize, start);\n}\n</code></pre> subset_sum_i.kt<pre><code>/* バックトラッキング:部分集合和 I */\nfun backtrack(\n state: MutableList&lt;Int&gt;,\n target: Int,\n choices: IntArray,\n start: Int,\n res: MutableList&lt;MutableList&lt;Int&gt;?&gt;\n) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n res.add(state.toMutableList())\n return\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for (i in start..&lt;choices.size) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break\n }\n // 試行:選択を行い、target, start を更新する\n state.add(choices[i])\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i, res)\n // 復元:選択を取り消し、以前の状態に戻す\n state.removeAt(state.size - 1)\n }\n}\n\n/* 部分集合和 I を解く */\nfun subsetSumI(nums: IntArray, target: Int): MutableList&lt;MutableList&lt;Int&gt;?&gt; {\n val state = mutableListOf&lt;Int&gt;() // 状態(部分集合)\n nums.sort() // nums をソートする\n val start = 0 // 走査の開始位置\n val res = mutableListOf&lt;MutableList&lt;Int&gt;?&gt;() // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res)\n return res\n}\n</code></pre> subset_sum_i.rb<pre><code>### バックトラッキング:部分集合和 I ###\ndef backtrack(state, target, choices, start, res)\n # 部分集合の和が target と等しくなったら、解を記録する\n if target.zero?\n res &lt;&lt; state.dup\n return\n end\n # すべての選択肢を走査する\n # 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n for i in start...choices.length\n # 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n # 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n break if target - choices[i] &lt; 0\n # 試行:選択を行い、target, start を更新する\n state &lt;&lt; choices[i]\n # 次の選択に進む\n backtrack(state, target - choices[i], choices, i, res)\n # 復元:選択を取り消し、以前の状態に戻す\n state.pop\n end\nend\n\n### 部分集合和 I を解く ###\ndef subset_sum_i(nums, target)\n state = [] # 状態(部分集合)\n nums.sort! # nums をソートする\n start = 0 # 走査の開始位置\n res = [] # 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res)\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 13-12 に、配列 \\([3, 4, 5]\\) と目標 \\(9\\) を入力したときの、バックトラッキング全体の流れを示します。</p> <p></p> <p> 図 13-12   部分集合和 I のバックトラッキング </p>","path":["第 13 章   バックトラッキング","13.3   部分集合和問題"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1332","level":2,"title":"13.3.2   重複した要素がある場合","text":"<p>問題</p> <p>正の整数の配列 <code>nums</code> と、目標となる正の整数 <code>target</code> が与えられます。要素の和が <code>target</code> になるすべての組み合わせを求めてください。配列には重複した要素が含まれる場合があり、各要素は一度だけ選べます。組み合わせをリストで返し、重複した組み合わせは含めないでください。</p> <p>前の問題と異なり、入力配列に重複した要素が含まれる場合があり、新たな問題が生じます。たとえば配列 \\([4, \\hat{4}, 5]\\) と目標 \\(9\\) に対し、現在のコードは \\([4, 5], [\\hat{4}, 5]\\) を返し、部分集合が重複します。</p> <p>この重複は、ある回の選択で同じ値の要素を何度も選ぶことが原因です。図 13-13 の 1 回目には三つの選択肢があり、そのうち二つが \\(4\\) なので、重複した探索分岐から同じ部分集合が生成されます。同様に、2 回目の二つの \\(4\\) も重複を生みます。</p> <p></p> <p> 図 13-13   同じ値の要素によって重複する部分集合 </p>","path":["第 13 章   バックトラッキング","13.3   部分集合和問題"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#1_1","level":3,"title":"1.   同じ値の要素による分岐の枝刈り","text":"<p>これを防ぐには、同じ値の要素を、各回の選択で一度だけ選ぶようにします。配列はソート済みなので、同じ値は隣り合っています。そこで、ある回の選択で現在の要素が左隣と同じなら、その値はすでに選んだとわかるため、現在の要素を飛ばします。</p> <p>また、この問題では配列の各要素を一度しか選べません。この制約も <code>start</code> で満たせます。\\(x_{i}\\) を選んだら、次の回はインデックス \\(i + 1\\) から後ろを走査します。これで部分集合の重複を除くと同時に、同じ要素の再選択も防げます。</p>","path":["第 13 章   バックトラッキング","13.3   部分集合和問題"],"tags":[]},{"location":"chapter_backtracking/subset_sum_problem/#2_1","level":3,"title":"2.   コードの実装","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby subset_sum_ii.py<pre><code>def backtrack(\n state: list[int], target: int, choices: list[int], start: int, res: list[list[int]]\n):\n \"\"\"バックトラッキング:部分集合和 II\"\"\"\n # 部分集合の和が target と等しくなったら、解を記録する\n if target == 0:\n res.append(list(state))\n return\n # すべての選択肢を走査する\n # 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n # 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for i in range(start, len(choices)):\n # 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n # 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if target - choices[i] &lt; 0:\n break\n # 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if i &gt; start and choices[i] == choices[i - 1]:\n continue\n # 試行:選択を行い、target, start を更新する\n state.append(choices[i])\n # 次の選択に進む\n backtrack(state, target - choices[i], choices, i + 1, res)\n # 復元:選択を取り消し、以前の状態に戻す\n state.pop()\n\ndef subset_sum_ii(nums: list[int], target: int) -&gt; list[list[int]]:\n \"\"\"部分集合和 II を解く\"\"\"\n state = [] # 状態(部分集合)\n nums.sort() # nums をソートする\n start = 0 # 走査の開始位置\n res = [] # 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res)\n return res\n</code></pre> subset_sum_ii.cpp<pre><code>/* バックトラッキング:部分集合和 II */\nvoid backtrack(vector&lt;int&gt; &amp;state, int target, vector&lt;int&gt; &amp;choices, int start, vector&lt;vector&lt;int&gt;&gt; &amp;res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n res.push_back(state);\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n // 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for (int i = start; i &lt; choices.size(); i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if (i &gt; start &amp;&amp; choices[i] == choices[i - 1]) {\n continue;\n }\n // 試行:選択を行い、target, start を更新する\n state.push_back(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i + 1, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop_back();\n }\n}\n\n/* 部分集合和 II を解く */\nvector&lt;vector&lt;int&gt;&gt; subsetSumII(vector&lt;int&gt; &amp;nums, int target) {\n vector&lt;int&gt; state; // 状態(部分集合)\n sort(nums.begin(), nums.end()); // nums をソートする\n int start = 0; // 走査の開始位置\n vector&lt;vector&lt;int&gt;&gt; res; // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_ii.java<pre><code>/* バックトラッキング:部分集合和 II */\nvoid backtrack(List&lt;Integer&gt; state, int target, int[] choices, int start, List&lt;List&lt;Integer&gt;&gt; res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n res.add(new ArrayList&lt;&gt;(state));\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n // 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for (int i = start; i &lt; choices.length; i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if (i &gt; start &amp;&amp; choices[i] == choices[i - 1]) {\n continue;\n }\n // 試行:選択を行い、target, start を更新する\n state.add(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i + 1, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.remove(state.size() - 1);\n }\n}\n\n/* 部分集合和 II を解く */\nList&lt;List&lt;Integer&gt;&gt; subsetSumII(int[] nums, int target) {\n List&lt;Integer&gt; state = new ArrayList&lt;&gt;(); // 状態(部分集合)\n Arrays.sort(nums); // nums をソートする\n int start = 0; // 走査の開始位置\n List&lt;List&lt;Integer&gt;&gt; res = new ArrayList&lt;&gt;(); // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_ii.cs<pre><code>/* バックトラッキング:部分集合和 II */\nvoid Backtrack(List&lt;int&gt; state, int target, int[] choices, int start, List&lt;List&lt;int&gt;&gt; res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n res.Add(new List&lt;int&gt;(state));\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n // 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for (int i = start; i &lt; choices.Length; i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if (i &gt; start &amp;&amp; choices[i] == choices[i - 1]) {\n continue;\n }\n // 試行:選択を行い、target, start を更新する\n state.Add(choices[i]);\n // 次の選択に進む\n Backtrack(state, target - choices[i], choices, i + 1, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.RemoveAt(state.Count - 1);\n }\n}\n\n/* 部分集合和 II を解く */\nList&lt;List&lt;int&gt;&gt; SubsetSumII(int[] nums, int target) {\n List&lt;int&gt; state = []; // 状態(部分集合)\n Array.Sort(nums); // nums をソートする\n int start = 0; // 走査の開始位置\n List&lt;List&lt;int&gt;&gt; res = []; // 結果リスト(部分集合のリスト)\n Backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_ii.go<pre><code>/* バックトラッキング:部分集合和 II */\nfunc backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if target == 0 {\n newState := append([]int{}, *state...)\n *res = append(*res, newState)\n return\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n // 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for i := start; i &lt; len(*choices); i++ {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if target-(*choices)[i] &lt; 0 {\n break\n }\n // 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if i &gt; start &amp;&amp; (*choices)[i] == (*choices)[i-1] {\n continue\n }\n // 試行:選択を行い、target, start を更新する\n *state = append(*state, (*choices)[i])\n // 次の選択に進む\n backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res)\n // 復元:選択を取り消し、以前の状態に戻す\n *state = (*state)[:len(*state)-1]\n }\n}\n\n/* 部分集合和 II を解く */\nfunc subsetSumII(nums []int, target int) [][]int {\n state := make([]int, 0) // 状態(部分集合)\n sort.Ints(nums) // nums をソートする\n start := 0 // 走査の開始位置\n res := make([][]int, 0) // 結果リスト(部分集合のリスト)\n backtrackSubsetSumII(start, target, &amp;state, &amp;nums, &amp;res)\n return res\n}\n</code></pre> subset_sum_ii.swift<pre><code>/* バックトラッキング:部分集合和 II */\nfunc backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if target == 0 {\n res.append(state)\n return\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n // 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for i in choices.indices.dropFirst(start) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if target - choices[i] &lt; 0 {\n break\n }\n // 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if i &gt; start, choices[i] == choices[i - 1] {\n continue\n }\n // 試行:選択を行い、target, start を更新する\n state.append(choices[i])\n // 次の選択に進む\n backtrack(state: &amp;state, target: target - choices[i], choices: choices, start: i + 1, res: &amp;res)\n // 復元:選択を取り消し、以前の状態に戻す\n state.removeLast()\n }\n}\n\n/* 部分集合和 II を解く */\nfunc subsetSumII(nums: [Int], target: Int) -&gt; [[Int]] {\n var state: [Int] = [] // 状態(部分集合)\n let nums = nums.sorted() // nums をソートする\n let start = 0 // 走査の開始位置\n var res: [[Int]] = [] // 結果リスト(部分集合のリスト)\n backtrack(state: &amp;state, target: target, choices: nums, start: start, res: &amp;res)\n return res\n}\n</code></pre> subset_sum_ii.js<pre><code>/* バックトラッキング:部分集合和 II */\nfunction backtrack(state, target, choices, start, res) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target === 0) {\n res.push([...state]);\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n // 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for (let i = start; i &lt; choices.length; i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if (i &gt; start &amp;&amp; choices[i] === choices[i - 1]) {\n continue;\n }\n // 試行:選択を行い、target, start を更新する\n state.push(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i + 1, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop();\n }\n}\n\n/* 部分集合和 II を解く */\nfunction subsetSumII(nums, target) {\n const state = []; // 状態(部分集合)\n nums.sort((a, b) =&gt; a - b); // nums をソートする\n const start = 0; // 走査の開始位置\n const res = []; // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_ii.ts<pre><code>/* バックトラッキング:部分集合和 II */\nfunction backtrack(\n state: number[],\n target: number,\n choices: number[],\n start: number,\n res: number[][]\n): void {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target === 0) {\n res.push([...state]);\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n // 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for (let i = start; i &lt; choices.length; i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if (i &gt; start &amp;&amp; choices[i] === choices[i - 1]) {\n continue;\n }\n // 試行:選択を行い、target, start を更新する\n state.push(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i + 1, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop();\n }\n}\n\n/* 部分集合和 II を解く */\nfunction subsetSumII(nums: number[], target: number): number[][] {\n const state = []; // 状態(部分集合)\n nums.sort((a, b) =&gt; a - b); // nums をソートする\n const start = 0; // 走査の開始位置\n const res = []; // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_ii.dart<pre><code>/* バックトラッキング:部分集合和 II */\nvoid backtrack(\n List&lt;int&gt; state,\n int target,\n List&lt;int&gt; choices,\n int start,\n List&lt;List&lt;int&gt;&gt; res,\n) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n res.add(List.from(state));\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n // 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for (int i = start; i &lt; choices.length; i++) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break;\n }\n // 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if (i &gt; start &amp;&amp; choices[i] == choices[i - 1]) {\n continue;\n }\n // 試行:選択を行い、target, start を更新する\n state.add(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i + 1, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.removeLast();\n }\n}\n\n/* 部分集合和 II を解く */\nList&lt;List&lt;int&gt;&gt; subsetSumII(List&lt;int&gt; nums, int target) {\n List&lt;int&gt; state = []; // 状態(部分集合)\n nums.sort(); // nums をソートする\n int start = 0; // 走査の開始位置\n List&lt;List&lt;int&gt;&gt; res = []; // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res);\n return res;\n}\n</code></pre> subset_sum_ii.rs<pre><code>/* バックトラッキング:部分集合和 II */\nfn backtrack(\n state: &amp;mut Vec&lt;i32&gt;,\n target: i32,\n choices: &amp;[i32],\n start: usize,\n res: &amp;mut Vec&lt;Vec&lt;i32&gt;&gt;,\n) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if target == 0 {\n res.push(state.clone());\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n // 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for i in start..choices.len() {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if target - choices[i] &lt; 0 {\n break;\n }\n // 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if i &gt; start &amp;&amp; choices[i] == choices[i - 1] {\n continue;\n }\n // 試行:選択を行い、target, start を更新する\n state.push(choices[i]);\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i + 1, res);\n // 復元:選択を取り消し、以前の状態に戻す\n state.pop();\n }\n}\n\n/* 部分集合和 II を解く */\nfn subset_sum_ii(nums: &amp;mut [i32], target: i32) -&gt; Vec&lt;Vec&lt;i32&gt;&gt; {\n let mut state = Vec::new(); // 状態(部分集合)\n nums.sort(); // nums をソートする\n let start = 0; // 走査の開始位置\n let mut res = Vec::new(); // 結果リスト(部分集合のリスト)\n backtrack(&amp;mut state, target, nums, start, &amp;mut res);\n res\n}\n</code></pre> subset_sum_ii.c<pre><code>/* バックトラッキング:部分集合和 II */\nvoid backtrack(int target, int *choices, int choicesSize, int start) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n for (int i = 0; i &lt; stateSize; i++) {\n res[resSize][i] = state[i];\n }\n resColSizes[resSize++] = stateSize;\n return;\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n // 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for (int i = start; i &lt; choicesSize; i++) {\n // 枝刈り 1:部分集合の和が target を超える場合、直ちにスキップする\n if (target - choices[i] &lt; 0) {\n continue;\n }\n // 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if (i &gt; start &amp;&amp; choices[i] == choices[i - 1]) {\n continue;\n }\n // 試行:選択を行い、target, start を更新する\n state[stateSize] = choices[i];\n stateSize++;\n // 次の選択に進む\n backtrack(target - choices[i], choices, choicesSize, i + 1);\n // 復元:選択を取り消し、以前の状態に戻す\n stateSize--;\n }\n}\n\n/* 部分集合和 II を解く */\nvoid subsetSumII(int *nums, int numsSize, int target) {\n // nums をソートする\n qsort(nums, numsSize, sizeof(int), cmp);\n // バックトラッキングを開始する\n backtrack(target, nums, numsSize, 0);\n}\n</code></pre> subset_sum_ii.kt<pre><code>/* バックトラッキング:部分集合和 II */\nfun backtrack(\n state: MutableList&lt;Int&gt;,\n target: Int,\n choices: IntArray,\n start: Int,\n res: MutableList&lt;MutableList&lt;Int&gt;?&gt;\n) {\n // 部分集合の和が target と等しくなったら、解を記録する\n if (target == 0) {\n res.add(state.toMutableList())\n return\n }\n // すべての選択肢を走査する\n // 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n // 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for (i in start..&lt;choices.size) {\n // 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n // 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n if (target - choices[i] &lt; 0) {\n break\n }\n // 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n if (i &gt; start &amp;&amp; choices[i] == choices[i - 1]) {\n continue\n }\n // 試行:選択を行い、target, start を更新する\n state.add(choices[i])\n // 次の選択に進む\n backtrack(state, target - choices[i], choices, i + 1, res)\n // 復元:選択を取り消し、以前の状態に戻す\n state.removeAt(state.size - 1)\n }\n}\n\n/* 部分集合和 II を解く */\nfun subsetSumII(nums: IntArray, target: Int): MutableList&lt;MutableList&lt;Int&gt;?&gt; {\n val state = mutableListOf&lt;Int&gt;() // 状態(部分集合)\n nums.sort() // nums をソートする\n val start = 0 // 走査の開始位置\n val res = mutableListOf&lt;MutableList&lt;Int&gt;?&gt;() // 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res)\n return res\n}\n</code></pre> subset_sum_ii.rb<pre><code>### バックトラッキング:部分集合和 II ###\ndef backtrack(state, target, choices, start, res)\n # 部分集合の和が target と等しくなったら、解を記録する\n if target.zero?\n res &lt;&lt; state.dup\n return\n end\n\n # すべての選択肢を走査する\n # 枝刈り 2:start から走査を始め、重複した部分集合の生成を避ける\n # 枝刈り 3:start から走査を始め、同じ要素の再選択を避ける\n for i in start...choices.length\n # 枝刈り 1:部分集合の和が target を超えたら、直ちにループを終了する\n # 配列はソート済みで後ろの要素ほど大きいため、部分集合の和が必ず target を超えるからである\n break if target - choices[i] &lt; 0\n # 枝刈り 4:この要素が左隣の要素と等しい場合、探索の分岐が重複するため、直ちにスキップする\n next if i &gt; start &amp;&amp; choices[i] == choices[i - 1]\n # 試行:選択を行い、target, start を更新する\n state &lt;&lt; choices[i]\n # 次の選択に進む\n backtrack(state, target - choices[i], choices, i + 1, res)\n # 復元:選択を取り消し、以前の状態に戻す\n state.pop\n end\nend\n\n### 部分集合和 II を解く ###\ndef subset_sum_ii(nums, target)\n state = [] # 状態(部分集合)\n nums.sort! # nums をソートする\n start = 0 # 走査の開始位置\n res = [] # 結果リスト(部分集合のリスト)\n backtrack(state, target, nums, start, res)\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 13-14 に、配列 \\([4, 4, 5]\\) と目標 \\(9\\) のバックトラッキングを示します。4 種類の枝刈りが含まれています。図とコードの注釈を照らし合わせ、探索全体の流れと、それぞれの枝刈りの働きを理解してください。</p> <p></p> <p> 図 13-14   部分集合和 II のバックトラッキング </p>","path":["第 13 章   バックトラッキング","13.3   部分集合和問題"],"tags":[]},{"location":"chapter_backtracking/summary/","level":1,"title":"13.5   まとめ","text":"","path":["第 13 章   バックトラッキング","13.5   まとめ"],"tags":[]},{"location":"chapter_backtracking/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>バックトラッキングは本質的に総当たり法で、解の空間を深さ優先で走査し、条件を満たす解を探します。見つかった解を記録し、すべての解を得るか、走査が終わるまで続けます。</li> <li>探索は試行と後戻りからなります。深さ優先探索で選択肢を試し、制約を満たさない場合は直前の選択を取り消して元の状態に戻り、ほかの選択肢を試します。試行と後戻りは互いに逆の操作です。</li> <li>バックトラッキングの問題には通常、複数の制約があり、それらを枝刈りに利用できます。不要な探索分岐を早めに打ち切ることで、効率を大きく高められます。</li> <li>主に探索問題と制約充足問題に使われます。組合せ最適化問題も解けますが、より効率的な方法や、よりよい結果を得られる方法があることが多いです。</li> <li>順列列挙問題では、与えられた集合の要素のすべての並べ方を探します。各要素が選択済みかを配列に記録し、同じ要素を再び選ぶ分岐を枝刈りして、各要素を一度だけ選びます。</li> <li>順列列挙で集合に重複した要素があると、結果の順列も重複します。通常はハッシュ集合を使い、同じ値の要素を各回の選択で一度だけ選ぶよう制限します。</li> <li>部分集合和問題では、与えられた集合から、和が目標値になる部分集合をすべて探します。集合は順序を区別しませんが、探索はすべての順序を出力するため、重複が生じます。探索前にソートし、各回の走査の開始位置を変数で指定することで、重複を生む分岐を枝刈りします。</li> <li>部分集合和問題では、配列内の同じ値の要素も重複した集合を生みます。ソート済みであることを利用し、隣り合う要素が同じかを調べて枝刈りすることで、同じ値を各回で一度だけ選ぶようにします。</li> <li>\\(n\\) クイーン問題では、\\(n\\) 個のクイーンを \\(n \\times n\\) の盤面に、どの 2 個も攻撃し合わないよう配置します。行、列、主対角線方向、副対角線方向の制約があります。行の制約は、各行に一つずつ配置する戦略で満たします。</li> <li>列と対角線の制約は似た方法で扱います。列には、各列にクイーンがあるかを記録する配列を使い、選んだマスが有効かを判定します。対角線には二つの配列を使い、各方向の線上にクイーンがあるかを記録します。難しいのは、同じ主(副)対角線方向の線上にあるマスの、行・列インデックスの規則を見つけることです。</li> </ul>","path":["第 13 章   バックトラッキング","13.5   まとめ"],"tags":[]},{"location":"chapter_backtracking/summary/#2-q-a","level":3,"title":"2.   Q &amp; A","text":"<p>Q:バックトラッキングと再帰の関係は、どう理解すればよいですか。</p> <p>大まかに言うと、バックトラッキングは「アルゴリズムの戦略」で、再帰は「道具」に近いものです。</p> <ul> <li>バックトラッキングは通常、再帰で実装します。ただし、バックトラッキングは再帰の用途の一つであり、探索問題に再帰を使う方法です。</li> <li>再帰の構造は「部分問題に分解する」という解き方を表し、分割統治、バックトラッキング、動的計画法(メモ化再帰)などによく使われます。</li> </ul>","path":["第 13 章   バックトラッキング","13.5   まとめ"],"tags":[]},{"location":"chapter_computational_complexity/","level":1,"title":"第 2 章   計算量の解析","text":"<p>概要</p> <p>計算量の解析は、広大なアルゴリズムの宇宙で時空を案内するガイドのようです。</p> <p>時間と空間という二つの次元を深く探り、より洗練された解法を見つける手助けをしてくれます。</p>","path":["第 2 章   計算量の解析"],"tags":[]},{"location":"chapter_computational_complexity/#_1","level":2,"title":"この章の内容","text":"<ul> <li>2.1   アルゴリズムの効率の評価</li> <li>2.2   反復と再帰</li> <li>2.3   時間計算量</li> <li>2.4   空間計算量</li> <li>2.5   まとめ</li> <li>2.6   練習問題</li> </ul>","path":["第 2 章   計算量の解析"],"tags":[]},{"location":"chapter_computational_complexity/exercises/","level":1,"title":"2.6   練習問題","text":"","path":["第 2 章   計算量の解析","2.6   練習問題"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#261","level":2,"title":"2.6.1   知識の確認","text":"","path":["第 2 章   計算量の解析","2.6   練習問題"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#1","level":3,"title":"1.   反復と再帰の時間と空間","text":"<p>次の二つのコードは、どちらも \\(1 + 2 + \\dots + n\\)(\\(n \\ge 1\\) とします)を計算します。<code>n</code> を 4 に設定し、 プログラムが実際に実行される順に沿って問いに答え、二つの書き方の効率を比較してください。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby complexity_exercises.py<pre><code>def sum_iter(n: int) -&gt; int:\n \"\"\"反復による総和\"\"\"\n res = 0\n for i in range(1, n + 1):\n res += i\n return res\n\ndef sum_recur(n: int) -&gt; int:\n \"\"\"再帰による総和\"\"\"\n if n == 1:\n return 1\n return n + sum_recur(n - 1)\n</code></pre> complexity_exercises.cpp<pre><code>/* 反復による総和 */\nint sumIter(int n) {\n int res = 0;\n for (int i = 1; i &lt;= n; ++i) {\n res += i;\n }\n return res;\n}\n\n/* 再帰による総和 */\nint sumRecur(int n) {\n if (n == 1) {\n return 1;\n }\n return n + sumRecur(n - 1);\n}\n</code></pre> complexity_exercises.java<pre><code>/* 反復による総和 */\nint sumIter(int n) {\n int res = 0;\n for (int i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 再帰による総和 */\nint sumRecur(int n) {\n if (n == 1) {\n return 1;\n }\n return n + sumRecur(n - 1);\n}\n</code></pre> complexity_exercises.cs<pre><code>/* 反復による総和 */\nint SumIter(int n) {\n int res = 0;\n for (int i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 再帰による総和 */\nint SumRecur(int n) {\n if (n == 1) {\n return 1;\n }\n return n + SumRecur(n - 1);\n}\n</code></pre> complexity_exercises.go<pre><code>/* 反復による総和 */\nfunc sumIter(n int) int {\n res := 0\n for i := 1; i &lt;= n; i++ {\n res += i\n }\n return res\n}\n\n/* 再帰による総和 */\nfunc sumRecur(n int) int {\n if n == 1 {\n return 1\n }\n return n + sumRecur(n-1)\n}\n</code></pre> complexity_exercises.swift<pre><code>/* 反復による総和 */\nfunc sumIter(n: Int) -&gt; Int {\n var res = 0\n for i in 1 ... n {\n res += i\n }\n return res\n}\n\n/* 再帰による総和 */\nfunc sumRecur(n: Int) -&gt; Int {\n if n == 1 {\n return 1\n }\n return n + sumRecur(n: n - 1)\n}\n</code></pre> complexity_exercises.js<pre><code>/* 反復による総和 */\nfunction sumIter(n) {\n let res = 0;\n for (let i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 再帰による総和 */\nfunction sumRecur(n) {\n if (n === 1) {\n return 1;\n }\n return n + sumRecur(n - 1);\n}\n</code></pre> complexity_exercises.ts<pre><code>/* 反復による総和 */\nfunction sumIter(n: number): number {\n let res = 0;\n for (let i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 再帰による総和 */\nfunction sumRecur(n: number): number {\n if (n === 1) {\n return 1;\n }\n return n + sumRecur(n - 1);\n}\n</code></pre> complexity_exercises.dart<pre><code>/* 反復による総和 */\nint sumIter(int n) {\n int res = 0;\n for (int i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 再帰による総和 */\nint sumRecur(int n) {\n if (n == 1) {\n return 1;\n }\n return n + sumRecur(n - 1);\n}\n</code></pre> complexity_exercises.rs<pre><code>/* 反復による総和 */\nfn sum_iter(n: i32) -&gt; i32 {\n let mut res = 0;\n for i in 1..=n {\n res += i;\n }\n res\n}\n\n/* 再帰による総和 */\nfn sum_recur(n: i32) -&gt; i32 {\n if n == 1 {\n return 1;\n }\n n + sum_recur(n - 1)\n}\n</code></pre> complexity_exercises.c<pre><code>/* 反復による総和 */\nint sumIter(int n) {\n int res = 0;\n for (int i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 再帰による総和 */\nint sumRecur(int n) {\n if (n == 1) {\n return 1;\n }\n return n + sumRecur(n - 1);\n}\n</code></pre> complexity_exercises.kt<pre><code>/* 反復による総和 */\nfun sumIter(n: Int): Int {\n var res = 0\n for (i in 1..n) {\n res += i\n }\n return res\n}\n\n/* 再帰による総和 */\nfun sumRecur(n: Int): Int {\n if (n == 1) {\n return 1\n }\n return n + sumRecur(n - 1)\n}\n</code></pre> complexity_exercises.rb<pre><code>### 反復による総和 ###\ndef sum_iter(n)\n res = 0\n for i in 1..n\n res += i\n end\n res\nend\n\n### 再帰による総和 ###\ndef sum_recur(n)\n return 1 if n == 1\n\n n + sum_recur(n - 1)\nend\n</code></pre> <ol> <li><code>n = 4</code> を入力して反復関数を実行すると、各反復の終了後に累積変数 <code>res</code> の値はそれぞれいくつになりますか。</li> <li><code>n = 4</code> を入力して再帰関数を実行すると、引数 <code>n</code> は順にどの値を取りますか。最も深い呼び出しから戻るとき、結果はどのように求まりますか。</li> <li>二つの書き方の時間計算量と空間計算量はそれぞれいくつですか。問い 1、2 の実行過程に基づいて理由を説明してください。</li> </ol> 解答例 <ol> <li> <p>ループ変数 <code>i</code> は順に <code>1、2、3、4</code> となり、各反復の終了後に <code>res</code> は <code>1、3、6、10</code> となります。したがって、反復関数は 10 を返します。</p> </li> <li> <p>引数 <code>n</code> は順に <code>4 → 3 → 2 → 1</code> となります。 最も深い呼び出しが 1 を返し、その後、各呼び出しで順に <code>2 + 1 = 3</code>、<code>3 + 3 = 6</code>、<code>4 + 6 = 10</code> が求まります。 最も深い位置では、4 回の関数呼び出しがすべて未完了です。</p> </li> <li> <p>どちらも \\(n\\) に比例する回数のループまたは呼び出しを行うため、時間計算量はともに \\(O(n)\\) です。 空間計算量は異なります。反復版は定数個の変数だけを使うので、\\(O(1)\\) です。 再帰版では、終了条件に達するまで、先に行った関数呼び出しが結果を待ち続けます。このため、呼び出しスタックには最大で \\(n\\) 回の呼び出しが同時に保持され、 空間計算量は \\(O(n)\\) となります。</p> <p>空間計算量の解析では、コード中の変数に加え、再帰呼び出しが使う領域も考慮する必要があります。</p> </li> </ol>","path":["第 2 章   計算量の解析","2.6   練習問題"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#2","level":3,"title":"2.   三つのコードの時間計算量","text":"<p>次の三つのコード片には、いずれも正の整数 \\(n\\) を入力します。時間計算量が小さい順に並べ、それぞれの計算量を書いてください。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby complexity_exercises.py<pre><code>def linear_loop(n: int) -&gt; int:\n \"\"\"線形オーダーのループ\"\"\"\n res = 0\n for i in range(n):\n res += i\n return res\n\ndef quadratic_loop(n: int) -&gt; int:\n \"\"\"二次オーダーのループ\"\"\"\n res = 0\n for i in range(n):\n for j in range(i, n):\n res += j\n return res\n\ndef logarithmic_loop(n: int) -&gt; int:\n \"\"\"対数オーダーのループ\"\"\"\n while n &gt; 1:\n n //= 2\n return n\n</code></pre> complexity_exercises.cpp<pre><code>/* 線形オーダーのループ */\nint linearLoop(int n) {\n int res = 0;\n for (int i = 0; i &lt; n; ++i) {\n res += i;\n }\n return res;\n}\n\n/* 二次オーダーのループ */\nint quadraticLoop(int n) {\n int res = 0;\n for (int i = 0; i &lt; n; ++i) {\n for (int j = i; j &lt; n; ++j) {\n res += j;\n }\n }\n return res;\n}\n\n/* 対数オーダーのループ */\nint logarithmicLoop(int n) {\n while (n &gt; 1) {\n n /= 2;\n }\n return n;\n}\n</code></pre> complexity_exercises.java<pre><code>/* 線形オーダーのループ */\nint linearLoop(int n) {\n int res = 0;\n for (int i = 0; i &lt; n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 二次オーダーのループ */\nint quadraticLoop(int n) {\n int res = 0;\n for (int i = 0; i &lt; n; i++) {\n for (int j = i; j &lt; n; j++) {\n res += j;\n }\n }\n return res;\n}\n\n/* 対数オーダーのループ */\nint logarithmicLoop(int n) {\n while (n &gt; 1) {\n n /= 2;\n }\n return n;\n}\n</code></pre> complexity_exercises.cs<pre><code>/* 線形オーダーのループ */\nint LinearLoop(int n) {\n int res = 0;\n for (int i = 0; i &lt; n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 二次オーダーのループ */\nint QuadraticLoop(int n) {\n int res = 0;\n for (int i = 0; i &lt; n; i++) {\n for (int j = i; j &lt; n; j++) {\n res += j;\n }\n }\n return res;\n}\n\n/* 対数オーダーのループ */\nint LogarithmicLoop(int n) {\n while (n &gt; 1) {\n n /= 2;\n }\n return n;\n}\n</code></pre> complexity_exercises.go<pre><code>/* 線形オーダーのループ */\nfunc linearLoop(n int) int {\n res := 0\n for i := 0; i &lt; n; i++ {\n res += i\n }\n return res\n}\n\n/* 二次オーダーのループ */\nfunc quadraticLoop(n int) int {\n res := 0\n for i := 0; i &lt; n; i++ {\n for j := i; j &lt; n; j++ {\n res += j\n }\n }\n return res\n}\n\n/* 対数オーダーのループ */\nfunc logarithmicLoop(n int) int {\n for n &gt; 1 {\n n /= 2\n }\n return n\n}\n</code></pre> complexity_exercises.swift<pre><code>/* 線形オーダーのループ */\nfunc linearLoop(n: Int) -&gt; Int {\n var res = 0\n for i in 0 ..&lt; n {\n res += i\n }\n return res\n}\n\n/* 二次オーダーのループ */\nfunc quadraticLoop(n: Int) -&gt; Int {\n var res = 0\n for i in 0 ..&lt; n {\n for j in i ..&lt; n {\n res += j\n }\n }\n return res\n}\n\n/* 対数オーダーのループ */\nfunc logarithmicLoop(n: Int) -&gt; Int {\n var n = n\n while n &gt; 1 {\n n /= 2\n }\n return n\n}\n</code></pre> complexity_exercises.js<pre><code>/* 線形オーダーのループ */\nfunction linearLoop(n) {\n let res = 0;\n for (let i = 0; i &lt; n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 二次オーダーのループ */\nfunction quadraticLoop(n) {\n let res = 0;\n for (let i = 0; i &lt; n; i++) {\n for (let j = i; j &lt; n; j++) {\n res += j;\n }\n }\n return res;\n}\n\n/* 対数オーダーのループ */\nfunction logarithmicLoop(n) {\n while (n &gt; 1) {\n n = Math.floor(n / 2);\n }\n return n;\n}\n</code></pre> complexity_exercises.ts<pre><code>/* 線形オーダーのループ */\nfunction linearLoop(n: number): number {\n let res = 0;\n for (let i = 0; i &lt; n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 二次オーダーのループ */\nfunction quadraticLoop(n: number): number {\n let res = 0;\n for (let i = 0; i &lt; n; i++) {\n for (let j = i; j &lt; n; j++) {\n res += j;\n }\n }\n return res;\n}\n\n/* 対数オーダーのループ */\nfunction logarithmicLoop(n: number): number {\n while (n &gt; 1) {\n n = Math.floor(n / 2);\n }\n return n;\n}\n</code></pre> complexity_exercises.dart<pre><code>/* 線形オーダーのループ */\nint linearLoop(int n) {\n int res = 0;\n for (int i = 0; i &lt; n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 二次オーダーのループ */\nint quadraticLoop(int n) {\n int res = 0;\n for (int i = 0; i &lt; n; i++) {\n for (int j = i; j &lt; n; j++) {\n res += j;\n }\n }\n return res;\n}\n\n/* 対数オーダーのループ */\nint logarithmicLoop(int n) {\n while (n &gt; 1) {\n n ~/= 2;\n }\n return n;\n}\n</code></pre> complexity_exercises.rs<pre><code>/* 線形オーダーのループ */\nfn linear_loop(n: i32) -&gt; i32 {\n let mut res = 0;\n for i in 0..n {\n res += i;\n }\n res\n}\n\n/* 二次オーダーのループ */\nfn quadratic_loop(n: i32) -&gt; i32 {\n let mut res = 0;\n for i in 0..n {\n for j in i..n {\n res += j;\n }\n }\n res\n}\n\n/* 対数オーダーのループ */\nfn logarithmic_loop(mut n: i32) -&gt; i32 {\n while n &gt; 1 {\n n /= 2;\n }\n n\n}\n</code></pre> complexity_exercises.c<pre><code>/* 線形オーダーのループ */\nint linearLoop(int n) {\n int res = 0;\n for (int i = 0; i &lt; n; i++) {\n res += i;\n }\n return res;\n}\n\n/* 二次オーダーのループ */\nint quadraticLoop(int n) {\n int res = 0;\n for (int i = 0; i &lt; n; i++) {\n for (int j = i; j &lt; n; j++) {\n res += j;\n }\n }\n return res;\n}\n\n/* 対数オーダーのループ */\nint logarithmicLoop(int n) {\n while (n &gt; 1) {\n n /= 2;\n }\n return n;\n}\n</code></pre> complexity_exercises.kt<pre><code>/* 線形オーダーのループ */\nfun linearLoop(n: Int): Int {\n var res = 0\n for (i in 0 until n) {\n res += i\n }\n return res\n}\n\n/* 二次オーダーのループ */\nfun quadraticLoop(n: Int): Int {\n var res = 0\n for (i in 0 until n) {\n for (j in i until n) {\n res += j\n }\n }\n return res\n}\n\n/* 対数オーダーのループ */\nfun logarithmicLoop(n: Int): Int {\n var value = n\n while (value &gt; 1) {\n value /= 2\n }\n return value\n}\n</code></pre> complexity_exercises.rb<pre><code>### 線形オーダーのループ ###\ndef linear_loop(n)\n res = 0\n for i in 0...n\n res += i\n end\n res\nend\n\n### 二次オーダーのループ ###\ndef quadratic_loop(n)\n res = 0\n for i in 0...n\n for j in i...n\n res += j\n end\n end\n res\nend\n\n### 対数オーダーのループ ###\ndef logarithmic_loop(n)\n n /= 2 while n &gt; 1\n n\nend\n</code></pre> 解答例 <p>小さい順に、コード片 3 が \\(O(\\log n)\\)、コード片 1 が \\(O(n)\\)、コード片 2 が \\(O(n^2)\\) です。 コード片 3 は、各反復で \\(n\\) を半分にするため、約 \\(\\log_2 n\\) 回繰り返します。 コード片 1 のループは、ちょうど \\(n\\) 回実行されます。コード片 2 の内側のループ回数は順に \\(n,n-1,\\dots,1\\) で、合計 \\(n(n+1)/2\\) 回となるため、二次のオーダーです。</p>","path":["第 2 章   計算量の解析","2.6   練習問題"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#3","level":3,"title":"3.   領域を節約できる反転方法","text":"<p>配列 <code>nums</code> の全要素を反転する方法として、次の二つがあります。</p> <ol> <li>同じ長さの配列 <code>res</code> を新しく作り、逆順にコピーして返します。</li> <li> <p>二つのインデックス <code>i</code> と <code>j</code> をそれぞれ先頭と末尾から中央へ進め、<code>nums[i]</code> と <code>nums[j]</code> を一組ずつ交換します。</p> <p>それぞれの空間計算量はいくつですか。どちらが「インプレース」の操作ですか。</p> </li> </ol> 解答例 <ol> <li> <p>入力と同じ長さの補助配列が必要なので、空間計算量は \\(O(n)\\) です。</p> </li> <li> <p>二つのインデックス変数だけを使うため、 空間計算量は \\(O(1)\\) で、インプレースの操作です。</p> <p>注意点として、インプレースの反転は入力配列を変更します。 入力の変更が許される場合に限って優先すべきです。元の配列を残す必要があれば、方法 1 のコピーにかかるコストは避けられません。</p> </li> </ol>","path":["第 2 章   計算量の解析","2.6   練習問題"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#262","level":2,"title":"2.6.2   プログラミング演習","text":"","path":["第 2 章   計算量の解析","2.6   練習問題"],"tags":[]},{"location":"chapter_computational_complexity/exercises/#1_1","level":3,"title":"1.   フィボナッチ数","text":"<p>フィボナッチ数列は、\\(F(0)=0\\)、\\(F(1)=1\\) を満たし、\\(n\\ge2\\) のとき \\(F(n)=F(n-1)+F(n-2)\\) が成り立ちます。</p> <p>非負整数 <code>n</code> が与えられます。再帰を使わず、ループで \\(F(n)\\) を計算して返してください。</p> 解くためのヒント <ol> <li>まず n が 0 と 1 の場合を個別に処理します</li> <li>次の項の計算には直前の二項だけが必要で、数列全体を保持する必要はありません</li> <li>二つの変数を更新するときは、まだ使う古い値を先に上書きしないように注意します</li> </ol> <p>LeetCode</p>","path":["第 2 章   計算量の解析","2.6   練習問題"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/","level":1,"title":"2.2   反復と再帰","text":"<p>アルゴリズムでは、同じ処理を繰り返すことがよくあり、計算量の解析とも密接に関わります。そこで時間計算量と空間計算量を説明する前に、繰り返しを実現する二つの基本的な制御構造、反復と再帰を学びます。</p>","path":["第 2 章   計算量の解析","2.2   反復と再帰"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#221","level":2,"title":"2.2.1   反復","text":"<p>反復(iteration)は、ある処理を繰り返し実行する制御構造です。一定の条件を満たす間、同じコードを実行し続け、条件を満たさなくなったら終了します。</p>","path":["第 2 章   計算量の解析","2.2   反復と再帰"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#1-for","level":3,"title":"1.   for ループ","text":"<p><code>for</code> ループは最も一般的な反復の形の一つで、繰り返し回数があらかじめわかっている場合に適しています。</p> <p>次の関数は、<code>for</code> ループで \\(1 + 2 + \\dots + n\\) を計算し、結果を変数 <code>res</code> に記録します。Python の <code>range(a, b)</code> は左端を含み右端を含まない区間なので、走査する範囲は \\(a, a + 1, \\dots, b-1\\) です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py<pre><code>def for_loop(n: int) -&gt; int:\n \"\"\"for ループ\"\"\"\n res = 0\n # ループで 1, 2, ..., n-1, n の和を求める\n for i in range(1, n + 1):\n res += i\n return res\n</code></pre> iteration.cpp<pre><code>/* for ループ */\nint forLoop(int n) {\n int res = 0;\n // ループで 1, 2, ..., n-1, n の和を求める\n for (int i = 1; i &lt;= n; ++i) {\n res += i;\n }\n return res;\n}\n</code></pre> iteration.java<pre><code>/* for ループ */\nint forLoop(int n) {\n int res = 0;\n // ループで 1, 2, ..., n-1, n の和を求める\n for (int i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n</code></pre> iteration.cs<pre><code>/* for ループ */\nint ForLoop(int n) {\n int res = 0;\n // ループで 1, 2, ..., n-1, n の和を求める\n for (int i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n</code></pre> iteration.go<pre><code>/* for ループ */\nfunc forLoop(n int) int {\n res := 0\n // ループで 1, 2, ..., n-1, n の和を求める\n for i := 1; i &lt;= n; i++ {\n res += i\n }\n return res\n}\n</code></pre> iteration.swift<pre><code>/* for ループ */\nfunc forLoop(n: Int) -&gt; Int {\n var res = 0\n // ループで 1, 2, ..., n-1, n の和を求める\n for i in 1 ... n {\n res += i\n }\n return res\n}\n</code></pre> iteration.js<pre><code>/* for ループ */\nfunction forLoop(n) {\n let res = 0;\n // ループで 1, 2, ..., n-1, n の和を求める\n for (let i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n</code></pre> iteration.ts<pre><code>/* for ループ */\nfunction forLoop(n: number): number {\n let res = 0;\n // ループで 1, 2, ..., n-1, n の和を求める\n for (let i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n</code></pre> iteration.dart<pre><code>/* for ループ */\nint forLoop(int n) {\n int res = 0;\n // ループで 1, 2, ..., n-1, n の和を求める\n for (int i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n</code></pre> iteration.rs<pre><code>/* for ループ */\nfn for_loop(n: i32) -&gt; i32 {\n let mut res = 0;\n // ループで 1, 2, ..., n-1, n の和を求める\n for i in 1..=n {\n res += i;\n }\n res\n}\n</code></pre> iteration.c<pre><code>/* for ループ */\nint forLoop(int n) {\n int res = 0;\n // ループで 1, 2, ..., n-1, n の和を求める\n for (int i = 1; i &lt;= n; i++) {\n res += i;\n }\n return res;\n}\n</code></pre> iteration.kt<pre><code>/* for ループ */\nfun forLoop(n: Int): Int {\n var res = 0\n // ループで 1, 2, ..., n-1, n の和を求める\n for (i in 1..n) {\n res += i\n }\n return res\n}\n</code></pre> iteration.rb<pre><code>### for ループ ###\ndef for_loop(n)\n res = 0\n\n # ループで 1, 2, ..., n-1, n の和を求める\n for i in 1..n\n res += i\n end\n\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 2-1 に、この合計を求める関数のフローチャートを示します。</p> <p></p> <p> 図 2-1   合計を求める関数のフローチャート </p> <p>この関数の操作回数は入力の大きさ \\(n\\) に比例し、「線形の関係」にあります。実際、時間計算量が記述するのは、この「線形の関係」です。詳しくは次の節で説明します。</p>","path":["第 2 章   計算量の解析","2.2   反復と再帰"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#2-while","level":3,"title":"2.   while ループ","text":"<p><code>for</code> と同様に、<code>while</code> ループも反復を実現する方法です。毎回、最初に条件を調べ、真なら処理を続け、偽ならループを終了します。</p> <p><code>while</code> ループで \\(1 + 2 + \\dots + n\\) を計算してみましょう。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py<pre><code>def while_loop(n: int) -&gt; int:\n \"\"\"while ループ\"\"\"\n res = 0\n i = 1 # 条件変数を初期化する\n # ループで 1, 2, ..., n-1, n の和を求める\n while i &lt;= n:\n res += i\n i += 1 # 条件変数を更新する\n return res\n</code></pre> iteration.cpp<pre><code>/* while ループ */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // 条件変数を初期化する\n // ループで 1, 2, ..., n-1, n の和を求める\n while (i &lt;= n) {\n res += i;\n i++; // 条件変数を更新する\n }\n return res;\n}\n</code></pre> iteration.java<pre><code>/* while ループ */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // 条件変数を初期化する\n // ループで 1, 2, ..., n-1, n の和を求める\n while (i &lt;= n) {\n res += i;\n i++; // 条件変数を更新する\n }\n return res;\n}\n</code></pre> iteration.cs<pre><code>/* while ループ */\nint WhileLoop(int n) {\n int res = 0;\n int i = 1; // 条件変数を初期化する\n // ループで 1, 2, ..., n-1, n の和を求める\n while (i &lt;= n) {\n res += i;\n i += 1; // 条件変数を更新する\n }\n return res;\n}\n</code></pre> iteration.go<pre><code>/* while ループ */\nfunc whileLoop(n int) int {\n res := 0\n // 条件変数を初期化する\n i := 1\n // ループで 1, 2, ..., n-1, n の和を求める\n for i &lt;= n {\n res += i\n // 条件変数を更新する\n i++\n }\n return res\n}\n</code></pre> iteration.swift<pre><code>/* while ループ */\nfunc whileLoop(n: Int) -&gt; Int {\n var res = 0\n var i = 1 // 条件変数を初期化する\n // ループで 1, 2, ..., n-1, n の和を求める\n while i &lt;= n {\n res += i\n i += 1 // 条件変数を更新する\n }\n return res\n}\n</code></pre> iteration.js<pre><code>/* while ループ */\nfunction whileLoop(n) {\n let res = 0;\n let i = 1; // 条件変数を初期化する\n // ループで 1, 2, ..., n-1, n の和を求める\n while (i &lt;= n) {\n res += i;\n i++; // 条件変数を更新する\n }\n return res;\n}\n</code></pre> iteration.ts<pre><code>/* while ループ */\nfunction whileLoop(n: number): number {\n let res = 0;\n let i = 1; // 条件変数を初期化する\n // ループで 1, 2, ..., n-1, n の和を求める\n while (i &lt;= n) {\n res += i;\n i++; // 条件変数を更新する\n }\n return res;\n}\n</code></pre> iteration.dart<pre><code>/* while ループ */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // 条件変数を初期化する\n // ループで 1, 2, ..., n-1, n の和を求める\n while (i &lt;= n) {\n res += i;\n i++; // 条件変数を更新する\n }\n return res;\n}\n</code></pre> iteration.rs<pre><code>/* while ループ */\nfn while_loop(n: i32) -&gt; i32 {\n let mut res = 0;\n let mut i = 1; // 条件変数を初期化する\n\n // ループで 1, 2, ..., n-1, n の和を求める\n while i &lt;= n {\n res += i;\n i += 1; // 条件変数を更新する\n }\n res\n}\n</code></pre> iteration.c<pre><code>/* while ループ */\nint whileLoop(int n) {\n int res = 0;\n int i = 1; // 条件変数を初期化する\n // ループで 1, 2, ..., n-1, n の和を求める\n while (i &lt;= n) {\n res += i;\n i++; // 条件変数を更新する\n }\n return res;\n}\n</code></pre> iteration.kt<pre><code>/* while ループ */\nfun whileLoop(n: Int): Int {\n var res = 0\n var i = 1 // 条件変数を初期化する\n // ループで 1, 2, ..., n-1, n の和を求める\n while (i &lt;= n) {\n res += i\n i++ // 条件変数を更新する\n }\n return res\n}\n</code></pre> iteration.rb<pre><code>### while ループ ###\ndef while_loop(n)\n res = 0\n i = 1 # 条件変数を初期化する\n\n # ループで 1, 2, ..., n-1, n の和を求める\n while i &lt;= n\n res += i\n i += 1 # 条件変数を更新する\n end\n\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p><code>while</code> ループは <code>for</code> ループより自由度が高く、条件変数の初期化と更新の手順を自由に設計できます。</p> <p>たとえば次のコードでは、条件変数 \\(i\\) を各回で 2 回更新します。このような処理は、<code>for</code> ループではやや書きにくくなります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py<pre><code>def while_loop_ii(n: int) -&gt; int:\n \"\"\"while ループ(2 回の更新)\"\"\"\n res = 0\n i = 1 # 条件変数を初期化する\n # ループで 1, 4, 10, ... の和を求める\n while i &lt;= n:\n res += i\n # 条件変数を更新する\n i += 1\n i *= 2\n return res\n</code></pre> iteration.cpp<pre><code>/* while ループ(2 回の更新) */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // 条件変数を初期化する\n // ループで 1, 4, 10, ... の和を求める\n while (i &lt;= n) {\n res += i;\n // 条件変数を更新する\n i++;\n i *= 2;\n }\n return res;\n}\n</code></pre> iteration.java<pre><code>/* while ループ(2 回の更新) */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // 条件変数を初期化する\n // ループで 1, 4, 10, ... の和を求める\n while (i &lt;= n) {\n res += i;\n // 条件変数を更新する\n i++;\n i *= 2;\n }\n return res;\n}\n</code></pre> iteration.cs<pre><code>/* while ループ(2 回の更新) */\nint WhileLoopII(int n) {\n int res = 0;\n int i = 1; // 条件変数を初期化する\n // ループで 1, 4, 10, ... の和を求める\n while (i &lt;= n) {\n res += i;\n // 条件変数を更新する\n i += 1; \n i *= 2;\n }\n return res;\n}\n</code></pre> iteration.go<pre><code>/* while ループ(2 回の更新) */\nfunc whileLoopII(n int) int {\n res := 0\n // 条件変数を初期化する\n i := 1\n // ループで 1, 4, 10, ... の和を求める\n for i &lt;= n {\n res += i\n // 条件変数を更新する\n i++\n i *= 2\n }\n return res\n}\n</code></pre> iteration.swift<pre><code>/* while ループ(2 回の更新) */\nfunc whileLoopII(n: Int) -&gt; Int {\n var res = 0\n var i = 1 // 条件変数を初期化する\n // ループで 1, 4, 10, ... の和を求める\n while i &lt;= n {\n res += i\n // 条件変数を更新する\n i += 1\n i *= 2\n }\n return res\n}\n</code></pre> iteration.js<pre><code>/* while ループ(2 回の更新) */\nfunction whileLoopII(n) {\n let res = 0;\n let i = 1; // 条件変数を初期化する\n // ループで 1, 4, 10, ... の和を求める\n while (i &lt;= n) {\n res += i;\n // 条件変数を更新する\n i++;\n i *= 2;\n }\n return res;\n}\n</code></pre> iteration.ts<pre><code>/* while ループ(2 回の更新) */\nfunction whileLoopII(n: number): number {\n let res = 0;\n let i = 1; // 条件変数を初期化する\n // ループで 1, 4, 10, ... の和を求める\n while (i &lt;= n) {\n res += i;\n // 条件変数を更新する\n i++;\n i *= 2;\n }\n return res;\n}\n</code></pre> iteration.dart<pre><code>/* while ループ(2 回の更新) */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // 条件変数を初期化する\n // ループで 1, 4, 10, ... の和を求める\n while (i &lt;= n) {\n res += i;\n // 条件変数を更新する\n i++;\n i *= 2;\n }\n return res;\n}\n</code></pre> iteration.rs<pre><code>/* while ループ(2 回の更新) */\nfn while_loop_ii(n: i32) -&gt; i32 {\n let mut res = 0;\n let mut i = 1; // 条件変数を初期化する\n\n // ループで 1, 4, 10, ... の和を求める\n while i &lt;= n {\n res += i;\n // 条件変数を更新する\n i += 1;\n i *= 2;\n }\n res\n}\n</code></pre> iteration.c<pre><code>/* while ループ(2 回の更新) */\nint whileLoopII(int n) {\n int res = 0;\n int i = 1; // 条件変数を初期化する\n // ループで 1, 4, 10, ... の和を求める\n while (i &lt;= n) {\n res += i;\n // 条件変数を更新する\n i++;\n i *= 2;\n }\n return res;\n}\n</code></pre> iteration.kt<pre><code>/* while ループ(2 回の更新) */\nfun whileLoopII(n: Int): Int {\n var res = 0\n var i = 1 // 条件変数を初期化する\n // ループで 1, 4, 10, ... の和を求める\n while (i &lt;= n) {\n res += i\n // 条件変数を更新する\n i++\n i *= 2\n }\n return res\n}\n</code></pre> iteration.rb<pre><code>### while ループ(2 回の更新)###\ndef while_loop_ii(n)\n res = 0\n i = 1 # 条件変数を初期化する\n\n # ループで 1, 4, 10, ... の和を求める\n while i &lt;= n\n res += i\n # 条件変数を更新する\n i += 1\n i *= 2\n end\n\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>全体として、<code>for</code> はコードが簡潔で、<code>while</code> は柔軟です。どちらも反復を実現でき、問題の要件に応じて選びます。</p>","path":["第 2 章   計算量の解析","2.2   反復と再帰"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#3","level":3,"title":"3.   入れ子のループ","text":"<p>ループの中に別のループを入れられます。<code>for</code> の例を示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby iteration.py<pre><code>def nested_for_loop(n: int) -&gt; str:\n \"\"\"二重 for ループ\"\"\"\n res = \"\"\n # i = 1, 2, ..., n-1, n のループ\n for i in range(1, n + 1):\n # j = 1, 2, ..., n-1, n のループ\n for j in range(1, n + 1):\n res += f\"({i}, {j}), \"\n return res\n</code></pre> iteration.cpp<pre><code>/* 二重 for ループ */\nstring nestedForLoop(int n) {\n ostringstream res;\n // i = 1, 2, ..., n-1, n のループ\n for (int i = 1; i &lt;= n; ++i) {\n // j = 1, 2, ..., n-1, n のループ\n for (int j = 1; j &lt;= n; ++j) {\n res &lt;&lt; \"(\" &lt;&lt; i &lt;&lt; \", \" &lt;&lt; j &lt;&lt; \"), \";\n }\n }\n return res.str();\n}\n</code></pre> iteration.java<pre><code>/* 二重 for ループ */\nString nestedForLoop(int n) {\n StringBuilder res = new StringBuilder();\n // i = 1, 2, ..., n-1, n のループ\n for (int i = 1; i &lt;= n; i++) {\n // j = 1, 2, ..., n-1, n のループ\n for (int j = 1; j &lt;= n; j++) {\n res.append(\"(\" + i + \", \" + j + \"), \");\n }\n }\n return res.toString();\n}\n</code></pre> iteration.cs<pre><code>/* 二重 for ループ */\nstring NestedForLoop(int n) {\n StringBuilder res = new();\n // i = 1, 2, ..., n-1, n のループ\n for (int i = 1; i &lt;= n; i++) {\n // j = 1, 2, ..., n-1, n のループ\n for (int j = 1; j &lt;= n; j++) {\n res.Append($\"({i}, {j}), \");\n }\n }\n return res.ToString();\n}\n</code></pre> iteration.go<pre><code>/* 二重 for ループ */\nfunc nestedForLoop(n int) string {\n res := \"\"\n // i = 1, 2, ..., n-1, n のループ\n for i := 1; i &lt;= n; i++ {\n for j := 1; j &lt;= n; j++ {\n // j = 1, 2, ..., n-1, n のループ\n res += fmt.Sprintf(\"(%d, %d), \", i, j)\n }\n }\n return res\n}\n</code></pre> iteration.swift<pre><code>/* 二重 for ループ */\nfunc nestedForLoop(n: Int) -&gt; String {\n var res = \"\"\n // i = 1, 2, ..., n-1, n のループ\n for i in 1 ... n {\n // j = 1, 2, ..., n-1, n のループ\n for j in 1 ... n {\n res.append(\"(\\(i), \\(j)), \")\n }\n }\n return res\n}\n</code></pre> iteration.js<pre><code>/* 二重 for ループ */\nfunction nestedForLoop(n) {\n let res = '';\n // i = 1, 2, ..., n-1, n のループ\n for (let i = 1; i &lt;= n; i++) {\n // j = 1, 2, ..., n-1, n のループ\n for (let j = 1; j &lt;= n; j++) {\n res += `(${i}, ${j}), `;\n }\n }\n return res;\n}\n</code></pre> iteration.ts<pre><code>/* 二重 for ループ */\nfunction nestedForLoop(n: number): string {\n let res = '';\n // i = 1, 2, ..., n-1, n のループ\n for (let i = 1; i &lt;= n; i++) {\n // j = 1, 2, ..., n-1, n のループ\n for (let j = 1; j &lt;= n; j++) {\n res += `(${i}, ${j}), `;\n }\n }\n return res;\n}\n</code></pre> iteration.dart<pre><code>/* 二重 for ループ */\nString nestedForLoop(int n) {\n String res = \"\";\n // i = 1, 2, ..., n-1, n のループ\n for (int i = 1; i &lt;= n; i++) {\n // j = 1, 2, ..., n-1, n のループ\n for (int j = 1; j &lt;= n; j++) {\n res += \"($i, $j), \";\n }\n }\n return res;\n}\n</code></pre> iteration.rs<pre><code>/* 二重 for ループ */\nfn nested_for_loop(n: i32) -&gt; String {\n let mut res = vec![];\n // i = 1, 2, ..., n-1, n のループ\n for i in 1..=n {\n // j = 1, 2, ..., n-1, n のループ\n for j in 1..=n {\n res.push(format!(\"({}, {}), \", i, j));\n }\n }\n res.join(\"\")\n}\n</code></pre> iteration.c<pre><code>/* 二重 for ループ */\nchar *nestedForLoop(int n) {\n // n * n は点の個数であり、文字列 \"(i, j), \" の長さは最大 6+10*2 である。さらに末尾のヌル文字 \\0 のための領域を加える\n int size = n * n * 26 + 1;\n char *res = malloc(size * sizeof(char));\n // i = 1, 2, ..., n-1, n のループ\n for (int i = 1; i &lt;= n; i++) {\n // j = 1, 2, ..., n-1, n のループ\n for (int j = 1; j &lt;= n; j++) {\n char tmp[26];\n snprintf(tmp, sizeof(tmp), \"(%d, %d), \", i, j);\n strncat(res, tmp, size - strlen(res) - 1);\n }\n }\n return res;\n}\n</code></pre> iteration.kt<pre><code>/* 二重 for ループ */\nfun nestedForLoop(n: Int): String {\n val res = StringBuilder()\n // i = 1, 2, ..., n-1, n のループ\n for (i in 1..n) {\n // j = 1, 2, ..., n-1, n のループ\n for (j in 1..n) {\n res.append(\" ($i, $j), \")\n }\n }\n return res.toString()\n}\n</code></pre> iteration.rb<pre><code>### 二重 for ループ ###\ndef nested_for_loop(n)\n res = \"\"\n\n # i = 1, 2, ..., n-1, n のループ\n for i in 1..n\n # j = 1, 2, ..., n-1, n のループ\n for j in 1..n\n res += \"(#{i}, #{j}), \"\n end\n end\n\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 2-2 に、この入れ子のループのフローチャートを示します。</p> <p></p> <p> 図 2-2   入れ子のループのフローチャート </p> <p>この場合、操作回数は \\(n^2\\) に比例します。言い換えると、実行時間は入力の大きさ \\(n\\) と「二乗の関係」にあります。</p> <p>さらにループを入れ子にすると、そのたびに「次元が上がり」、時間計算量は「三乗」「四乗」というように増えていきます。</p>","path":["第 2 章   計算量の解析","2.2   反復と再帰"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#222","level":2,"title":"2.2.2   再帰","text":"<p>再帰(recursion)は、関数が自分自身を呼び出して問題を解くアルゴリズム戦略です。主に二つの段階があります。</p> <ol> <li>呼び出しを進める:通常は小さく、または単純になった引数を渡して自分自身を呼び出し続け、「終了条件」に到達するまで深く進みます。</li> <li>戻る:「終了条件」に達したら、最も深い再帰関数から順に戻り、各段階の結果を集めます。</li> </ol> <p>実装の観点では、再帰のコードは主に三つの要素からなります。</p> <ol> <li>終了条件:呼び出しを進める段階から、戻る段階へ切り替える時点を決めます。</li> <li>再帰呼び出し:関数が自分自身を呼び出し、通常は小さく、または単純になった引数を渡します。</li> <li>結果の返却:現在の再帰段階の結果を、一つ上の段階へ返します。</li> </ol> <p>次のコードでは、<code>recur(n)</code> を呼び出すだけで \\(1 + 2 + \\dots + n\\) を計算できます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py<pre><code>def recur(n: int) -&gt; int:\n \"\"\"再帰\"\"\"\n # 終了条件\n if n == 1:\n return 1\n # 往路:再帰呼び出し\n res = recur(n - 1)\n # 復路:結果を返す\n return n + res\n</code></pre> recursion.cpp<pre><code>/* 再帰 */\nint recur(int n) {\n // 終了条件\n if (n == 1)\n return 1;\n // 往路:再帰呼び出し\n int res = recur(n - 1);\n // 復路:結果を返す\n return n + res;\n}\n</code></pre> recursion.java<pre><code>/* 再帰 */\nint recur(int n) {\n // 終了条件\n if (n == 1)\n return 1;\n // 往路:再帰呼び出し\n int res = recur(n - 1);\n // 復路:結果を返す\n return n + res;\n}\n</code></pre> recursion.cs<pre><code>/* 再帰 */\nint Recur(int n) {\n // 終了条件\n if (n == 1)\n return 1;\n // 往路:再帰呼び出し\n int res = Recur(n - 1);\n // 復路:結果を返す\n return n + res;\n}\n</code></pre> recursion.go<pre><code>/* 再帰 */\nfunc recur(n int) int {\n // 終了条件\n if n == 1 {\n return 1\n }\n // 往路:再帰呼び出し\n res := recur(n - 1)\n // 復路:結果を返す\n return n + res\n}\n</code></pre> recursion.swift<pre><code>/* 再帰 */\nfunc recur(n: Int) -&gt; Int {\n // 終了条件\n if n == 1 {\n return 1\n }\n // 往路:再帰呼び出し\n let res = recur(n: n - 1)\n // 復路:結果を返す\n return n + res\n}\n</code></pre> recursion.js<pre><code>/* 再帰 */\nfunction recur(n) {\n // 終了条件\n if (n === 1) return 1;\n // 往路:再帰呼び出し\n const res = recur(n - 1);\n // 復路:結果を返す\n return n + res;\n}\n</code></pre> recursion.ts<pre><code>/* 再帰 */\nfunction recur(n: number): number {\n // 終了条件\n if (n === 1) return 1;\n // 往路:再帰呼び出し\n const res = recur(n - 1);\n // 復路:結果を返す\n return n + res;\n}\n</code></pre> recursion.dart<pre><code>/* 再帰 */\nint recur(int n) {\n // 終了条件\n if (n == 1) return 1;\n // 往路:再帰呼び出し\n int res = recur(n - 1);\n // 復路:結果を返す\n return n + res;\n}\n</code></pre> recursion.rs<pre><code>/* 再帰 */\nfn recur(n: i32) -&gt; i32 {\n // 終了条件\n if n == 1 {\n return 1;\n }\n // 往路:再帰呼び出し\n let res = recur(n - 1);\n // 復路:結果を返す\n n + res\n}\n</code></pre> recursion.c<pre><code>/* 再帰 */\nint recur(int n) {\n // 終了条件\n if (n == 1)\n return 1;\n // 往路:再帰呼び出し\n int res = recur(n - 1);\n // 復路:結果を返す\n return n + res;\n}\n</code></pre> recursion.kt<pre><code>/* 再帰 */\nfun recur(n: Int): Int {\n // 終了条件\n if (n == 1)\n return 1\n // 往路:再帰呼び出し\n val res = recur(n - 1)\n // 復路:結果を返す\n return n + res\n}\n</code></pre> recursion.rb<pre><code>### 再帰 ###\ndef recur(n)\n # 終了条件\n return 1 if n == 1\n # 往路:再帰呼び出し\n res = recur(n - 1)\n # 復路:結果を返す\n n + res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 2-3 に、この関数の再帰の流れを示します。</p> <p></p> <p> 図 2-3   合計を求める関数の再帰の流れ </p> <p>計算結果は反復でも再帰でも同じですが、考え方と問題の解き方はまったく異なります。</p> <ul> <li>反復:「下から上へ」解きます。最も基本的な手順から始め、それを繰り返したり積み重ねたりして、処理を完了します。</li> <li>再帰:「上から下へ」解きます。元の問題を同じ形の小さな部分問題に分解し、さらに小さくしていき、解が既知の基本ケースに達したら止めます。</li> </ul> <p>先ほどの合計を例に、問題を \\(f(n) = 1 + 2 + \\dots + n\\) とします。</p> <ul> <li>反復:\\(1\\) から \\(n\\) までループし、毎回加算することで、\\(f(n)\\) を求めます。</li> <li>再帰:問題を \\(f(n) = n + f(n-1)\\) に分解し、基本ケース \\(f(1) = 1\\) まで再帰的に分解し続けます。</li> </ul>","path":["第 2 章   計算量の解析","2.2   反復と再帰"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#1","level":3,"title":"1.   呼び出しスタック","text":"<p>再帰関数が自分自身を呼ぶたびに、システムは新しい呼び出し用のメモリを確保し、ローカル変数、呼び出しアドレスなどを保存します。これにより、二つの影響が生じます。</p> <ul> <li>関数の実行に必要な情報は「スタックフレーム」と呼ぶメモリ領域に保存され、関数から戻るまで解放されません。そのため、再帰は通常、反復より多くのメモリを使います。</li> <li>再帰呼び出しには追加のコストがかかります。そのため、再帰は通常、ループより時間効率が低くなります。</li> </ul> <p>図 2-4 のように、終了条件に到達する直前には、まだ戻っていない再帰関数が \\(n\\) 個あり、再帰の深さは \\(n\\) です。</p> <p></p> <p> 図 2-4   再帰呼び出しの深さ </p> <p>実際には、言語が許す再帰の深さには通常、制限があります。深すぎる再帰はスタックオーバーフローを引き起こす可能性があります。</p>","path":["第 2 章   計算量の解析","2.2   反復と再帰"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#2","level":3,"title":"2.   末尾再帰","text":"<p>興味深いことに、関数が戻る直前の最後の処理として再帰呼び出しを行う場合、コンパイラやインタプリタは、空間効率が反復と同程度になるよう最適化できます。これを末尾再帰(tail recursion)と呼びます。</p> <ul> <li>通常の再帰:一つ上の関数に戻った後も処理を続けるため、前の呼び出しの実行状態を保存する必要があります。</li> <li>末尾再帰:再帰呼び出しが戻る直前の最後の操作なので、一つ上に戻った後に実行する処理はありません。そのため、前の関数の実行状態を保存する必要がありません。</li> </ul> <p>\\(1 + 2 + \\dots + n\\) の計算では、結果の変数 <code>res</code> を関数の引数にすれば、末尾再帰にできます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py<pre><code>def tail_recur(n, res):\n \"\"\"末尾再帰\"\"\"\n # 終了条件\n if n == 0:\n return res\n # 末尾再帰呼び出し\n return tail_recur(n - 1, res + n)\n</code></pre> recursion.cpp<pre><code>/* 末尾再帰 */\nint tailRecur(int n, int res) {\n // 終了条件\n if (n == 0)\n return res;\n // 末尾再帰呼び出し\n return tailRecur(n - 1, res + n);\n}\n</code></pre> recursion.java<pre><code>/* 末尾再帰 */\nint tailRecur(int n, int res) {\n // 終了条件\n if (n == 0)\n return res;\n // 末尾再帰呼び出し\n return tailRecur(n - 1, res + n);\n}\n</code></pre> recursion.cs<pre><code>/* 末尾再帰 */\nint TailRecur(int n, int res) {\n // 終了条件\n if (n == 0)\n return res;\n // 末尾再帰呼び出し\n return TailRecur(n - 1, res + n);\n}\n</code></pre> recursion.go<pre><code>/* 末尾再帰 */\nfunc tailRecur(n int, res int) int {\n // 終了条件\n if n == 0 {\n return res\n }\n // 末尾再帰呼び出し\n return tailRecur(n-1, res+n)\n}\n</code></pre> recursion.swift<pre><code>/* 末尾再帰 */\nfunc tailRecur(n: Int, res: Int) -&gt; Int {\n // 終了条件\n if n == 0 {\n return res\n }\n // 末尾再帰呼び出し\n return tailRecur(n: n - 1, res: res + n)\n}\n</code></pre> recursion.js<pre><code>/* 末尾再帰 */\nfunction tailRecur(n, res) {\n // 終了条件\n if (n === 0) return res;\n // 末尾再帰呼び出し\n return tailRecur(n - 1, res + n);\n}\n</code></pre> recursion.ts<pre><code>/* 末尾再帰 */\nfunction tailRecur(n: number, res: number): number {\n // 終了条件\n if (n === 0) return res;\n // 末尾再帰呼び出し\n return tailRecur(n - 1, res + n);\n}\n</code></pre> recursion.dart<pre><code>/* 末尾再帰 */\nint tailRecur(int n, int res) {\n // 終了条件\n if (n == 0) return res;\n // 末尾再帰呼び出し\n return tailRecur(n - 1, res + n);\n}\n</code></pre> recursion.rs<pre><code>/* 末尾再帰 */\nfn tail_recur(n: i32, res: i32) -&gt; i32 {\n // 終了条件\n if n == 0 {\n return res;\n }\n // 末尾再帰呼び出し\n tail_recur(n - 1, res + n)\n}\n</code></pre> recursion.c<pre><code>/* 末尾再帰 */\nint tailRecur(int n, int res) {\n // 終了条件\n if (n == 0)\n return res;\n // 末尾再帰呼び出し\n return tailRecur(n - 1, res + n);\n}\n</code></pre> recursion.kt<pre><code>/* 末尾再帰 */\ntailrec fun tailRecur(n: Int, res: Int): Int {\n // tailrec キーワードを追加し、末尾再帰の最適化を有効にする\n // 終了条件\n if (n == 0)\n return res\n // 末尾再帰呼び出し\n return tailRecur(n - 1, res + n)\n}\n</code></pre> recursion.rb<pre><code>### 末尾再帰 ###\ndef tail_recur(n, res)\n # 終了条件\n return res if n == 0\n # 末尾再帰呼び出し\n tail_recur(n - 1, res + n)\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 2-5 に末尾再帰の流れを示します。通常の再帰とは、加算を行うタイミングが異なります。</p> <ul> <li>通常の再帰:戻る段階で加算し、各呼び出しから戻った後に 1 回ずつ加算します。</li> <li>末尾再帰:呼び出しを進める段階で加算し、戻る段階では順に戻るだけです。</li> </ul> <p></p> <p> 図 2-5   末尾再帰の流れ </p> <p>ヒント</p> <p>多くのコンパイラやインタプリタは末尾再帰最適化に対応していないことに注意してください。たとえば Python は標準では対応していないため、末尾再帰の形でもスタックオーバーフローが起こる可能性があります。</p>","path":["第 2 章   計算量の解析","2.2   反復と再帰"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#3_1","level":3,"title":"3.   再帰木","text":"<p>分割統治に関わる問題では、反復より再帰の方が直感的で、読みやすいコードになることがよくあります。「フィボナッチ数列」を例に考えます。</p> <p>問題</p> <p>フィボナッチ数列 \\(0, 1, 1, 2, 3, 5, 8, 13, \\dots\\) の、第 \\(n\\) 番目の数を求めてください。</p> <p>第 \\(n\\) 番目の数を \\(f(n)\\) とすると、二つのことがわかります。</p> <ul> <li>最初の二つの数は、\\(f(1) = 0\\) と \\(f(2) = 1\\) です。</li> <li>各数は直前の二つの数の和であり、\\(f(n) = f(n - 1) + f(n - 2)\\) です。</li> </ul> <p>この漸化式に従って再帰呼び出しを行い、最初の二つの数を終了条件にすれば、再帰のコードを書けます。<code>fib(n)</code> を呼ぶと、第 \\(n\\) 番目の数が得られます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py<pre><code>def fib(n: int) -&gt; int:\n \"\"\"フィボナッチ数列:再帰\"\"\"\n # 終了条件 f(1) = 0, f(2) = 1\n if n == 1 or n == 2:\n return n - 1\n # 再帰呼び出し f(n) = f(n-1) + f(n-2)\n res = fib(n - 1) + fib(n - 2)\n # 結果 f(n) を返す\n return res\n</code></pre> recursion.cpp<pre><code>/* フィボナッチ数列:再帰 */\nint fib(int n) {\n // 終了条件 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // 再帰呼び出し f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // 結果 f(n) を返す\n return res;\n}\n</code></pre> recursion.java<pre><code>/* フィボナッチ数列:再帰 */\nint fib(int n) {\n // 終了条件 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // 再帰呼び出し f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // 結果 f(n) を返す\n return res;\n}\n</code></pre> recursion.cs<pre><code>/* フィボナッチ数列:再帰 */\nint Fib(int n) {\n // 終了条件 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // 再帰呼び出し f(n) = f(n-1) + f(n-2)\n int res = Fib(n - 1) + Fib(n - 2);\n // 結果 f(n) を返す\n return res;\n}\n</code></pre> recursion.go<pre><code>/* フィボナッチ数列:再帰 */\nfunc fib(n int) int {\n // 終了条件 f(1) = 0, f(2) = 1\n if n == 1 || n == 2 {\n return n - 1\n }\n // 再帰呼び出し f(n) = f(n-1) + f(n-2)\n res := fib(n-1) + fib(n-2)\n // 結果 f(n) を返す\n return res\n}\n</code></pre> recursion.swift<pre><code>/* フィボナッチ数列:再帰 */\nfunc fib(n: Int) -&gt; Int {\n // 終了条件 f(1) = 0, f(2) = 1\n if n == 1 || n == 2 {\n return n - 1\n }\n // 再帰呼び出し f(n) = f(n-1) + f(n-2)\n let res = fib(n: n - 1) + fib(n: n - 2)\n // 結果 f(n) を返す\n return res\n}\n</code></pre> recursion.js<pre><code>/* フィボナッチ数列:再帰 */\nfunction fib(n) {\n // 終了条件 f(1) = 0, f(2) = 1\n if (n === 1 || n === 2) return n - 1;\n // 再帰呼び出し f(n) = f(n-1) + f(n-2)\n const res = fib(n - 1) + fib(n - 2);\n // 結果 f(n) を返す\n return res;\n}\n</code></pre> recursion.ts<pre><code>/* フィボナッチ数列:再帰 */\nfunction fib(n: number): number {\n // 終了条件 f(1) = 0, f(2) = 1\n if (n === 1 || n === 2) return n - 1;\n // 再帰呼び出し f(n) = f(n-1) + f(n-2)\n const res = fib(n - 1) + fib(n - 2);\n // 結果 f(n) を返す\n return res;\n}\n</code></pre> recursion.dart<pre><code>/* フィボナッチ数列:再帰 */\nint fib(int n) {\n // 終了条件 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2) return n - 1;\n // 再帰呼び出し f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // 結果 f(n) を返す\n return res;\n}\n</code></pre> recursion.rs<pre><code>/* フィボナッチ数列:再帰 */\nfn fib(n: i32) -&gt; i32 {\n // 終了条件 f(1) = 0, f(2) = 1\n if n == 1 || n == 2 {\n return n - 1;\n }\n // 再帰呼び出し f(n) = f(n-1) + f(n-2)\n let res = fib(n - 1) + fib(n - 2);\n // 結果を返す\n res\n}\n</code></pre> recursion.c<pre><code>/* フィボナッチ数列:再帰 */\nint fib(int n) {\n // 終了条件 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1;\n // 再帰呼び出し f(n) = f(n-1) + f(n-2)\n int res = fib(n - 1) + fib(n - 2);\n // 結果 f(n) を返す\n return res;\n}\n</code></pre> recursion.kt<pre><code>/* フィボナッチ数列:再帰 */\nfun fib(n: Int): Int {\n // 終了条件 f(1) = 0, f(2) = 1\n if (n == 1 || n == 2)\n return n - 1\n // 再帰呼び出し f(n) = f(n-1) + f(n-2)\n val res = fib(n - 1) + fib(n - 2)\n // 結果 f(n) を返す\n return res\n}\n</code></pre> recursion.rb<pre><code>### フィボナッチ数列:再帰 ###\ndef fib(n)\n # 終了条件 f(1) = 0, f(2) = 1\n return n - 1 if n == 1 || n == 2\n # 再帰呼び出し f(n) = f(n-1) + f(n-2)\n res = fib(n - 1) + fib(n - 2)\n # 結果 f(n) を返す\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>このコードでは、関数内で再帰呼び出しを 2 回行い、一つの呼び出しから二つの分岐が生じます。図 2-6 のように呼び出しを繰り返すと、最終的に \\(n\\) 層の再帰木(recursion tree)ができます。</p> <p></p> <p> 図 2-6   フィボナッチ数列の再帰木 </p> <p>本質的に、再帰は「問題を小さな部分問題に分解する」という考え方を表しており、この分割統治戦略は非常に重要です。</p> <ul> <li>アルゴリズムの面では、探索、ソート、バックトラッキング、分割統治、動的計画法など、多くの重要な戦略が直接または間接的にこの考え方を使っています。</li> <li>データ構造の面では、連結リスト、木、グラフの問題は分割統治で分析しやすいため、再帰が自然に適しています。</li> </ul>","path":["第 2 章   計算量の解析","2.2   反復と再帰"],"tags":[]},{"location":"chapter_computational_complexity/iteration_and_recursion/#223","level":2,"title":"2.2.3   反復と再帰の比較","text":"<p>ここまでの内容をまとめると、表 2-1 のように実装、性能、用途に違いがあります。</p> <p> 表 2-1   反復と再帰の特徴の比較 </p> 反復 再帰 実装方法 ループ構造 関数が自分自身を呼び出す 時間効率 通常は高く、関数呼び出しのコストがない 各関数呼び出しにコストがかかる メモリ使用量 通常は一定量のメモリを使う 呼び出しの積み重ねで大量のスタックフレームを使う場合がある 適した問題 単純な繰り返し処理に適し、コードが直感的で読みやすい 木、グラフ、分割統治、バックトラッキングなど、部分問題への分解に適し、コード構造が簡潔で明快 <p>ヒント</p> <p>以下の内容が難しく感じられたら、「スタック」の章を読んでから復習してください。</p> <p>では、反復と再帰にはどのような関係があるのでしょうか。先ほどの再帰関数では、戻る段階で加算します。つまり、最初に呼ばれた関数が、最後に加算を終えます。この仕組みは、スタックの「先入れ後出し」の原則と共通しています。</p> <p>実際、「呼び出しスタック」や「スタックフレーム」という用語自体が、再帰とスタックの密接な関係を示しています。</p> <ol> <li>呼び出しを進める:関数を呼ぶと、システムは「呼び出しスタック」に新しいフレームを確保し、ローカル変数、引数、戻り先アドレスなどを保存します。</li> <li>戻る:関数の実行が終わって戻ると、対応するフレームを「呼び出しスタック」から取り除き、前の関数の実行環境を復元します。</li> </ol> <p>したがって、明示的なスタックで呼び出しスタックの動作を模倣すれば、再帰を反復に変換できます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby recursion.py<pre><code>def for_loop_recur(n: int) -&gt; int:\n \"\"\"反復を使って再帰を再現する\"\"\"\n # 明示的なスタックを使って、システムの呼び出しスタックを再現する\n stack = []\n res = 0\n # 往路:再帰呼び出し\n for i in range(n, 0, -1):\n # 「プッシュ操作」で再帰の「往路」を再現する\n stack.append(i)\n # 復路:結果を返す\n while stack:\n # 「ポップ操作」で再帰の「復路」を再現する\n res += stack.pop()\n # res = 1+2+3+...+n\n return res\n</code></pre> recursion.cpp<pre><code>/* 反復を使って再帰を再現する */\nint forLoopRecur(int n) {\n // 明示的なスタックを使って、システムの呼び出しスタックを再現する\n stack&lt;int&gt; stack;\n int res = 0;\n // 往路:再帰呼び出し\n for (int i = n; i &gt; 0; i--) {\n // 「プッシュ操作」で再帰の「往路」を再現する\n stack.push(i);\n }\n // 復路:結果を返す\n while (!stack.empty()) {\n // 「ポップ操作」で再帰の「復路」を再現する\n res += stack.top();\n stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n</code></pre> recursion.java<pre><code>/* 反復を使って再帰を再現する */\nint forLoopRecur(int n) {\n // 明示的なスタックを使って、システムの呼び出しスタックを再現する\n Stack&lt;Integer&gt; stack = new Stack&lt;&gt;();\n int res = 0;\n // 往路:再帰呼び出し\n for (int i = n; i &gt; 0; i--) {\n // 「プッシュ操作」で再帰の「往路」を再現する\n stack.push(i);\n }\n // 復路:結果を返す\n while (!stack.isEmpty()) {\n // 「ポップ操作」で再帰の「復路」を再現する\n res += stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n</code></pre> recursion.cs<pre><code>/* 反復を使って再帰を再現する */\nint ForLoopRecur(int n) {\n // 明示的なスタックを使って、システムの呼び出しスタックを再現する\n Stack&lt;int&gt; stack = new();\n int res = 0;\n // 往路:再帰呼び出し\n for (int i = n; i &gt; 0; i--) {\n // 「プッシュ操作」で再帰の「往路」を再現する\n stack.Push(i);\n }\n // 復路:結果を返す\n while (stack.Count &gt; 0) {\n // 「ポップ操作」で再帰の「復路」を再現する\n res += stack.Pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n</code></pre> recursion.go<pre><code>/* 反復を使って再帰を再現する */\nfunc forLoopRecur(n int) int {\n // 明示的なスタックを使って、システムの呼び出しスタックを再現する\n stack := list.New()\n res := 0\n // 往路:再帰呼び出し\n for i := n; i &gt; 0; i-- {\n // 「プッシュ操作」で再帰の「往路」を再現する\n stack.PushBack(i)\n }\n // 復路:結果を返す\n for stack.Len() != 0 {\n // 「ポップ操作」で再帰の「復路」を再現する\n res += stack.Back().Value.(int)\n stack.Remove(stack.Back())\n }\n // res = 1+2+3+...+n\n return res\n}\n</code></pre> recursion.swift<pre><code>/* 反復を使って再帰を再現する */\nfunc forLoopRecur(n: Int) -&gt; Int {\n // 明示的なスタックを使って、システムの呼び出しスタックを再現する\n var stack: [Int] = []\n var res = 0\n // 往路:再帰呼び出し\n for i in (1 ... n).reversed() {\n // 「プッシュ操作」で再帰の「往路」を再現する\n stack.append(i)\n }\n // 復路:結果を返す\n while !stack.isEmpty {\n // 「ポップ操作」で再帰の「復路」を再現する\n res += stack.removeLast()\n }\n // res = 1+2+3+...+n\n return res\n}\n</code></pre> recursion.js<pre><code>/* 反復を使って再帰を再現する */\nfunction forLoopRecur(n) {\n // 明示的なスタックを使って、システムの呼び出しスタックを再現する\n const stack = [];\n let res = 0;\n // 往路:再帰呼び出し\n for (let i = n; i &gt; 0; i--) {\n // 「プッシュ操作」で再帰の「往路」を再現する\n stack.push(i);\n }\n // 復路:結果を返す\n while (stack.length) {\n // 「ポップ操作」で再帰の「復路」を再現する\n res += stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n</code></pre> recursion.ts<pre><code>/* 反復を使って再帰を再現する */\nfunction forLoopRecur(n: number): number {\n // 明示的なスタックを使って、システムの呼び出しスタックを再現する \n const stack: number[] = [];\n let res: number = 0;\n // 往路:再帰呼び出し\n for (let i = n; i &gt; 0; i--) {\n // 「プッシュ操作」で再帰の「往路」を再現する\n stack.push(i);\n }\n // 復路:結果を返す\n while (stack.length) {\n // 「ポップ操作」で再帰の「復路」を再現する\n res += stack.pop();\n }\n // res = 1+2+3+...+n\n return res;\n}\n</code></pre> recursion.dart<pre><code>/* 反復を使って再帰を再現する */\nint forLoopRecur(int n) {\n // 明示的なスタックを使って、システムの呼び出しスタックを再現する\n List&lt;int&gt; stack = [];\n int res = 0;\n // 往路:再帰呼び出し\n for (int i = n; i &gt; 0; i--) {\n // 「プッシュ操作」で再帰の「往路」を再現する\n stack.add(i);\n }\n // 復路:結果を返す\n while (!stack.isEmpty) {\n // 「ポップ操作」で再帰の「復路」を再現する\n res += stack.removeLast();\n }\n // res = 1+2+3+...+n\n return res;\n}\n</code></pre> recursion.rs<pre><code>/* 反復を使って再帰を再現する */\nfn for_loop_recur(n: i32) -&gt; i32 {\n // 明示的なスタックを使って、システムの呼び出しスタックを再現する\n let mut stack = Vec::new();\n let mut res = 0;\n // 往路:再帰呼び出し\n for i in (1..=n).rev() {\n // 「プッシュ操作」で再帰の「往路」を再現する\n stack.push(i);\n }\n // 復路:結果を返す\n while !stack.is_empty() {\n // 「ポップ操作」で再帰の「復路」を再現する\n res += stack.pop().unwrap();\n }\n // res = 1+2+3+...+n\n res\n}\n</code></pre> recursion.c<pre><code>/* 反復を使って再帰を再現する */\nint forLoopRecur(int n) {\n int stack[1000]; // 大きな配列を使ってスタックを再現する\n int top = -1; // スタックの先頭のインデックス\n int res = 0;\n // 往路:再帰呼び出し\n for (int i = n; i &gt; 0; i--) {\n // 「プッシュ操作」で再帰の「往路」を再現する\n stack[1 + top++] = i;\n }\n // 復路:結果を返す\n while (top &gt;= 0) {\n // 「ポップ操作」で再帰の「復路」を再現する\n res += stack[top--];\n }\n // res = 1+2+3+...+n\n return res;\n}\n</code></pre> recursion.kt<pre><code>/* 反復を使って再帰を再現する */\nfun forLoopRecur(n: Int): Int {\n // 明示的なスタックを使って、システムの呼び出しスタックを再現する\n val stack = Stack&lt;Int&gt;()\n var res = 0\n // 往路:再帰呼び出し\n for (i in n downTo 0) {\n // 「プッシュ操作」で再帰の「往路」を再現する\n stack.push(i)\n }\n // 復路:結果を返す\n while (stack.isNotEmpty()) {\n // 「ポップ操作」で再帰の「復路」を再現する\n res += stack.pop()\n }\n // res = 1+2+3+...+n\n return res\n}\n</code></pre> recursion.rb<pre><code>### 反復を使って再帰を再現する ###\ndef for_loop_recur(n)\n # 明示的なスタックを使って、システムの呼び出しスタックを再現する\n stack = []\n res = 0\n\n # 往路:再帰呼び出し\n for i in n.downto(0)\n # 「プッシュ操作」で再帰の「往路」を再現する\n stack &lt;&lt; i\n end\n # 復路:結果を返す\n while !stack.empty?\n res += stack.pop\n end\n\n # res = 1+2+3+...+n\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>このコードでは、反復に変換したことで複雑になっています。反復と再帰は多くの場合、相互に変換できますが、必ずしもそうする価値があるとは限りません。理由は二つあります。</p> <ul> <li>変換後のコードの方が理解しにくく、可読性が下がる可能性があります。</li> <li>複雑な問題では、システムの呼び出しスタックの動作を模倣すること自体が非常に難しい場合があります。</li> </ul> <p>反復と再帰のどちらを選ぶかは、問題の性質によって決まります。実際のプログラミングでは、両者の利点と欠点を比較し、状況に合った方法を選ぶことが大切です。</p>","path":["第 2 章   計算量の解析","2.2   反復と再帰"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/","level":1,"title":"2.1   アルゴリズムの効率の評価","text":"<p>アルゴリズムの設計では、次の二つの目標を順に目指します。</p> <ol> <li>解法を見つける:指定された入力範囲で、問題の正しい解を確実に求められる必要があります。</li> <li>最良の解法を探す:同じ問題に複数の解法がある場合、できるだけ効率的なアルゴリズムを見つけたいと考えます。</li> </ol> <p>つまり、問題を解けることを前提に、その効率がアルゴリズムの良し悪しを測る主な基準になります。効率には次の二つの側面があります。</p> <ul> <li>時間効率:アルゴリズムの実行時間の長さです。</li> <li>空間効率:アルゴリズムが使うメモリ空間の大きさです。</li> </ul> <p>目標は、「速くて省メモリ」なデータ構造とアルゴリズムを設計することです。そのためには、効率を適切に評価することが欠かせません。評価できて初めて、アルゴリズム同士を比較し、設計や最適化の指針を得られるからです。</p> <p>効率の評価方法は、主に実測と理論的な見積もりの二つに分けられます。</p>","path":["第 2 章   計算量の解析","2.1   アルゴリズムの効率の評価"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/#211","level":2,"title":"2.1.1   実測","text":"<p>同じ問題を解けるアルゴリズム <code>A</code> と <code>B</code> の効率を比べたいとします。最も直接的なのは、コンピュータで両方を実行し、実行時間とメモリ使用量を計測・記録する方法です。実態を反映できますが、大きな制約もあります。</p> <p>一つは、測定環境の影響を取り除きにくいことです。性能はハードウェア構成に左右されます。並列性が高いアルゴリズムならマルチコア CPU に適し、メモリ操作が多いアルゴリズムなら高性能なメモリでよい結果を得られます。つまり、マシンが違えば測定結果も異なる可能性があります。さまざまなマシンで測定して平均的な効率を求める必要がありますが、それは現実的ではありません。</p> <p>もう一つは、十分な測定には大量の資源が必要なことです。入力データ量によって効率は変わります。データが少なければ <code>A</code> の方が <code>B</code> より速くても、多ければ結果が逆転するかもしれません。説得力のある結論には、さまざまな規模の入力を試す必要があり、大量の計算資源を使います。</p>","path":["第 2 章   計算量の解析","2.1   アルゴリズムの効率の評価"],"tags":[]},{"location":"chapter_computational_complexity/performance_evaluation/#212","level":2,"title":"2.1.2   理論的な見積もり","text":"<p>実測には大きな制約があるため、計算だけで効率を評価することを考えます。この見積もり方法を漸近計算量解析(asymptotic complexity analysis)、略して計算量の解析と呼びます。</p> <p>計算量の解析は、実行に必要な時間・空間資源と入力データの大きさの関係を表します。入力データが大きくなるにつれ、必要な時間や空間がどのように増えるかを記述するものです。少し抽象的な定義なので、三つに分けて理解しましょう。</p> <ul> <li>「時間・空間資源」は、それぞれ時間計算量(time complexity)と空間計算量(space complexity)に対応します。</li> <li>「入力データが大きくなるにつれ」とは、実行効率と入力データの大きさの関係を表すという意味です。</li> <li>「時間や空間がどのように増えるか」とは、具体的な実行時間や使用量ではなく、増加の「速さ」に注目するという意味です。</li> </ul> <p>計算量の解析は、次の点で実測の欠点を補えます。</p> <ul> <li>実際にコードを動かす必要がなく、省エネルギーです。</li> <li>測定環境に依存せず、結果をすべての実行環境に適用できます。</li> <li>異なるデータ量での効率、特に大量のデータに対する性能を示せます。</li> </ul> <p>ヒント</p> <p>まだ計算量の概念がわかりにくくても、心配はいりません。後の節で詳しく説明します。</p> <p>計算量の解析は効率を評価する「物差し」です。必要な時間・空間資源を見積もり、異なるアルゴリズムの効率を比較できます。</p> <p>計算量は数学的な概念なので、初学者には抽象的で、やや難しいかもしれません。その意味では、最初に説明する内容として適していない面もあります。しかし、データ構造やアルゴリズムの特徴を論じる際は、実行速度や空間使用量の分析を避けられません。</p> <p>そこで、データ構造とアルゴリズムを深く学ぶ前に、計算量の解析をひとまず理解し、簡単なアルゴリズムの計算量を分析できるようにしておくことをおすすめします。</p>","path":["第 2 章   計算量の解析","2.1   アルゴリズムの効率の評価"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/","level":1,"title":"2.4   空間計算量","text":"<p>空間計算量(space complexity)は、データ量が増えるにつれて、アルゴリズムのメモリ使用量がどのように増えるかを測ります。時間計算量とよく似ており、「実行時間」を「メモリ使用量」に置き換えた概念です。</p>","path":["第 2 章   計算量の解析","2.4   空間計算量"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#241","level":2,"title":"2.4.1   アルゴリズムに関わる空間","text":"<p>実行中に使うメモリ空間は、主に次の種類に分かれます。</p> <ul> <li>入力空間:入力データを保存します。</li> <li>一時空間:実行中の変数、オブジェクト、関数の実行状態などを保存します。</li> <li>出力空間:出力データを保存します。</li> </ul> <p>通常、空間計算量には「一時空間」と「出力空間」を含めます。</p> <p>一時空間は、さらに三つに分けられます。</p> <ul> <li>一時データ:実行中の定数、変数、オブジェクトなどを保存します。</li> <li>スタックフレーム:呼び出した関数の実行状態を保存します。関数を呼ぶたびにスタックの先頭にフレームが作られ、戻ると解放されます。</li> <li>命令空間:コンパイルされた命令を保存します。実際の集計では通常、無視します。</li> </ul> <p>プログラムの空間計算量では、図 2-15 のように、通常は一時データ、スタックフレーム、出力データの三つを数えます。</p> <p></p> <p> 図 2-15   アルゴリズムが使う空間 </p> <p>関連するコードを次に示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code>class Node:\n \"\"\"クラス\"\"\"\n def __init__(self, x: int):\n self.val: int = x # ノードの値\n self.next: Node | None = None # 次のノードへの参照\n\ndef function() -&gt; int:\n \"\"\"関数\"\"\"\n # 何らかの処理を実行...\n return 0\n\ndef algorithm(n) -&gt; int: # 入力データ\n A = 0 # 一時データ(定数、通常は大文字で表す)\n b = 0 # 一時データ(変数)\n node = Node(0) # 一時データ(オブジェクト)\n c = function() # スタックフレーム(関数呼び出し)\n return A + b + c # 出力データ\n</code></pre> <pre><code>/* 構造体 */\nstruct Node {\n int val;\n Node *next;\n Node(int x) : val(x), next(nullptr) {}\n};\n\n/* 関数 */\nint func() {\n // 何らかの処理を実行...\n return 0;\n}\n\nint algorithm(int n) { // 入力データ\n const int a = 0; // 一時データ(定数)\n int b = 0; // 一時データ(変数)\n Node* node = new Node(0); // 一時データ(オブジェクト)\n int c = func(); // スタックフレーム(関数呼び出し)\n return a + b + c; // 出力データ\n}\n</code></pre> <pre><code>/* クラス */\nclass Node {\n int val;\n Node next;\n Node(int x) { val = x; }\n}\n\n/* 関数 */\nint function() {\n // 何らかの処理を実行...\n return 0;\n}\n\nint algorithm(int n) { // 入力データ\n final int a = 0; // 一時データ(定数)\n int b = 0; // 一時データ(変数)\n Node node = new Node(0); // 一時データ(オブジェクト)\n int c = function(); // スタックフレーム(関数呼び出し)\n return a + b + c; // 出力データ\n}\n</code></pre> <pre><code>/* クラス */\nclass Node(int x) {\n int val = x;\n Node next;\n}\n\n/* 関数 */\nint Function() {\n // 何らかの処理を実行...\n return 0;\n}\n\nint Algorithm(int n) { // 入力データ\n const int a = 0; // 一時データ(定数)\n int b = 0; // 一時データ(変数)\n Node node = new(0); // 一時データ(オブジェクト)\n int c = Function(); // スタックフレーム(関数呼び出し)\n return a + b + c; // 出力データ\n}\n</code></pre> <pre><code>/* 構造体 */\ntype node struct {\n val int\n next *node\n}\n\n/* node 構造体を作成 */\nfunc newNode(val int) *node {\n return &amp;node{val: val}\n}\n\n/* 関数 */\nfunc function() int {\n // 何らかの処理を実行...\n return 0\n}\n\nfunc algorithm(n int) int { // 入力データ\n const a = 0 // 一時データ(定数)\n b := 0 // 一時データ(変数)\n newNode(0) // 一時データ(オブジェクト)\n c := function() // スタックフレーム(関数呼び出し)\n return a + b + c // 出力データ\n}\n</code></pre> <pre><code>/* クラス */\nclass Node {\n var val: Int\n var next: Node?\n\n init(x: Int) {\n val = x\n }\n}\n\n/* 関数 */\nfunc function() -&gt; Int {\n // 何らかの処理を実行...\n return 0\n}\n\nfunc algorithm(n: Int) -&gt; Int { // 入力データ\n let a = 0 // 一時データ(定数)\n var b = 0 // 一時データ(変数)\n let node = Node(x: 0) // 一時データ(オブジェクト)\n let c = function() // スタックフレーム(関数呼び出し)\n return a + b + c // 出力データ\n}\n</code></pre> <pre><code>/* クラス */\nclass Node {\n val;\n next;\n constructor(val) {\n this.val = val === undefined ? 0 : val; // ノードの値\n this.next = null; // 次のノードへの参照\n }\n}\n\n/* 関数 */\nfunction constFunc() {\n // 何らかの処理を実行\n return 0;\n}\n\nfunction algorithm(n) { // 入力データ\n const a = 0; // 一時データ(定数)\n let b = 0; // 一時データ(変数)\n const node = new Node(0); // 一時データ(オブジェクト)\n const c = constFunc(); // スタックフレーム(関数呼び出し)\n return a + b + c; // 出力データ\n}\n</code></pre> <pre><code>/* クラス */\nclass Node {\n val: number;\n next: Node | null;\n constructor(val?: number) {\n this.val = val === undefined ? 0 : val; // ノードの値\n this.next = null; // 次のノードへの参照\n }\n}\n\n/* 関数 */\nfunction constFunc(): number {\n // 何らかの処理を実行\n return 0;\n}\n\nfunction algorithm(n: number): number { // 入力データ\n const a = 0; // 一時データ(定数)\n let b = 0; // 一時データ(変数)\n const node = new Node(0); // 一時データ(オブジェクト)\n const c = constFunc(); // スタックフレーム(関数呼び出し)\n return a + b + c; // 出力データ\n}\n</code></pre> <pre><code>/* クラス */\nclass Node {\n int val;\n Node next;\n Node(this.val, [this.next]);\n}\n\n/* 関数 */\nint function() {\n // 何らかの処理を実行...\n return 0;\n}\n\nint algorithm(int n) { // 入力データ\n const int a = 0; // 一時データ(定数)\n int b = 0; // 一時データ(変数)\n Node node = Node(0); // 一時データ(オブジェクト)\n int c = function(); // スタックフレーム(関数呼び出し)\n return a + b + c; // 出力データ\n}\n</code></pre> <pre><code>use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* 構造体 */\nstruct Node {\n val: i32,\n next: Option&lt;Rc&lt;RefCell&lt;Node&gt;&gt;&gt;,\n}\n\n/* Node 構造体を作成 */\nimpl Node {\n fn new(val: i32) -&gt; Self {\n Self { val: val, next: None }\n }\n}\n\n/* 関数 */\nfn function() -&gt; i32 { \n // 何らかの処理を実行...\n return 0;\n}\n\nfn algorithm(n: i32) -&gt; i32 { // 入力データ\n const a: i32 = 0; // 一時データ(定数)\n let mut b = 0; // 一時データ(変数)\n let node = Node::new(0); // 一時データ(オブジェクト)\n let c = function(); // スタックフレーム(関数呼び出し)\n return a + b + c; // 出力データ\n}\n</code></pre> <pre><code>/* 関数 */\nint func() {\n // 何らかの処理を実行...\n return 0;\n}\n\nint algorithm(int n) { // 入力データ\n const int a = 0; // 一時データ(定数)\n int b = 0; // 一時データ(変数)\n int c = func(); // スタックフレーム(関数呼び出し)\n return a + b + c; // 出力データ\n}\n</code></pre> <pre><code>/* クラス */\nclass Node(var _val: Int) {\n var next: Node? = null\n}\n\n/* 関数 */\nfun function(): Int {\n // 何らかの処理を実行...\n return 0\n}\n\nfun algorithm(n: Int): Int { // 入力データ\n val a = 0 // 一時データ(定数)\n var b = 0 // 一時データ(変数)\n val node = Node(0) // 一時データ(オブジェクト)\n val c = function() // スタックフレーム(関数呼び出し)\n return a + b + c // 出力データ\n}\n</code></pre> <pre><code>### クラス ###\nclass Node\n attr_accessor :val # ノードの値\n attr_accessor :next # 次のノードへの参照\n\n def initialize(x)\n @val = x\n end\nend\n\n### 関数 ###\ndef function\n # 何らかの処理を実行...\n 0\nend\n\n### アルゴリズム ###\ndef algorithm(n) # 入力データ\n a = 0 # 一時データ(定数)\n b = 0 # 一時データ(変数)\n node = Node.new(0) # 一時データ(オブジェクト)\n c = function # スタックフレーム(関数呼び出し)\n a + b + c # 出力データ\nend\n</code></pre>","path":["第 2 章   計算量の解析","2.4   空間計算量"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#242","level":2,"title":"2.4.2   見積もり方","text":"<p>空間計算量の見積もり方は時間計算量とほぼ同じで、数える対象を「操作回数」から「使う空間の大きさ」に変えます。</p> <p>ただし時間計算量と異なり、通常は最悪空間計算量だけに注目します。メモリ容量は必ず満たすべき要件であり、どの入力でも十分なメモリを確保できるようにする必要があるためです。</p> <p>次のコードでは、「最悪」には二つの意味があります。</p> <ol> <li>最悪の入力を基準にする:\\(n &lt; 10\\) なら \\(O(1)\\) ですが、\\(n &gt; 10\\) なら配列 <code>nums</code> が \\(O(n)\\) の空間を使うため、最悪空間計算量は \\(O(n)\\) です。</li> <li>実行中の最大メモリ使用量を基準にする:たとえば、最後の行の実行前は \\(O(1)\\) ですが、配列 <code>nums</code> を初期化すると \\(O(n)\\) の空間を使うため、最悪空間計算量は \\(O(n)\\) です。</li> </ol> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code>def algorithm(n: int):\n a = 0 # O(1)\n b = [0] * 10000 # O(1)\n if n &gt; 10:\n nums = [0] * n # O(n)\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 0; // O(1)\n vector&lt;int&gt; b(10000); // O(1)\n if (n &gt; 10)\n vector&lt;int&gt; nums(n); // O(n)\n}\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 0; // O(1)\n int[] b = new int[10000]; // O(1)\n if (n &gt; 10)\n int[] nums = new int[n]; // O(n)\n}\n</code></pre> <pre><code>void Algorithm(int n) {\n int a = 0; // O(1)\n int[] b = new int[10000]; // O(1)\n if (n &gt; 10) {\n int[] nums = new int[n]; // O(n)\n }\n}\n</code></pre> <pre><code>func algorithm(n int) {\n a := 0 // O(1)\n b := make([]int, 10000) // O(1)\n var nums []int\n if n &gt; 10 {\n nums := make([]int, n) // O(n)\n }\n fmt.Println(a, b, nums)\n}\n</code></pre> <pre><code>func algorithm(n: Int) {\n let a = 0 // O(1)\n let b = Array(repeating: 0, count: 10000) // O(1)\n if n &gt; 10 {\n let nums = Array(repeating: 0, count: n) // O(n)\n }\n}\n</code></pre> <pre><code>function algorithm(n) {\n const a = 0; // O(1)\n const b = new Array(10000); // O(1)\n if (n &gt; 10) {\n const nums = new Array(n); // O(n)\n }\n}\n</code></pre> <pre><code>function algorithm(n: number): void {\n const a = 0; // O(1)\n const b = new Array(10000); // O(1)\n if (n &gt; 10) {\n const nums = new Array(n); // O(n)\n }\n}\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 0; // O(1)\n List&lt;int&gt; b = List.filled(10000, 0); // O(1)\n if (n &gt; 10) {\n List&lt;int&gt; nums = List.filled(n, 0); // O(n)\n }\n}\n</code></pre> <pre><code>fn algorithm(n: i32) {\n let a = 0; // O(1)\n let b = [0; 10000]; // O(1)\n if n &gt; 10 {\n let nums = vec![0; n as usize]; // O(n)\n }\n}\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 0; // O(1)\n int b[10000]; // O(1)\n if (n &gt; 10)\n int nums[n] = {0}; // O(n)\n}\n</code></pre> <pre><code>fun algorithm(n: Int) {\n val a = 0 // O(1)\n val b = IntArray(10000) // O(1)\n if (n &gt; 10) {\n val nums = IntArray(n) // O(n)\n }\n}\n</code></pre> <pre><code>def algorithm(n)\n a = 0 # O(1)\n b = Array.new(10000) # O(1)\n nums = Array.new(n) if n &gt; 10 # O(n)\nend\n</code></pre> <p>再帰関数では、スタックフレームを数え忘れないよう注意が必要です。次のコードを見てください。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code>def function() -&gt; int:\n # 何らかの処理を実行\n return 0\n\ndef loop(n: int):\n \"\"\"ループの空間計算量は O(1)\"\"\"\n for _ in range(n):\n function()\n\ndef recur(n: int):\n \"\"\"再帰の空間計算量は O(n)\"\"\"\n if n == 1:\n return\n return recur(n - 1)\n</code></pre> <pre><code>int func() {\n // 何らかの処理を実行\n return 0;\n}\n/* ループの空間計算量は O(1) */\nvoid loop(int n) {\n for (int i = 0; i &lt; n; i++) {\n func();\n }\n}\n/* 再帰の空間計算量は O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n recur(n - 1);\n}\n</code></pre> <pre><code>int function() {\n // 何らかの処理を実行\n return 0;\n}\n/* ループの空間計算量は O(1) */\nvoid loop(int n) {\n for (int i = 0; i &lt; n; i++) {\n function();\n }\n}\n/* 再帰の空間計算量は O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n recur(n - 1);\n}\n</code></pre> <pre><code>int Function() {\n // 何らかの処理を実行\n return 0;\n}\n/* ループの空間計算量は O(1) */\nvoid Loop(int n) {\n for (int i = 0; i &lt; n; i++) {\n Function();\n }\n}\n/* 再帰の空間計算量は O(n) */\nint Recur(int n) {\n if (n == 1) return 1;\n return Recur(n - 1);\n}\n</code></pre> <pre><code>func function() int {\n // 何らかの処理を実行\n return 0\n}\n\n/* ループの空間計算量は O(1) */\nfunc loop(n int) {\n for i := 0; i &lt; n; i++ {\n function()\n }\n}\n\n/* 再帰の空間計算量は O(n) */\nfunc recur(n int) {\n if n == 1 {\n return\n }\n recur(n - 1)\n}\n</code></pre> <pre><code>@discardableResult\nfunc function() -&gt; Int {\n // 何らかの処理を実行\n return 0\n}\n\n/* ループの空間計算量は O(1) */\nfunc loop(n: Int) {\n for _ in 0 ..&lt; n {\n function()\n }\n}\n\n/* 再帰の空間計算量は O(n) */\nfunc recur(n: Int) {\n if n == 1 {\n return\n }\n recur(n: n - 1)\n}\n</code></pre> <pre><code>function constFunc() {\n // 何らかの処理を実行\n return 0;\n}\n/* ループの空間計算量は O(1) */\nfunction loop(n) {\n for (let i = 0; i &lt; n; i++) {\n constFunc();\n }\n}\n/* 再帰の空間計算量は O(n) */\nfunction recur(n) {\n if (n === 1) return;\n return recur(n - 1);\n}\n</code></pre> <pre><code>function constFunc(): number {\n // 何らかの処理を実行\n return 0;\n}\n/* ループの空間計算量は O(1) */\nfunction loop(n: number): void {\n for (let i = 0; i &lt; n; i++) {\n constFunc();\n }\n}\n/* 再帰の空間計算量は O(n) */\nfunction recur(n: number): void {\n if (n === 1) return;\n return recur(n - 1);\n}\n</code></pre> <pre><code>int function() {\n // 何らかの処理を実行\n return 0;\n}\n/* ループの空間計算量は O(1) */\nvoid loop(int n) {\n for (int i = 0; i &lt; n; i++) {\n function();\n }\n}\n/* 再帰の空間計算量は O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n recur(n - 1);\n}\n</code></pre> <pre><code>fn function() -&gt; i32 {\n // 何らかの処理を実行\n return 0;\n}\n/* ループの空間計算量は O(1) */\nfn loop(n: i32) {\n for i in 0..n {\n function();\n }\n}\n/* 再帰の空間計算量は O(n) */\nfn recur(n: i32) {\n if n == 1 {\n return;\n }\n recur(n - 1);\n}\n</code></pre> <pre><code>int func() {\n // 何らかの処理を実行\n return 0;\n}\n/* ループの空間計算量は O(1) */\nvoid loop(int n) {\n for (int i = 0; i &lt; n; i++) {\n func();\n }\n}\n/* 再帰の空間計算量は O(n) */\nvoid recur(int n) {\n if (n == 1) return;\n recur(n - 1);\n}\n</code></pre> <pre><code>fun function(): Int {\n // 何らかの処理を実行\n return 0\n}\n/* ループの空間計算量は O(1) */\nfun loop(n: Int) {\n for (i in 0..&lt;n) {\n function()\n }\n}\n/* 再帰の空間計算量は O(n) */\nfun recur(n: Int) {\n if (n == 1) return\n return recur(n - 1)\n}\n</code></pre> <pre><code>def function\n # 何らかの処理を実行\n 0\nend\n\n### ループの空間計算量は O(1) ###\ndef loop(n)\n (0...n).each { function }\nend\n\n### 再帰の空間計算量は O(n) ###\ndef recur(n)\n return if n == 1\n recur(n - 1)\nend\n</code></pre> <p>関数 <code>loop()</code> と <code>recur()</code> の時間計算量はどちらも \\(O(n)\\) ですが、空間計算量は異なります。</p> <ul> <li><code>loop()</code> は <code>function()</code> を \\(n\\) 回呼びますが、毎回戻ってスタックフレームを解放するため、空間計算量は \\(O(1)\\) のままです。</li> <li>再帰関数 <code>recur()</code> では、戻っていない <code>recur()</code> が同時に \\(n\\) 個存在するため、スタックフレームに \\(O(n)\\) の空間を使います。</li> </ul>","path":["第 2 章   計算量の解析","2.4   空間計算量"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#243","level":2,"title":"2.4.3   代表的な種類","text":"<p>入力の大きさを \\(n\\) とし、代表的な空間計算量を小さい順に図 2-16 に示します。</p> \\[ \\begin{aligned} &amp; O(1) &lt; O(\\log n) &lt; O(n) &lt; O(n^2) &lt; O(2^n) \\newline &amp; \\text{定数} &lt; \\text{対数} &lt; \\text{線形} &lt; \\text{二乗} &lt; \\text{指数} \\end{aligned} \\] <p></p> <p> 図 2-16   代表的な空間計算量 </p>","path":["第 2 章   計算量の解析","2.4   空間計算量"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#1-o1","level":3,"title":"1.   定数のオーダー \\(O(1)\\)","text":"<p>定数のオーダーは、入力の大きさ \\(n\\) によって個数が変わらない定数、変数、オブジェクトによく見られます。</p> <p>ループ内の変数の初期化や関数呼び出しで使ったメモリは、次の反復に入ると解放され、累積しないため、空間計算量は \\(O(1)\\) のままです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py<pre><code>def function() -&gt; int:\n \"\"\"関数\"\"\"\n # 何らかの処理を実行する\n return 0\n\ndef constant(n: int):\n \"\"\"定数オーダー\"\"\"\n # 定数、変数、オブジェクトは O(1) の領域を使用する\n a = 0\n nums = [0] * 10000\n node = ListNode(0)\n # ループ内の変数は O(1) の領域を使用する\n for _ in range(n):\n c = 0\n # ループ内の関数は O(1) の領域を使用する\n for _ in range(n):\n function()\n</code></pre> space_complexity.cpp<pre><code>/* 関数 */\nint func() {\n // 何らかの処理を実行する\n return 0;\n}\n\n/* 定数オーダー */\nvoid constant(int n) {\n // 定数、変数、オブジェクトは O(1) の領域を使用する\n const int a = 0;\n int b = 0;\n vector&lt;int&gt; nums(10000);\n ListNode node(0);\n // ループ内の変数は O(1) の領域を使用する\n for (int i = 0; i &lt; n; i++) {\n int c = 0;\n }\n // ループ内の関数は O(1) の領域を使用する\n for (int i = 0; i &lt; n; i++) {\n func();\n }\n}\n</code></pre> space_complexity.java<pre><code>/* 関数 */\nint function() {\n // 何らかの処理を実行する\n return 0;\n}\n\n/* 定数オーダー */\nvoid constant(int n) {\n // 定数、変数、オブジェクトは O(1) の領域を使用する\n final int a = 0;\n int b = 0;\n int[] nums = new int[10000];\n ListNode node = new ListNode(0);\n // ループ内の変数は O(1) の領域を使用する\n for (int i = 0; i &lt; n; i++) {\n int c = 0;\n }\n // ループ内の関数は O(1) の領域を使用する\n for (int i = 0; i &lt; n; i++) {\n function();\n }\n}\n</code></pre> space_complexity.cs<pre><code>/* 関数 */\nint Function() {\n // 何らかの処理を実行する\n return 0;\n}\n\n/* 定数オーダー */\nvoid Constant(int n) {\n // 定数、変数、オブジェクトは O(1) の領域を使用する\n int a = 0;\n int b = 0;\n int[] nums = new int[10000];\n ListNode node = new(0);\n // ループ内の変数は O(1) の領域を使用する\n for (int i = 0; i &lt; n; i++) {\n int c = 0;\n }\n // ループ内の関数は O(1) の領域を使用する\n for (int i = 0; i &lt; n; i++) {\n Function();\n }\n}\n</code></pre> space_complexity.go<pre><code>/* 関数 */\nfunc function() int {\n // 何らかの処理を実行する...\n return 0\n}\n\n/* 定数オーダー */\nfunc spaceConstant(n int) {\n // 定数、変数、オブジェクトは O(1) の領域を使用する\n const a = 0\n b := 0\n nums := make([]int, 10000)\n node := newNode(0)\n // ループ内の変数は O(1) の領域を使用する\n var c int\n for i := 0; i &lt; n; i++ {\n c = 0\n }\n // ループ内の関数は O(1) の領域を使用する\n for i := 0; i &lt; n; i++ {\n function()\n }\n b += 0\n c += 0\n nums[0] = 0\n node.val = 0\n}\n</code></pre> space_complexity.swift<pre><code>/* 関数 */\n@discardableResult\nfunc function() -&gt; Int {\n // 何らかの処理を実行する\n return 0\n}\n\n/* 定数オーダー */\nfunc constant(n: Int) {\n // 定数、変数、オブジェクトは O(1) の領域を使用する\n let a = 0\n var b = 0\n let nums = Array(repeating: 0, count: 10000)\n let node = ListNode(x: 0)\n // ループ内の変数は O(1) の領域を使用する\n for _ in 0 ..&lt; n {\n let c = 0\n }\n // ループ内の関数は O(1) の領域を使用する\n for _ in 0 ..&lt; n {\n function()\n }\n}\n</code></pre> space_complexity.js<pre><code>/* 関数 */\nfunction constFunc() {\n // 何らかの処理を実行する\n return 0;\n}\n\n/* 定数オーダー */\nfunction constant(n) {\n // 定数、変数、オブジェクトは O(1) の領域を使用する\n const a = 0;\n const b = 0;\n const nums = new Array(10000);\n const node = new ListNode(0);\n // ループ内の変数は O(1) の領域を使用する\n for (let i = 0; i &lt; n; i++) {\n const c = 0;\n }\n // ループ内の関数は O(1) の領域を使用する\n for (let i = 0; i &lt; n; i++) {\n constFunc();\n }\n}\n</code></pre> space_complexity.ts<pre><code>/* 関数 */\nfunction constFunc(): number {\n // 何らかの処理を実行する\n return 0;\n}\n\n/* 定数オーダー */\nfunction constant(n: number): void {\n // 定数、変数、オブジェクトは O(1) の領域を使用する\n const a = 0;\n const b = 0;\n const nums = new Array(10000);\n const node = new ListNode(0);\n // ループ内の変数は O(1) の領域を使用する\n for (let i = 0; i &lt; n; i++) {\n const c = 0;\n }\n // ループ内の関数は O(1) の領域を使用する\n for (let i = 0; i &lt; n; i++) {\n constFunc();\n }\n}\n</code></pre> space_complexity.dart<pre><code>/* 関数 */\nint function() {\n // 何らかの処理を実行する\n return 0;\n}\n\n/* 定数オーダー */\nvoid constant(int n) {\n // 定数、変数、オブジェクトは O(1) の領域を使用する\n final int a = 0;\n int b = 0;\n List&lt;int&gt; nums = List.filled(10000, 0);\n ListNode node = ListNode(0);\n // ループ内の変数は O(1) の領域を使用する\n for (var i = 0; i &lt; n; i++) {\n int c = 0;\n }\n // ループ内の関数は O(1) の領域を使用する\n for (var i = 0; i &lt; n; i++) {\n function();\n }\n}\n</code></pre> space_complexity.rs<pre><code>/* 関数 */\nfn function() -&gt; i32 {\n // 何らかの処理を実行する\n return 0;\n}\n\n/* 定数オーダー */\n#[allow(unused)]\nfn constant(n: i32) {\n // 定数、変数、オブジェクトは O(1) の領域を使用する\n const A: i32 = 0;\n let b = 0;\n let nums = vec![0; 10000];\n let node = ListNode::new(0);\n // ループ内の変数は O(1) の領域を使用する\n for i in 0..n {\n let c = 0;\n }\n // ループ内の関数は O(1) の領域を使用する\n for i in 0..n {\n function();\n }\n}\n</code></pre> space_complexity.c<pre><code>/* 関数 */\nint func() {\n // 何らかの処理を実行する\n return 0;\n}\n\n/* 定数オーダー */\nvoid constant(int n) {\n // 定数、変数、オブジェクトは O(1) の領域を使用する\n const int a = 0;\n int b = 0;\n int nums[1000];\n ListNode *node = newListNode(0);\n free(node);\n // ループ内の変数は O(1) の領域を使用する\n for (int i = 0; i &lt; n; i++) {\n int c = 0;\n }\n // ループ内の関数は O(1) の領域を使用する\n for (int i = 0; i &lt; n; i++) {\n func();\n }\n}\n</code></pre> space_complexity.kt<pre><code>/* 関数 */\nfun function(): Int {\n // 何らかの処理を実行する\n return 0\n}\n\n/* 定数オーダー */\nfun constant(n: Int) {\n // 定数、変数、オブジェクトは O(1) の領域を使用する\n val a = 0\n var b = 0\n val nums = Array(10000) { 0 }\n val node = ListNode(0)\n // ループ内の変数は O(1) の領域を使用する\n for (i in 0..&lt;n) {\n val c = 0\n }\n // ループ内の関数は O(1) の領域を使用する\n for (i in 0..&lt;n) {\n function()\n }\n}\n</code></pre> space_complexity.rb<pre><code>### 関数 ###\ndef function\n # 何らかの処理を実行する\n 0\nend\n\n### 定数オーダー ###\ndef constant(n)\n # 定数、変数、オブジェクトは O(1) の領域を使用する\n a = 0\n nums = [0] * 10000\n node = ListNode.new\n\n # ループ内の変数は O(1) の領域を使用する\n (0...n).each { c = 0 }\n # ループ内の関数は O(1) の領域を使用する\n (0...n).each { function }\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 2 章   計算量の解析","2.4   空間計算量"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#2-on","level":3,"title":"2.   線形のオーダー \\(O(n)\\)","text":"<p>線形のオーダーは、要素数が \\(n\\) に比例する配列、連結リスト、スタック、キューなどによく見られます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py<pre><code>def linear(n: int):\n \"\"\"線形オーダー\"\"\"\n # 長さ n のリストは O(n) の領域を使用する\n nums = [0] * n\n # 長さ n のハッシュテーブルは O(n) の領域を使用する\n hmap = dict[int, str]()\n for i in range(n):\n hmap[i] = str(i)\n</code></pre> space_complexity.cpp<pre><code>/* 線形オーダー */\nvoid linear(int n) {\n // 長さ n の配列は O(n) の領域を使用する\n vector&lt;int&gt; nums(n);\n // 長さ n のリストは O(n) の領域を使用する\n vector&lt;ListNode&gt; nodes;\n for (int i = 0; i &lt; n; i++) {\n nodes.push_back(ListNode(i));\n }\n // 長さ n のハッシュテーブルは O(n) の領域を使用する\n unordered_map&lt;int, string&gt; map;\n for (int i = 0; i &lt; n; i++) {\n map[i] = to_string(i);\n }\n}\n</code></pre> space_complexity.java<pre><code>/* 線形オーダー */\nvoid linear(int n) {\n // 長さ n の配列は O(n) の領域を使用する\n int[] nums = new int[n];\n // 長さ n のリストは O(n) の領域を使用する\n List&lt;ListNode&gt; nodes = new ArrayList&lt;&gt;();\n for (int i = 0; i &lt; n; i++) {\n nodes.add(new ListNode(i));\n }\n // 長さ n のハッシュテーブルは O(n) の領域を使用する\n Map&lt;Integer, String&gt; map = new HashMap&lt;&gt;();\n for (int i = 0; i &lt; n; i++) {\n map.put(i, String.valueOf(i));\n }\n}\n</code></pre> space_complexity.cs<pre><code>/* 線形オーダー */\nvoid Linear(int n) {\n // 長さ n の配列は O(n) の領域を使用する\n int[] nums = new int[n];\n // 長さ n のリストは O(n) の領域を使用する\n List&lt;ListNode&gt; nodes = [];\n for (int i = 0; i &lt; n; i++) {\n nodes.Add(new ListNode(i));\n }\n // 長さ n のハッシュテーブルは O(n) の領域を使用する\n Dictionary&lt;int, string&gt; map = [];\n for (int i = 0; i &lt; n; i++) {\n map.Add(i, i.ToString());\n }\n}\n</code></pre> space_complexity.go<pre><code>/* 線形オーダー */\nfunc spaceLinear(n int) {\n // 長さ n の配列は O(n) の領域を使用する\n _ = make([]int, n)\n // 長さ n のリストは O(n) の領域を使用する\n var nodes []*node\n for i := 0; i &lt; n; i++ {\n nodes = append(nodes, newNode(i))\n }\n // 長さ n のハッシュテーブルは O(n) の領域を使用する\n m := make(map[int]string, n)\n for i := 0; i &lt; n; i++ {\n m[i] = strconv.Itoa(i)\n }\n}\n</code></pre> space_complexity.swift<pre><code>/* 線形オーダー */\nfunc linear(n: Int) {\n // 長さ n の配列は O(n) の領域を使用する\n let nums = Array(repeating: 0, count: n)\n // 長さ n のリストは O(n) の領域を使用する\n let nodes = (0 ..&lt; n).map { ListNode(x: $0) }\n // 長さ n のハッシュテーブルは O(n) の領域を使用する\n let map = Dictionary(uniqueKeysWithValues: (0 ..&lt; n).map { ($0, \"\\($0)\") })\n}\n</code></pre> space_complexity.js<pre><code>/* 線形オーダー */\nfunction linear(n) {\n // 長さ n の配列は O(n) の領域を使用する\n const nums = new Array(n);\n // 長さ n のリストは O(n) の領域を使用する\n const nodes = [];\n for (let i = 0; i &lt; n; i++) {\n nodes.push(new ListNode(i));\n }\n // 長さ n のハッシュテーブルは O(n) の領域を使用する\n const map = new Map();\n for (let i = 0; i &lt; n; i++) {\n map.set(i, i.toString());\n }\n}\n</code></pre> space_complexity.ts<pre><code>/* 線形オーダー */\nfunction linear(n: number): void {\n // 長さ n の配列は O(n) の領域を使用する\n const nums = new Array(n);\n // 長さ n のリストは O(n) の領域を使用する\n const nodes: ListNode[] = [];\n for (let i = 0; i &lt; n; i++) {\n nodes.push(new ListNode(i));\n }\n // 長さ n のハッシュテーブルは O(n) の領域を使用する\n const map = new Map();\n for (let i = 0; i &lt; n; i++) {\n map.set(i, i.toString());\n }\n}\n</code></pre> space_complexity.dart<pre><code>/* 線形オーダー */\nvoid linear(int n) {\n // 長さ n の配列は O(n) の領域を使用する\n List&lt;int&gt; nums = List.filled(n, 0);\n // 長さ n のリストは O(n) の領域を使用する\n List&lt;ListNode&gt; nodes = [];\n for (var i = 0; i &lt; n; i++) {\n nodes.add(ListNode(i));\n }\n // 長さ n のハッシュテーブルは O(n) の領域を使用する\n Map&lt;int, String&gt; map = HashMap();\n for (var i = 0; i &lt; n; i++) {\n map.putIfAbsent(i, () =&gt; i.toString());\n }\n}\n</code></pre> space_complexity.rs<pre><code>/* 線形オーダー */\n#[allow(unused)]\nfn linear(n: i32) {\n // 長さ n の配列は O(n) の領域を使用する\n let mut nums = vec![0; n as usize];\n // 長さ n のリストは O(n) の領域を使用する\n let mut nodes = Vec::new();\n for i in 0..n {\n nodes.push(ListNode::new(i))\n }\n // 長さ n のハッシュテーブルは O(n) の領域を使用する\n let mut map = HashMap::new();\n for i in 0..n {\n map.insert(i, i.to_string());\n }\n}\n</code></pre> space_complexity.c<pre><code>/* ハッシュテーブル */\ntypedef struct {\n int key;\n int val;\n UT_hash_handle hh; // uthash.h を用いて実装する\n} HashTable;\n\n/* 線形オーダー */\nvoid linear(int n) {\n // 長さ n の配列は O(n) の領域を使用する\n int *nums = malloc(sizeof(int) * n);\n free(nums);\n\n // 長さ n のリストは O(n) の領域を使用する\n ListNode **nodes = malloc(sizeof(ListNode *) * n);\n for (int i = 0; i &lt; n; i++) {\n nodes[i] = newListNode(i);\n }\n // メモリの解放\n for (int i = 0; i &lt; n; i++) {\n free(nodes[i]);\n }\n free(nodes);\n\n // 長さ n のハッシュテーブルは O(n) の領域を使用する\n HashTable *h = NULL;\n for (int i = 0; i &lt; n; i++) {\n HashTable *tmp = malloc(sizeof(HashTable));\n tmp-&gt;key = i;\n tmp-&gt;val = i;\n HASH_ADD_INT(h, key, tmp);\n }\n\n // メモリの解放\n HashTable *curr, *tmp;\n HASH_ITER(hh, h, curr, tmp) {\n HASH_DEL(h, curr);\n free(curr);\n }\n}\n</code></pre> space_complexity.kt<pre><code>/* 線形オーダー */\nfun linear(n: Int) {\n // 長さ n の配列は O(n) の領域を使用する\n val nums = Array(n) { 0 }\n // 長さ n のリストは O(n) の領域を使用する\n val nodes = mutableListOf&lt;ListNode&gt;()\n for (i in 0..&lt;n) {\n nodes.add(ListNode(i))\n }\n // 長さ n のハッシュテーブルは O(n) の領域を使用する\n val map = mutableMapOf&lt;Int, String&gt;()\n for (i in 0..&lt;n) {\n map[i] = i.toString()\n }\n}\n</code></pre> space_complexity.rb<pre><code>### 線形オーダー ###\ndef linear(n)\n # 長さ n のリストは O(n) の領域を使用する\n nums = Array.new(n, 0)\n\n # 長さ n のハッシュテーブルは O(n) の領域を使用する\n hmap = {}\n for i in 0...n\n hmap[i] = i.to_s\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 2-17 の関数は再帰深度が \\(n\\) で、まだ戻っていない <code>linear_recur()</code> が同時に \\(n\\) 個存在するため、スタックフレームに \\(O(n)\\) の空間を使います。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py<pre><code>def linear_recur(n: int):\n \"\"\"線形オーダー(再帰による実装)\"\"\"\n print(\"再帰 n =\", n)\n if n == 1:\n return\n linear_recur(n - 1)\n</code></pre> space_complexity.cpp<pre><code>/* 線形オーダー(再帰による実装) */\nvoid linearRecur(int n) {\n cout &lt;&lt; \"再帰 n = \" &lt;&lt; n &lt;&lt; endl;\n if (n == 1)\n return;\n linearRecur(n - 1);\n}\n</code></pre> space_complexity.java<pre><code>/* 線形オーダー(再帰による実装) */\nvoid linearRecur(int n) {\n System.out.println(\"再帰 n = \" + n);\n if (n == 1)\n return;\n linearRecur(n - 1);\n}\n</code></pre> space_complexity.cs<pre><code>/* 線形オーダー(再帰による実装) */\nvoid LinearRecur(int n) {\n Console.WriteLine(\"再帰 n = \" + n);\n if (n == 1) return;\n LinearRecur(n - 1);\n}\n</code></pre> space_complexity.go<pre><code>/* 線形オーダー(再帰による実装) */\nfunc spaceLinearRecur(n int) {\n fmt.Println(\"再帰 n =\", n)\n if n == 1 {\n return\n }\n spaceLinearRecur(n - 1)\n}\n</code></pre> space_complexity.swift<pre><code>/* 線形オーダー(再帰による実装) */\nfunc linearRecur(n: Int) {\n print(\"再帰 n = \\(n)\")\n if n == 1 {\n return\n }\n linearRecur(n: n - 1)\n}\n</code></pre> space_complexity.js<pre><code>/* 線形オーダー(再帰による実装) */\nfunction linearRecur(n) {\n console.log(`再帰 n = ${n}`);\n if (n === 1) return;\n linearRecur(n - 1);\n}\n</code></pre> space_complexity.ts<pre><code>/* 線形オーダー(再帰による実装) */\nfunction linearRecur(n: number): void {\n console.log(`再帰 n = ${n}`);\n if (n === 1) return;\n linearRecur(n - 1);\n}\n</code></pre> space_complexity.dart<pre><code>/* 線形オーダー(再帰による実装) */\nvoid linearRecur(int n) {\n print('再帰 n = $n');\n if (n == 1) return;\n linearRecur(n - 1);\n}\n</code></pre> space_complexity.rs<pre><code>/* 線形オーダー(再帰による実装) */\nfn linear_recur(n: i32) {\n println!(\"再帰 n = {}\", n);\n if n == 1 {\n return;\n };\n linear_recur(n - 1);\n}\n</code></pre> space_complexity.c<pre><code>/* 線形オーダー(再帰による実装) */\nvoid linearRecur(int n) {\n printf(\"再帰 n = %d\\r\\n\", n);\n if (n == 1)\n return;\n linearRecur(n - 1);\n}\n</code></pre> space_complexity.kt<pre><code>/* 線形オーダー(再帰による実装) */\nfun linearRecur(n: Int) {\n println(\"再帰 n = $n\")\n if (n == 1)\n return\n linearRecur(n - 1)\n}\n</code></pre> space_complexity.rb<pre><code>### 線形オーダー(再帰による実装)###\ndef linear_recur(n)\n puts \"再帰 n = #{n}\"\n return if n == 1\n linear_recur(n - 1)\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p></p> <p> 図 2-17   再帰関数による線形の空間計算量 </p>","path":["第 2 章   計算量の解析","2.4   空間計算量"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#3-on2","level":3,"title":"3.   二乗のオーダー \\(O(n^2)\\)","text":"<p>二乗のオーダーは、要素数が \\(n\\) の二乗に比例する行列やグラフによく見られます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py<pre><code>def quadratic(n: int):\n \"\"\"二次オーダー\"\"\"\n # 二次元リストは O(n^2) の領域を使用する\n num_matrix = [[0] * n for _ in range(n)]\n</code></pre> space_complexity.cpp<pre><code>/* 二次オーダー */\nvoid quadratic(int n) {\n // 二次元リストは O(n^2) の領域を使用する\n vector&lt;vector&lt;int&gt;&gt; numMatrix;\n for (int i = 0; i &lt; n; i++) {\n vector&lt;int&gt; tmp;\n for (int j = 0; j &lt; n; j++) {\n tmp.push_back(0);\n }\n numMatrix.push_back(tmp);\n }\n}\n</code></pre> space_complexity.java<pre><code>/* 二次オーダー */\nvoid quadratic(int n) {\n // 行列は O(n^2) の領域を使用する\n int[][] numMatrix = new int[n][n];\n // 二次元リストは O(n^2) の領域を使用する\n List&lt;List&lt;Integer&gt;&gt; numList = new ArrayList&lt;&gt;();\n for (int i = 0; i &lt; n; i++) {\n List&lt;Integer&gt; tmp = new ArrayList&lt;&gt;();\n for (int j = 0; j &lt; n; j++) {\n tmp.add(0);\n }\n numList.add(tmp);\n }\n}\n</code></pre> space_complexity.cs<pre><code>/* 二次オーダー */\nvoid Quadratic(int n) {\n // 行列は O(n^2) の領域を使用する\n int[,] numMatrix = new int[n, n];\n // 二次元リストは O(n^2) の領域を使用する\n List&lt;List&lt;int&gt;&gt; numList = [];\n for (int i = 0; i &lt; n; i++) {\n List&lt;int&gt; tmp = [];\n for (int j = 0; j &lt; n; j++) {\n tmp.Add(0);\n }\n numList.Add(tmp);\n }\n}\n</code></pre> space_complexity.go<pre><code>/* 二次オーダー */\nfunc spaceQuadratic(n int) {\n // 行列は O(n^2) の領域を使用する\n numMatrix := make([][]int, n)\n for i := 0; i &lt; n; i++ {\n numMatrix[i] = make([]int, n)\n }\n}\n</code></pre> space_complexity.swift<pre><code>/* 二次オーダー */\nfunc quadratic(n: Int) {\n // 二次元リストは O(n^2) の領域を使用する\n let numList = Array(repeating: Array(repeating: 0, count: n), count: n)\n}\n</code></pre> space_complexity.js<pre><code>/* 二次オーダー */\nfunction quadratic(n) {\n // 行列は O(n^2) の領域を使用する\n const numMatrix = Array(n)\n .fill(null)\n .map(() =&gt; Array(n).fill(null));\n // 二次元リストは O(n^2) の領域を使用する\n const numList = [];\n for (let i = 0; i &lt; n; i++) {\n const tmp = [];\n for (let j = 0; j &lt; n; j++) {\n tmp.push(0);\n }\n numList.push(tmp);\n }\n}\n</code></pre> space_complexity.ts<pre><code>/* 二次オーダー */\nfunction quadratic(n: number): void {\n // 行列は O(n^2) の領域を使用する\n const numMatrix = Array(n)\n .fill(null)\n .map(() =&gt; Array(n).fill(null));\n // 二次元リストは O(n^2) の領域を使用する\n const numList = [];\n for (let i = 0; i &lt; n; i++) {\n const tmp = [];\n for (let j = 0; j &lt; n; j++) {\n tmp.push(0);\n }\n numList.push(tmp);\n }\n}\n</code></pre> space_complexity.dart<pre><code>/* 二次オーダー */\nvoid quadratic(int n) {\n // 行列は O(n^2) の領域を使用する\n List&lt;List&lt;int&gt;&gt; numMatrix = List.generate(n, (_) =&gt; List.filled(n, 0));\n // 二次元リストは O(n^2) の領域を使用する\n List&lt;List&lt;int&gt;&gt; numList = [];\n for (var i = 0; i &lt; n; i++) {\n List&lt;int&gt; tmp = [];\n for (int j = 0; j &lt; n; j++) {\n tmp.add(0);\n }\n numList.add(tmp);\n }\n}\n</code></pre> space_complexity.rs<pre><code>/* 二次オーダー */\n#[allow(unused)]\nfn quadratic(n: i32) {\n // 行列は O(n^2) の領域を使用する\n let num_matrix = vec![vec![0; n as usize]; n as usize];\n // 二次元リストは O(n^2) の領域を使用する\n let mut num_list = Vec::new();\n for i in 0..n {\n let mut tmp = Vec::new();\n for j in 0..n {\n tmp.push(0);\n }\n num_list.push(tmp);\n }\n}\n</code></pre> space_complexity.c<pre><code>/* 二次オーダー */\nvoid quadratic(int n) {\n // 二次元リストは O(n^2) の領域を使用する\n int **numMatrix = malloc(sizeof(int *) * n);\n for (int i = 0; i &lt; n; i++) {\n int *tmp = malloc(sizeof(int) * n);\n for (int j = 0; j &lt; n; j++) {\n tmp[j] = 0;\n }\n numMatrix[i] = tmp;\n }\n\n // メモリの解放\n for (int i = 0; i &lt; n; i++) {\n free(numMatrix[i]);\n }\n free(numMatrix);\n}\n</code></pre> space_complexity.kt<pre><code>/* 二次オーダー */\nfun quadratic(n: Int) {\n // 行列は O(n^2) の領域を使用する\n val numMatrix = arrayOfNulls&lt;Array&lt;Int&gt;?&gt;(n)\n // 二次元リストは O(n^2) の領域を使用する\n val numList = mutableListOf&lt;MutableList&lt;Int&gt;&gt;()\n for (i in 0..&lt;n) {\n val tmp = mutableListOf&lt;Int&gt;()\n for (j in 0..&lt;n) {\n tmp.add(0)\n }\n numList.add(tmp)\n }\n}\n</code></pre> space_complexity.rb<pre><code>### 二次オーダー ###\ndef quadratic(n)\n # 二次元リストは O(n^2) の領域を使用する\n Array.new(n) { Array.new(n, 0) }\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 2-18 の関数は再帰深度が \\(n\\) で、各呼び出しで長さ \\(n\\)、\\(n-1\\)、\\(\\dots\\)、\\(2\\)、\\(1\\) の配列を初期化します。平均の長さは \\(n / 2\\) なので、全体では \\(O(n^2)\\) の空間を使います。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py<pre><code>def quadratic_recur(n: int) -&gt; int:\n \"\"\"二次オーダー(再帰による実装)\"\"\"\n if n &lt;= 0:\n return 0\n # 配列 nums の長さは n, n-1, ..., 2, 1\n nums = [0] * n\n return quadratic_recur(n - 1)\n</code></pre> space_complexity.cpp<pre><code>/* 二次オーダー(再帰による実装) */\nint quadraticRecur(int n) {\n if (n &lt;= 0)\n return 0;\n vector&lt;int&gt; nums(n);\n cout &lt;&lt; \"再帰 n = \" &lt;&lt; n &lt;&lt; \" における nums の長さ = \" &lt;&lt; nums.size() &lt;&lt; endl;\n return quadraticRecur(n - 1);\n}\n</code></pre> space_complexity.java<pre><code>/* 二次オーダー(再帰による実装) */\nint quadraticRecur(int n) {\n if (n &lt;= 0)\n return 0;\n // 配列 nums の長さは n, n-1, ..., 2, 1\n int[] nums = new int[n];\n System.out.println(\"再帰 n = \" + n + \" における nums の長さ = \" + nums.length);\n return quadraticRecur(n - 1);\n}\n</code></pre> space_complexity.cs<pre><code>/* 二次オーダー(再帰による実装) */\nint QuadraticRecur(int n) {\n if (n &lt;= 0) return 0;\n int[] nums = new int[n];\n Console.WriteLine(\"再帰 n = \" + n + \" における nums の長さ = \" + nums.Length);\n return QuadraticRecur(n - 1);\n}\n</code></pre> space_complexity.go<pre><code>/* 二次オーダー(再帰による実装) */\nfunc spaceQuadraticRecur(n int) int {\n if n &lt;= 0 {\n return 0\n }\n nums := make([]int, n)\n fmt.Printf(\"再帰 n = %d における nums の長さ = %d \\n\", n, len(nums))\n return spaceQuadraticRecur(n - 1)\n}\n</code></pre> space_complexity.swift<pre><code>/* 二次オーダー(再帰による実装) */\n@discardableResult\nfunc quadraticRecur(n: Int) -&gt; Int {\n if n &lt;= 0 {\n return 0\n }\n // 配列 nums の長さは n, n-1, ..., 2, 1\n let nums = Array(repeating: 0, count: n)\n print(\"再帰 n = \\(n) における nums の長さ = \\(nums.count)\")\n return quadraticRecur(n: n - 1)\n}\n</code></pre> space_complexity.js<pre><code>/* 二次オーダー(再帰による実装) */\nfunction quadraticRecur(n) {\n if (n &lt;= 0) return 0;\n const nums = new Array(n);\n console.log(`再帰 n = ${n} における nums の長さ = ${nums.length}`);\n return quadraticRecur(n - 1);\n}\n</code></pre> space_complexity.ts<pre><code>/* 二次オーダー(再帰による実装) */\nfunction quadraticRecur(n: number): number {\n if (n &lt;= 0) return 0;\n const nums = new Array(n);\n console.log(`再帰 n = ${n} における nums の長さ = ${nums.length}`);\n return quadraticRecur(n - 1);\n}\n</code></pre> space_complexity.dart<pre><code>/* 二次オーダー(再帰による実装) */\nint quadraticRecur(int n) {\n if (n &lt;= 0) return 0;\n List&lt;int&gt; nums = List.filled(n, 0);\n print('再帰 n = $n における nums の長さ = ${nums.length}');\n return quadraticRecur(n - 1);\n}\n</code></pre> space_complexity.rs<pre><code>/* 二次オーダー(再帰による実装) */\nfn quadratic_recur(n: i32) -&gt; i32 {\n if n &lt;= 0 {\n return 0;\n };\n // 配列 nums の長さは n, n-1, ..., 2, 1\n let nums = vec![0; n as usize];\n println!(\"再帰 n = {} における nums の長さ = {}\", n, nums.len());\n return quadratic_recur(n - 1);\n}\n</code></pre> space_complexity.c<pre><code>/* 二次オーダー(再帰による実装) */\nint quadraticRecur(int n) {\n if (n &lt;= 0)\n return 0;\n int *nums = malloc(sizeof(int) * n);\n printf(\"再帰 n = %d における nums の長さ = %d\\r\\n\", n, n);\n int res = quadraticRecur(n - 1);\n free(nums);\n return res;\n}\n</code></pre> space_complexity.kt<pre><code>/* 二次オーダー(再帰による実装) */\ntailrec fun quadraticRecur(n: Int): Int {\n if (n &lt;= 0)\n return 0\n // 配列 nums の長さは n, n-1, ..., 2, 1\n val nums = Array(n) { 0 }\n println(\"再帰 n = $n における nums の長さ = ${nums.size}\")\n return quadraticRecur(n - 1)\n}\n</code></pre> space_complexity.rb<pre><code>### 二次オーダー(再帰による実装)###\ndef quadratic_recur(n)\n return 0 unless n &gt; 0\n\n # 配列 nums の長さは n, n-1, ..., 2, 1\n nums = Array.new(n, 0)\n quadratic_recur(n - 1)\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p></p> <p> 図 2-18   再帰関数による二乗の空間計算量 </p>","path":["第 2 章   計算量の解析","2.4   空間計算量"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#4-o2n","level":3,"title":"4.   指数のオーダー \\(O(2^n)\\)","text":"<p>指数のオーダーは二分木によく見られます。図 2-19 の、層数が \\(n\\) の「パーフェクト二分木」はノード数が \\(2^n - 1\\) で、\\(O(2^n)\\) の空間を使います。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby space_complexity.py<pre><code>def build_tree(n: int) -&gt; TreeNode | None:\n \"\"\"指数オーダー(パーフェクト二分木の構築)\"\"\"\n if n == 0:\n return None\n root = TreeNode(0)\n root.left = build_tree(n - 1)\n root.right = build_tree(n - 1)\n return root\n</code></pre> space_complexity.cpp<pre><code>/* 指数オーダー(パーフェクト二分木の構築) */\nTreeNode *buildTree(int n) {\n if (n == 0)\n return nullptr;\n TreeNode *root = new TreeNode(0);\n root-&gt;left = buildTree(n - 1);\n root-&gt;right = buildTree(n - 1);\n return root;\n}\n</code></pre> space_complexity.java<pre><code>/* 指数オーダー(パーフェクト二分木の構築) */\nTreeNode buildTree(int n) {\n if (n == 0)\n return null;\n TreeNode root = new TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n</code></pre> space_complexity.cs<pre><code>/* 指数オーダー(パーフェクト二分木の構築) */\nTreeNode? BuildTree(int n) {\n if (n == 0) return null;\n TreeNode root = new(0) {\n left = BuildTree(n - 1),\n right = BuildTree(n - 1)\n };\n return root;\n}\n</code></pre> space_complexity.go<pre><code>/* 指数オーダー(パーフェクト二分木の構築) */\nfunc buildTree(n int) *TreeNode {\n if n == 0 {\n return nil\n }\n root := NewTreeNode(0)\n root.Left = buildTree(n - 1)\n root.Right = buildTree(n - 1)\n return root\n}\n</code></pre> space_complexity.swift<pre><code>/* 指数オーダー(パーフェクト二分木の構築) */\nfunc buildTree(n: Int) -&gt; TreeNode? {\n if n == 0 {\n return nil\n }\n let root = TreeNode(x: 0)\n root.left = buildTree(n: n - 1)\n root.right = buildTree(n: n - 1)\n return root\n}\n</code></pre> space_complexity.js<pre><code>/* 指数オーダー(パーフェクト二分木の構築) */\nfunction buildTree(n) {\n if (n === 0) return null;\n const root = new TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n</code></pre> space_complexity.ts<pre><code>/* 指数オーダー(パーフェクト二分木の構築) */\nfunction buildTree(n: number): TreeNode | null {\n if (n === 0) return null;\n const root = new TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n</code></pre> space_complexity.dart<pre><code>/* 指数オーダー(パーフェクト二分木の構築) */\nTreeNode? buildTree(int n) {\n if (n == 0) return null;\n TreeNode root = TreeNode(0);\n root.left = buildTree(n - 1);\n root.right = buildTree(n - 1);\n return root;\n}\n</code></pre> space_complexity.rs<pre><code>/* 指数オーダー(パーフェクト二分木の構築) */\nfn build_tree(n: i32) -&gt; Option&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt; {\n if n == 0 {\n return None;\n };\n let root = TreeNode::new(0);\n root.borrow_mut().left = build_tree(n - 1);\n root.borrow_mut().right = build_tree(n - 1);\n return Some(root);\n}\n</code></pre> space_complexity.c<pre><code>/* 指数オーダー(パーフェクト二分木の構築) */\nTreeNode *buildTree(int n) {\n if (n == 0)\n return NULL;\n TreeNode *root = newTreeNode(0);\n root-&gt;left = buildTree(n - 1);\n root-&gt;right = buildTree(n - 1);\n return root;\n}\n</code></pre> space_complexity.kt<pre><code>/* 指数オーダー(パーフェクト二分木の構築) */\nfun buildTree(n: Int): TreeNode? {\n if (n == 0)\n return null\n val root = TreeNode(0)\n root.left = buildTree(n - 1)\n root.right = buildTree(n - 1)\n return root\n}\n</code></pre> space_complexity.rb<pre><code>### 指数オーダー(パーフェクト二分木の構築)###\ndef build_tree(n)\n return if n == 0\n\n TreeNode.new.tap do |root|\n root.left = build_tree(n - 1)\n root.right = build_tree(n - 1)\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p></p> <p> 図 2-19   パーフェクト二分木による指数の空間計算量 </p>","path":["第 2 章   計算量の解析","2.4   空間計算量"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#5-olog-n","level":3,"title":"5.   対数のオーダー \\(O(\\log n)\\)","text":"<p>対数のオーダーは分割統治によく見られます。たとえばマージソートは、長さ \\(n\\) の配列を各再帰で中央から半分に分け、高さ \\(\\log n\\) の再帰木を作るため、スタックフレームに \\(O(\\log n)\\) の空間を使います。</p> <p>数値から文字列への変換も例になります。正の整数 \\(n\\) の桁数は \\(\\lfloor \\log_{10} n \\rfloor + 1\\) なので、文字列の長さも \\(\\lfloor \\log_{10} n \\rfloor + 1\\) です。したがって、空間計算量は \\(O(\\log_{10} n + 1) = O(\\log n)\\) です。</p>","path":["第 2 章   計算量の解析","2.4   空間計算量"],"tags":[]},{"location":"chapter_computational_complexity/space_complexity/#244","level":2,"title":"2.4.4   時間と空間のトレードオフ","text":"<p>理想的には、時間計算量も空間計算量も最適にしたいところです。しかし実際には、両方を同時に最適化するのは非常に難しいことが多いです。</p> <p>時間計算量を減らすには、空間計算量を増やす必要があることが多く、その逆も同様です。メモリを使って実行を速くする考え方を「空間を使って時間を節約する」、その逆を「時間を使って空間を節約する」と呼びます。</p> <p>どちらを選ぶかは、何を重視するかによります。多くの場合、空間より時間の方が貴重なため、空間を使って時間を節約する戦略が一般的です。ただし、データ量が非常に多い場合は、空間計算量を抑えることも重要です。</p>","path":["第 2 章   計算量の解析","2.4   空間計算量"],"tags":[]},{"location":"chapter_computational_complexity/summary/","level":1,"title":"2.5   まとめ","text":"","path":["第 2 章   計算量の解析","2.5   まとめ"],"tags":[]},{"location":"chapter_computational_complexity/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<p>アルゴリズムの効率の評価</p> <ul> <li>時間効率と空間効率は、アルゴリズムの良し悪しを測る二つの主な基準です。</li> <li>実測でも効率を評価できますが、測定環境の影響を除くのが難しく、大量の計算資源を使います。</li> <li>計算量の解析は実測の欠点を補い、すべての実行環境に適用できる結果と、異なるデータ量での効率を示します。</li> </ul> <p>時間計算量</p> <ul> <li>時間計算量は、データ量の増加に伴う実行時間の増え方を測るもので、効率の評価に役立ちます。ただし、入力が小さい場合や計算量が同じ場合など、効率を正確に比較できないこともあります。</li> <li>最悪時間計算量はビッグ \\(O\\) 記法で表し、関数の漸近上界に対応します。\\(n\\) が正の無限大に近づくときの、操作回数 \\(T(n)\\) の増加のオーダーを表します。</li> <li>時間計算量は、まず操作回数を数え、次に漸近上界を求める、という 2 段階で見積もります。</li> <li>代表的な時間計算量を小さい順に並べると、\\(O(1)\\)、\\(O(\\log n)\\)、\\(O(n)\\)、\\(O(n \\log n)\\)、\\(O(n^2)\\)、\\(O(2^n)\\)、\\(O(n!)\\) などがあります。</li> <li>計算量が一定ではなく、入力データの分布によって変わるアルゴリズムもあります。時間計算量には最悪・最良・平均があります。最良の場合を達成するには入力に厳しい条件が必要なため、最良時間計算量はほとんど使いません。</li> <li>平均時間計算量はランダムな入力での実行効率を表し、実際の用途での性能に最も近いものです。求めるには、入力データの分布と、それらを総合した期待値を計算する必要があります。</li> </ul> <p>空間計算量</p> <ul> <li>空間計算量は時間計算量と同様に、データ量の増加に伴うメモリ使用量の増え方を測ります。</li> <li>実行に関わるメモリ空間は、入力空間、一時空間、出力空間に分けられます。通常、入力空間は空間計算量に含めません。一時空間は、一時データ、スタックフレーム、命令の空間に分けられ、スタックフレームが計算量に影響するのは通常、再帰関数の場合だけです。</li> <li>通常は最悪空間計算量だけに注目し、最悪の入力と最もメモリを使う時点での空間計算量を求めます。</li> <li>代表的な空間計算量を小さい順に並べると、\\(O(1)\\)、\\(O(\\log n)\\)、\\(O(n)\\)、\\(O(n^2)\\)、\\(O(2^n)\\) などがあります。</li> </ul>","path":["第 2 章   計算量の解析","2.5   まとめ"],"tags":[]},{"location":"chapter_computational_complexity/summary/#2-q-a","level":3,"title":"2.   Q &amp; A","text":"<p>Q:末尾再帰の空間計算量は \\(O(1)\\) ですか。</p> <p>理論上、末尾再帰関数の空間計算量は \\(O(1)\\) に最適化できます。ただし、ほとんどのプログラミング言語(Java、Python、C++、Go、C# など)は自動的な末尾再帰最適化に対応していないため、通常は \\(O(n)\\) と考えます。</p> <p>Q:関数とメソッドという用語には、どのような違いがありますか。</p> <p>関数(function)は独立して実行でき、すべての引数を明示的に渡します。メソッド(method)はオブジェクトに関連付けられ、呼び出し元のオブジェクトが暗黙に渡されるため、クラスのインスタンスに含まれるデータを操作できます。</p> <p>いくつかの代表的な言語で説明します。</p> <ul> <li>C は手続き型言語であり、オブジェクト指向の概念がないため、関数だけがあります。ただし、構造体(struct)を作ってオブジェクト指向を模倣でき、構造体に関連付けた関数が、ほかの言語のメソッドに相当します。</li> <li>Java と C# はオブジェクト指向言語で、コードのまとまり(メソッド)は通常、クラスの一部です。静的メソッドはクラスに結び付けられ、特定のインスタンス変数にアクセスできないため、関数に似た動作をします。</li> <li>C++ と Python は、手続き型(関数)とオブジェクト指向(メソッド)の両方をサポートします。</li> </ul> <p>Q:「代表的な空間計算量」の図は、使用する空間の絶対量を示していますか。</p> <p>いいえ。図が示すのは空間計算量、つまり増え方の傾向であり、空間の絶対量ではありません。</p> <p>\\(n = 8\\) とすると、曲線の値が対応する関数と一致しないことに気づくかもしれません。各曲線には、見やすい範囲に収めるための定数項を加えているためです。</p> <p>実際には、各手法の計算量の「定数項」がわからないことが多く、通常、計算量だけで \\(n = 8\\) のときの最良の解法を選ぶことはできません。一方、\\(n = 8^5\\) なら増加の傾向が支配的になるため、選びやすくなります。</p> <p>Q 実際の用途に応じて、時間や空間を犠牲にして設計することはありますか。</p> <p>実際の用途では、空間を使って時間を節約する選択が多く見られます。たとえばデータベースのインデックスでは、B+ 木やハッシュインデックスに大量のメモリを使うことで、\\(O(\\log n)\\)、さらには \\(O(1)\\) の高速な検索を実現します。</p> <p>空間資源が貴重な場面では、時間を使って空間を節約することもあります。たとえば組み込み開発ではメモリが貴重なため、ハッシュテーブルを使わず、配列の順次探索を選ぶ場合があります。メモリを節約できる代わりに、探索は遅くなります。</p>","path":["第 2 章   計算量の解析","2.5   まとめ"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/","level":1,"title":"2.3   時間計算量","text":"<p>実行時間は、アルゴリズムの効率を直感的かつ正確に表します。コードの実行時間を正確に見積もるには、どうすればよいでしょうか。</p> <ol> <li>実行環境を決めます。ハードウェア構成、プログラミング言語、システム環境などは、いずれも実行効率に影響します。</li> <li>各計算操作にかかる時間を見積もります。たとえば、加算 <code>+</code> は 1 ns、乗算 <code>*</code> は 10 ns、出力 <code>print()</code> は 5 ns などです。</li> <li>コード内のすべての計算操作を数え、その実行時間を合計して、全体の実行時間を求めます。</li> </ol> <p>たとえば、次のコードで入力の大きさを \\(n\\) とします。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code># ある実行環境において\ndef algorithm(n: int):\n a = 2 # 1 ns\n a = a + 1 # 1 ns\n a = a * 2 # 10 ns\n # n 回繰り返す\n for _ in range(n): # 1 ns\n print(0) # 5 ns\n</code></pre> <pre><code>// ある実行環境において\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // n 回繰り返す\n for (int i = 0; i &lt; n; i++) { // 1 ns\n cout &lt;&lt; 0 &lt;&lt; endl; // 5 ns\n }\n}\n</code></pre> <pre><code>// ある実行環境において\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // n 回繰り返す\n for (int i = 0; i &lt; n; i++) { // 1 ns\n System.out.println(0); // 5 ns\n }\n}\n</code></pre> <pre><code>// ある実行環境において\nvoid Algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // n 回繰り返す\n for (int i = 0; i &lt; n; i++) { // 1 ns\n Console.WriteLine(0); // 5 ns\n }\n}\n</code></pre> <pre><code>// ある実行環境において\nfunc algorithm(n int) {\n a := 2 // 1 ns\n a = a + 1 // 1 ns\n a = a * 2 // 10 ns\n // n 回繰り返す\n for i := 0; i &lt; n; i++ { // 1 ns\n fmt.Println(a) // 5 ns\n }\n}\n</code></pre> <pre><code>// ある実行環境において\nfunc algorithm(n: Int) {\n var a = 2 // 1 ns\n a = a + 1 // 1 ns\n a = a * 2 // 10 ns\n // n 回繰り返す\n for _ in 0 ..&lt; n { // 1 ns\n print(0) // 5 ns\n }\n}\n</code></pre> <pre><code>// ある実行環境において\nfunction algorithm(n) {\n var a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // n 回繰り返す\n for(let i = 0; i &lt; n; i++) { // 1 ns\n console.log(0); // 5 ns\n }\n}\n</code></pre> <pre><code>// ある実行環境において\nfunction algorithm(n: number): void {\n var a: number = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // n 回繰り返す\n for(let i = 0; i &lt; n; i++) { // 1 ns\n console.log(0); // 5 ns\n }\n}\n</code></pre> <pre><code>// ある実行環境において\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // n 回繰り返す\n for (int i = 0; i &lt; n; i++) { // 1 ns\n print(0); // 5 ns\n }\n}\n</code></pre> <pre><code>// ある実行環境において\nfn algorithm(n: i32) {\n let mut a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // n 回繰り返す\n for _ in 0..n { // 1 ns\n println!(\"{}\", 0); // 5 ns\n }\n}\n</code></pre> <pre><code>// ある実行環境において\nvoid algorithm(int n) {\n int a = 2; // 1 ns\n a = a + 1; // 1 ns\n a = a * 2; // 10 ns\n // n 回繰り返す\n for (int i = 0; i &lt; n; i++) { // 1 ns\n printf(\"%d\", 0); // 5 ns\n }\n}\n</code></pre> <pre><code>// ある実行環境において\nfun algorithm(n: Int) {\n var a = 2 // 1 ns\n a = a + 1 // 1 ns\n a = a * 2 // 10 ns\n // n 回繰り返す\n for (i in 0..&lt;n) { // 1 ns\n println(0) // 5 ns\n }\n}\n</code></pre> <pre><code># ある実行環境において\ndef algorithm(n)\n a = 2 # 1 ns\n a = a + 1 # 1 ns\n a = a * 2 # 10 ns\n # n 回繰り返す\n (0...n).each do # 1 ns\n puts 0 # 5 ns\n end\nend\n</code></pre> <p>この方法では、実行時間は \\((6n + 12)\\) ns になります。</p> \\[ 1 + 1 + 10 + (1 + 5) \\times n = 6n + 12 \\] <p>しかし実際には、このように実行時間を積み上げる方法は、適切でも現実的でもありません。まず、アルゴリズムはさまざまな環境で動くため、見積もりを特定の環境に結び付けたくありません。また、各操作の実行時間を知るのは難しく、見積もり自体が非常に困難になります。</p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#231","level":2,"title":"2.3.1   時間の増え方を調べる","text":"<p>時間計算量の解析で調べるのは、実行時間そのものではなく、データ量が増えるにつれて実行時間がどのように増えるかです。</p> <p>「時間の増え方」は抽象的なので、例で理解しましょう。入力の大きさを \\(n\\) とし、三つのアルゴリズム <code>A</code>、<code>B</code>、<code>C</code> を考えます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code># アルゴリズム A の時間計算量:定数のオーダー\ndef algorithm_A(n: int):\n print(0)\n# アルゴリズム B の時間計算量:線形のオーダー\ndef algorithm_B(n: int):\n for _ in range(n):\n print(0)\n# アルゴリズム C の時間計算量:定数のオーダー\ndef algorithm_C(n: int):\n for _ in range(1000000):\n print(0)\n</code></pre> <pre><code>// アルゴリズム A の時間計算量:定数のオーダー\nvoid algorithm_A(int n) {\n cout &lt;&lt; 0 &lt;&lt; endl;\n}\n// アルゴリズム B の時間計算量:線形のオーダー\nvoid algorithm_B(int n) {\n for (int i = 0; i &lt; n; i++) {\n cout &lt;&lt; 0 &lt;&lt; endl;\n }\n}\n// アルゴリズム C の時間計算量:定数のオーダー\nvoid algorithm_C(int n) {\n for (int i = 0; i &lt; 1000000; i++) {\n cout &lt;&lt; 0 &lt;&lt; endl;\n }\n}\n</code></pre> <pre><code>// アルゴリズム A の時間計算量:定数のオーダー\nvoid algorithm_A(int n) {\n System.out.println(0);\n}\n// アルゴリズム B の時間計算量:線形のオーダー\nvoid algorithm_B(int n) {\n for (int i = 0; i &lt; n; i++) {\n System.out.println(0);\n }\n}\n// アルゴリズム C の時間計算量:定数のオーダー\nvoid algorithm_C(int n) {\n for (int i = 0; i &lt; 1000000; i++) {\n System.out.println(0);\n }\n}\n</code></pre> <pre><code>// アルゴリズム A の時間計算量:定数のオーダー\nvoid AlgorithmA(int n) {\n Console.WriteLine(0);\n}\n// アルゴリズム B の時間計算量:線形のオーダー\nvoid AlgorithmB(int n) {\n for (int i = 0; i &lt; n; i++) {\n Console.WriteLine(0);\n }\n}\n// アルゴリズム C の時間計算量:定数のオーダー\nvoid AlgorithmC(int n) {\n for (int i = 0; i &lt; 1000000; i++) {\n Console.WriteLine(0);\n }\n}\n</code></pre> <pre><code>// アルゴリズム A の時間計算量:定数のオーダー\nfunc algorithm_A(n int) {\n fmt.Println(0)\n}\n// アルゴリズム B の時間計算量:線形のオーダー\nfunc algorithm_B(n int) {\n for i := 0; i &lt; n; i++ {\n fmt.Println(0)\n }\n}\n// アルゴリズム C の時間計算量:定数のオーダー\nfunc algorithm_C(n int) {\n for i := 0; i &lt; 1000000; i++ {\n fmt.Println(0)\n }\n}\n</code></pre> <pre><code>// アルゴリズム A の時間計算量:定数のオーダー\nfunc algorithmA(n: Int) {\n print(0)\n}\n\n// アルゴリズム B の時間計算量:線形のオーダー\nfunc algorithmB(n: Int) {\n for _ in 0 ..&lt; n {\n print(0)\n }\n}\n\n// アルゴリズム C の時間計算量:定数のオーダー\nfunc algorithmC(n: Int) {\n for _ in 0 ..&lt; 1_000_000 {\n print(0)\n }\n}\n</code></pre> <pre><code>// アルゴリズム A の時間計算量:定数のオーダー\nfunction algorithm_A(n) {\n console.log(0);\n}\n// アルゴリズム B の時間計算量:線形のオーダー\nfunction algorithm_B(n) {\n for (let i = 0; i &lt; n; i++) {\n console.log(0);\n }\n}\n// アルゴリズム C の時間計算量:定数のオーダー\nfunction algorithm_C(n) {\n for (let i = 0; i &lt; 1000000; i++) {\n console.log(0);\n }\n}\n</code></pre> <pre><code>// アルゴリズム A の時間計算量:定数のオーダー\nfunction algorithm_A(n: number): void {\n console.log(0);\n}\n// アルゴリズム B の時間計算量:線形のオーダー\nfunction algorithm_B(n: number): void {\n for (let i = 0; i &lt; n; i++) {\n console.log(0);\n }\n}\n// アルゴリズム C の時間計算量:定数のオーダー\nfunction algorithm_C(n: number): void {\n for (let i = 0; i &lt; 1000000; i++) {\n console.log(0);\n }\n}\n</code></pre> <pre><code>// アルゴリズム A の時間計算量:定数のオーダー\nvoid algorithmA(int n) {\n print(0);\n}\n// アルゴリズム B の時間計算量:線形のオーダー\nvoid algorithmB(int n) {\n for (int i = 0; i &lt; n; i++) {\n print(0);\n }\n}\n// アルゴリズム C の時間計算量:定数のオーダー\nvoid algorithmC(int n) {\n for (int i = 0; i &lt; 1000000; i++) {\n print(0);\n }\n}\n</code></pre> <pre><code>// アルゴリズム A の時間計算量:定数のオーダー\nfn algorithm_A(n: i32) {\n println!(\"{}\", 0);\n}\n// アルゴリズム B の時間計算量:線形のオーダー\nfn algorithm_B(n: i32) {\n for _ in 0..n {\n println!(\"{}\", 0);\n }\n}\n// アルゴリズム C の時間計算量:定数のオーダー\nfn algorithm_C(n: i32) {\n for _ in 0..1000000 {\n println!(\"{}\", 0);\n }\n}\n</code></pre> <pre><code>// アルゴリズム A の時間計算量:定数のオーダー\nvoid algorithm_A(int n) {\n printf(\"%d\", 0);\n}\n// アルゴリズム B の時間計算量:線形のオーダー\nvoid algorithm_B(int n) {\n for (int i = 0; i &lt; n; i++) {\n printf(\"%d\", 0);\n }\n}\n// アルゴリズム C の時間計算量:定数のオーダー\nvoid algorithm_C(int n) {\n for (int i = 0; i &lt; 1000000; i++) {\n printf(\"%d\", 0);\n }\n}\n</code></pre> <pre><code>// アルゴリズム A の時間計算量:定数のオーダー\nfun algoritm_A(n: Int) {\n println(0)\n}\n// アルゴリズム B の時間計算量:線形のオーダー\nfun algorithm_B(n: Int) {\n for (i in 0..&lt;n){\n println(0)\n }\n}\n// アルゴリズム C の時間計算量:定数のオーダー\nfun algorithm_C(n: Int) {\n for (i in 0..&lt;1000000) {\n println(0)\n }\n}\n</code></pre> <pre><code># アルゴリズム A の時間計算量:定数のオーダー\ndef algorithm_A(n)\n puts 0\nend\n\n# アルゴリズム B の時間計算量:線形のオーダー\ndef algorithm_B(n)\n (0...n).each { puts 0 }\nend\n\n# アルゴリズム C の時間計算量:定数のオーダー\ndef algorithm_C(n)\n (0...1_000_000).each { puts 0 }\nend\n</code></pre> <p>図 2-7 に、この三つのアルゴリズムの時間計算量を示します。</p> <ul> <li><code>A</code> には出力操作が \\(1\\) 回だけあり、\\(n\\) が増えても実行時間は増えません。この時間計算量を「定数のオーダー」と呼びます。</li> <li><code>B</code> は出力を \\(n\\) 回繰り返し、\\(n\\) の増加に対して実行時間が線形に増えます。この時間計算量を「線形のオーダー」と呼びます。</li> <li><code>C</code> は出力を \\(1000000\\) 回繰り返します。実行時間は長いものの、入力の大きさ \\(n\\) とは無関係です。そのため、<code>C</code> の時間計算量は <code>A</code> と同じ「定数のオーダー」です。</li> </ul> <p></p> <p> 図 2-7   アルゴリズム A、B、C の実行時間の増え方 </p> <p>実行時間を直接数える方法と比べ、時間計算量の解析にはどのような特徴があるでしょうか。</p> <ul> <li>効率を有効に評価できます。たとえば <code>B</code> は線形に時間が増え、\\(n &gt; 1\\) では <code>A</code> より、\\(n &gt; 1000000\\) では <code>C</code> より遅くなります。入力の大きさ \\(n\\) が十分に大きければ、定数のオーダーのアルゴリズムは線形のオーダーより必ず有利になります。これが時間の増え方という意味です。</li> <li>見積もり方が簡単です。実行環境や計算操作の種類は、実行時間の増え方には関係しません。そのため、各操作の時間を同じ「単位時間」とみなし、「操作にかかる時間の集計」を「操作回数の集計」に置き換えられ、見積もりが大幅に簡単になります。</li> <li>一定の限界もあります。<code>A</code> と <code>C</code> は同じ時間計算量でも、実際の時間は大きく異なります。また、<code>B</code> は <code>C</code> より計算量が大きくても、入力の大きさ \\(n\\) が小さければ、明らかに <code>C</code> より速くなります。このような場合、計算量だけでは効率を判断しにくくなります。それでも計算量の解析は、効率を評価する最も有効で一般的な方法です。</li> </ul>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#232","level":2,"title":"2.3.2   関数の漸近上界","text":"<p>入力の大きさが \\(n\\) の関数を考えます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code>def algorithm(n: int):\n a = 1 # +1\n a = a + 1 # +1\n a = a * 2 # +1\n # n 回繰り返す\n for i in range(n): # +1\n print(0) # +1\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // n 回繰り返す\n for (int i = 0; i &lt; n; i++) { // +1(毎回 i ++ を実行)\n cout &lt;&lt; 0 &lt;&lt; endl; // +1\n }\n}\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // n 回繰り返す\n for (int i = 0; i &lt; n; i++) { // +1(毎回 i ++ を実行)\n System.out.println(0); // +1\n }\n}\n</code></pre> <pre><code>void Algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // n 回繰り返す\n for (int i = 0; i &lt; n; i++) { // +1(毎回 i ++ を実行)\n Console.WriteLine(0); // +1\n }\n}\n</code></pre> <pre><code>func algorithm(n int) {\n a := 1 // +1\n a = a + 1 // +1\n a = a * 2 // +1\n // n 回繰り返す\n for i := 0; i &lt; n; i++ { // +1\n fmt.Println(a) // +1\n }\n}\n</code></pre> <pre><code>func algorithm(n: Int) {\n var a = 1 // +1\n a = a + 1 // +1\n a = a * 2 // +1\n // n 回繰り返す\n for _ in 0 ..&lt; n { // +1\n print(0) // +1\n }\n}\n</code></pre> <pre><code>function algorithm(n) {\n var a = 1; // +1\n a += 1; // +1\n a *= 2; // +1\n // n 回繰り返す\n for(let i = 0; i &lt; n; i++){ // +1(毎回 i ++ を実行)\n console.log(0); // +1\n }\n}\n</code></pre> <pre><code>function algorithm(n: number): void{\n var a: number = 1; // +1\n a += 1; // +1\n a *= 2; // +1\n // n 回繰り返す\n for(let i = 0; i &lt; n; i++){ // +1(毎回 i ++ を実行)\n console.log(0); // +1\n }\n}\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // n 回繰り返す\n for (int i = 0; i &lt; n; i++) { // +1(毎回 i ++ を実行)\n print(0); // +1\n }\n}\n</code></pre> <pre><code>fn algorithm(n: i32) {\n let mut a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n\n // n 回繰り返す\n for _ in 0..n { // +1(毎回 i ++ を実行)\n println!(\"{}\", 0); // +1\n }\n}\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 1; // +1\n a = a + 1; // +1\n a = a * 2; // +1\n // n 回繰り返す\n for (int i = 0; i &lt; n; i++) { // +1(毎回 i ++ を実行)\n printf(\"%d\", 0); // +1\n }\n}\n</code></pre> <pre><code>fun algorithm(n: Int) {\n var a = 1 // +1\n a = a + 1 // +1\n a = a * 2 // +1\n // n 回繰り返す\n for (i in 0..&lt;n) { // +1(毎回 i ++ を実行)\n println(0) // +1\n }\n}\n</code></pre> <pre><code>def algorithm(n)\n a = 1 # +1\n a = a + 1 # +1\n a = a * 2 # +1\n # n 回繰り返す\n (0...n).each do # +1\n puts 0 # +1\n end\nend\n</code></pre> <p>操作回数を入力の大きさ \\(n\\) の関数 \\(T(n)\\) とすると、上の関数では次のようになります。</p> \\[ T(n) = 3 + 2n \\] <p>\\(T(n)\\) は一次関数なので、実行時間は線形に増え、時間計算量は線形のオーダーです。</p> <p>線形の時間計算量を \\(O(n)\\) と書きます。この数学記号をビッグ \\(O\\) 記法(big-\\(O\\) notation)といい、関数 \\(T(n)\\) の漸近上界(asymptotic upper bound)を表します。</p> <p>時間計算量の解析は、本質的には「操作回数 \\(T(n)\\)」の漸近上界を求めることで、明確な数学的定義があります。</p> <p>関数の漸近上界</p> <p>正の実数 \\(c\\) と実数 \\(n_0\\) が存在し、すべての \\(n &gt; n_0\\) に対して \\(T(n) \\leq c \\cdot f(n)\\) が成り立つとき、\\(f(n)\\) は \\(T(n)\\) の漸近上界を与えるといい、\\(T(n) = O(f(n))\\) と書きます。</p> <p>図 2-8 のように、漸近上界を求めるとは、\\(n\\) が無限大に近づくとき、\\(T(n)\\) と \\(f(n)\\) が同じ増加のオーダーとなり、定数係数 \\(c\\) だけ異なるような関数 \\(f(n)\\) を探すことです。</p> <p></p> <p> 図 2-8   関数の漸近上界 </p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#233","level":2,"title":"2.3.3   見積もり方","text":"<p>漸近上界は数学的なので、完全に理解できなくても心配はいりません。まず見積もり方を身に付け、実践しながら数学的な意味を理解していきましょう。</p> <p>定義によれば、\\(f(n)\\) が決まれば時間計算量 \\(O(f(n))\\) が得られます。では、漸近上界 \\(f(n)\\) はどう決めるのでしょうか。大きく二つの手順、操作回数の集計と漸近上界の判定で行います。</p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#1-1","level":3,"title":"1.   手順 1:操作回数を数える","text":"<p>コードを上から下へ、行ごとに数えれば求められます。ただし、\\(c \\cdot f(n)\\) の定数係数 \\(c\\) は任意の大きさにできるため、操作回数 \\(T(n)\\) の係数や定数項は無視できます。この原則から、数え方を簡単にする次の工夫が得られます。</p> <ol> <li>\\(T(n)\\) の定数を無視します。定数は \\(n\\) と無関係なので、時間計算量に影響しません。</li> <li>すべての係数を省略します。たとえば \\(2n\\) 回や \\(5n + 1\\) 回のループは、\\(n\\) 回と簡略化できます。\\(n\\) の前の係数は時間計算量に影響しないためです。</li> <li>入れ子のループでは掛け算を使います。総操作回数は、外側と内側の操作回数の積です。各ループには、それぞれ手順 <code>1.</code> と <code>2.</code> の工夫を適用できます。</li> </ol> <p>これらの工夫を使って、次の関数の操作回数を数えます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code>def algorithm(n: int):\n a = 1 # +0(工夫 1)\n a = a + n # +0(工夫 1)\n # +n(工夫 2)\n for i in range(5 * n + 1):\n print(0)\n # +n*n(工夫 3)\n for i in range(2 * n):\n for j in range(n + 1):\n print(0)\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 1; // +0(工夫 1)\n a = a + n; // +0(工夫 1)\n // +n(工夫 2)\n for (int i = 0; i &lt; 5 * n + 1; i++) {\n cout &lt;&lt; 0 &lt;&lt; endl;\n }\n // +n*n(工夫 3)\n for (int i = 0; i &lt; 2 * n; i++) {\n for (int j = 0; j &lt; n + 1; j++) {\n cout &lt;&lt; 0 &lt;&lt; endl;\n }\n }\n}\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 1; // +0(工夫 1)\n a = a + n; // +0(工夫 1)\n // +n(工夫 2)\n for (int i = 0; i &lt; 5 * n + 1; i++) {\n System.out.println(0);\n }\n // +n*n(工夫 3)\n for (int i = 0; i &lt; 2 * n; i++) {\n for (int j = 0; j &lt; n + 1; j++) {\n System.out.println(0);\n }\n }\n}\n</code></pre> <pre><code>void Algorithm(int n) {\n int a = 1; // +0(工夫 1)\n a = a + n; // +0(工夫 1)\n // +n(工夫 2)\n for (int i = 0; i &lt; 5 * n + 1; i++) {\n Console.WriteLine(0);\n }\n // +n*n(工夫 3)\n for (int i = 0; i &lt; 2 * n; i++) {\n for (int j = 0; j &lt; n + 1; j++) {\n Console.WriteLine(0);\n }\n }\n}\n</code></pre> <pre><code>func algorithm(n int) {\n a := 1 // +0(工夫 1)\n a = a + n // +0(工夫 1)\n // +n(工夫 2)\n for i := 0; i &lt; 5 * n + 1; i++ {\n fmt.Println(0)\n }\n // +n*n(工夫 3)\n for i := 0; i &lt; 2 * n; i++ {\n for j := 0; j &lt; n + 1; j++ {\n fmt.Println(0)\n }\n }\n}\n</code></pre> <pre><code>func algorithm(n: Int) {\n var a = 1 // +0(工夫 1)\n a = a + n // +0(工夫 1)\n // +n(工夫 2)\n for _ in 0 ..&lt; (5 * n + 1) {\n print(0)\n }\n // +n*n(工夫 3)\n for _ in 0 ..&lt; (2 * n) {\n for _ in 0 ..&lt; (n + 1) {\n print(0)\n }\n }\n}\n</code></pre> <pre><code>function algorithm(n) {\n let a = 1; // +0(工夫 1)\n a = a + n; // +0(工夫 1)\n // +n(工夫 2)\n for (let i = 0; i &lt; 5 * n + 1; i++) {\n console.log(0);\n }\n // +n*n(工夫 3)\n for (let i = 0; i &lt; 2 * n; i++) {\n for (let j = 0; j &lt; n + 1; j++) {\n console.log(0);\n }\n }\n}\n</code></pre> <pre><code>function algorithm(n: number): void {\n let a = 1; // +0(工夫 1)\n a = a + n; // +0(工夫 1)\n // +n(工夫 2)\n for (let i = 0; i &lt; 5 * n + 1; i++) {\n console.log(0);\n }\n // +n*n(工夫 3)\n for (let i = 0; i &lt; 2 * n; i++) {\n for (let j = 0; j &lt; n + 1; j++) {\n console.log(0);\n }\n }\n}\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 1; // +0(工夫 1)\n a = a + n; // +0(工夫 1)\n // +n(工夫 2)\n for (int i = 0; i &lt; 5 * n + 1; i++) {\n print(0);\n }\n // +n*n(工夫 3)\n for (int i = 0; i &lt; 2 * n; i++) {\n for (int j = 0; j &lt; n + 1; j++) {\n print(0);\n }\n }\n}\n</code></pre> <pre><code>fn algorithm(n: i32) {\n let mut a = 1; // +0(工夫 1)\n a = a + n; // +0(工夫 1)\n\n // +n(工夫 2)\n for i in 0..(5 * n + 1) {\n println!(\"{}\", 0);\n }\n\n // +n*n(工夫 3)\n for i in 0..(2 * n) {\n for j in 0..(n + 1) {\n println!(\"{}\", 0);\n }\n }\n}\n</code></pre> <pre><code>void algorithm(int n) {\n int a = 1; // +0(工夫 1)\n a = a + n; // +0(工夫 1)\n // +n(工夫 2)\n for (int i = 0; i &lt; 5 * n + 1; i++) {\n printf(\"%d\", 0);\n }\n // +n*n(工夫 3)\n for (int i = 0; i &lt; 2 * n; i++) {\n for (int j = 0; j &lt; n + 1; j++) {\n printf(\"%d\", 0);\n }\n }\n}\n</code></pre> <pre><code>fun algorithm(n: Int) {\n var a = 1 // +0(工夫 1)\n a = a + n // +0(工夫 1)\n // +n(工夫 2)\n for (i in 0..&lt;5 * n + 1) {\n println(0)\n }\n // +n*n(工夫 3)\n for (i in 0..&lt;2 * n) {\n for (j in 0..&lt;n + 1) {\n println(0)\n }\n }\n}\n</code></pre> <pre><code>def algorithm(n)\n a = 1 # +0(工夫 1)\n a = a + n # +0(工夫 1)\n # +n(工夫 2)\n (0...(5 * n + 1)).each do { puts 0 }\n # +n*n(工夫 3)\n (0...(2 * n)).each do\n (0...(n + 1)).each do { puts 0 }\n end\nend\n</code></pre> <p>次の式は、工夫を使う前と後の集計結果です。どちらからも、時間計算量 \\(O(n^2)\\) が得られます。</p> \\[ \\begin{aligned} T(n) &amp; = 2n(n + 1) + (5n + 1) + 2 &amp; \\text{すべて数える (-.-|||)} \\newline &amp; = 2n^2 + 7n + 3 \\newline T(n) &amp; = n^2 + n &amp; \\text{手間を省いて数える (o.O)} \\end{aligned} \\]","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#2-2","level":3,"title":"2.   手順 2:漸近上界を求める","text":"<p>時間計算量は、\\(T(n)\\) の最も高い次数の項で決まります。\\(n\\) が無限大に近づくと、その項が支配的になり、ほかの項の影響を無視できるためです。</p> <p>表 2-2 にいくつかの例を示します。一部の大きな値は、「係数はオーダーを変えられない」ことを強調するためです。\\(n\\) が無限大に近づくと、これらの定数は重要でなくなります。</p> <p> 表 2-2   操作回数と時間計算量の対応 </p> 操作回数 \\(T(n)\\) 時間計算量 \\(O(f(n))\\) \\(100000\\) \\(O(1)\\) \\(3n + 2\\) \\(O(n)\\) \\(2n^2 + 3n + 2\\) \\(O(n^2)\\) \\(n^3 + 10000n^2\\) \\(O(n^3)\\) \\(2^n + 10000n^{10000}\\) \\(O(2^n)\\)","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#234","level":2,"title":"2.3.4   代表的な種類","text":"<p>入力の大きさを \\(n\\) とすると、代表的な時間計算量は図 2-9 のとおりです。小さい順に並べています。</p> \\[ \\begin{aligned} &amp; O(1) &lt; O(\\log n) &lt; O(n) &lt; O(n \\log n) &lt; O(n^2) &lt; O(2^n) &lt; O(n!) \\newline &amp; \\text{定数} &lt; \\text{対数} &lt; \\text{線形} &lt; \\text{線形対数} &lt; \\text{二乗} &lt; \\text{指数} &lt; \\text{階乗} \\end{aligned} \\] <p></p> <p> 図 2-9   代表的な時間計算量 </p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#1-o1","level":3,"title":"1.   定数のオーダー \\(O(1)\\)","text":"<p>定数のオーダーでは、操作回数は入力の大きさ \\(n\\) に依存せず、\\(n\\) が変わっても変化しません。</p> <p>次の関数では、操作回数 <code>size</code> が大きくても、入力の大きさ \\(n\\) と無関係なので、時間計算量は \\(O(1)\\) です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py<pre><code>def constant(n: int) -&gt; int:\n \"\"\"定数オーダー\"\"\"\n count = 0\n size = 100000\n for _ in range(size):\n count += 1\n return count\n</code></pre> time_complexity.cpp<pre><code>/* 定数オーダー */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n for (int i = 0; i &lt; size; i++)\n count++;\n return count;\n}\n</code></pre> time_complexity.java<pre><code>/* 定数オーダー */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n for (int i = 0; i &lt; size; i++)\n count++;\n return count;\n}\n</code></pre> time_complexity.cs<pre><code>/* 定数オーダー */\nint Constant(int n) {\n int count = 0;\n int size = 100000;\n for (int i = 0; i &lt; size; i++)\n count++;\n return count;\n}\n</code></pre> time_complexity.go<pre><code>/* 定数オーダー */\nfunc constant(n int) int {\n count := 0\n size := 100000\n for i := 0; i &lt; size; i++ {\n count++\n }\n return count\n}\n</code></pre> time_complexity.swift<pre><code>/* 定数オーダー */\nfunc constant(n: Int) -&gt; Int {\n var count = 0\n let size = 100_000\n for _ in 0 ..&lt; size {\n count += 1\n }\n return count\n}\n</code></pre> time_complexity.js<pre><code>/* 定数オーダー */\nfunction constant(n) {\n let count = 0;\n const size = 100000;\n for (let i = 0; i &lt; size; i++) count++;\n return count;\n}\n</code></pre> time_complexity.ts<pre><code>/* 定数オーダー */\nfunction constant(n: number): number {\n let count = 0;\n const size = 100000;\n for (let i = 0; i &lt; size; i++) count++;\n return count;\n}\n</code></pre> time_complexity.dart<pre><code>/* 定数オーダー */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n for (var i = 0; i &lt; size; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.rs<pre><code>/* 定数オーダー */\nfn constant(n: i32) -&gt; i32 {\n _ = n;\n let mut count = 0;\n let size = 100_000;\n for _ in 0..size {\n count += 1;\n }\n count\n}\n</code></pre> time_complexity.c<pre><code>/* 定数オーダー */\nint constant(int n) {\n int count = 0;\n int size = 100000;\n for (int i = 0; i &lt; size; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.kt<pre><code>/* 定数オーダー */\nfun constant(n: Int): Int {\n var count = 0\n val size = 100000\n for (i in 0..&lt;size)\n count++\n return count\n}\n</code></pre> time_complexity.rb<pre><code>### 定数オーダー ###\ndef constant(n)\n count = 0\n size = 100000\n\n (0...size).each { count += 1 }\n\n count\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#2-on","level":3,"title":"2.   線形のオーダー \\(O(n)\\)","text":"<p>線形のオーダーでは、操作回数は入力の大きさ \\(n\\) に対して線形に増えます。通常、1 重のループで現れます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py<pre><code>def linear(n: int) -&gt; int:\n \"\"\"線形オーダー\"\"\"\n count = 0\n for _ in range(n):\n count += 1\n return count\n</code></pre> time_complexity.cpp<pre><code>/* 線形オーダー */\nint linear(int n) {\n int count = 0;\n for (int i = 0; i &lt; n; i++)\n count++;\n return count;\n}\n</code></pre> time_complexity.java<pre><code>/* 線形オーダー */\nint linear(int n) {\n int count = 0;\n for (int i = 0; i &lt; n; i++)\n count++;\n return count;\n}\n</code></pre> time_complexity.cs<pre><code>/* 線形オーダー */\nint Linear(int n) {\n int count = 0;\n for (int i = 0; i &lt; n; i++)\n count++;\n return count;\n}\n</code></pre> time_complexity.go<pre><code>/* 線形オーダー */\nfunc linear(n int) int {\n count := 0\n for i := 0; i &lt; n; i++ {\n count++\n }\n return count\n}\n</code></pre> time_complexity.swift<pre><code>/* 線形オーダー */\nfunc linear(n: Int) -&gt; Int {\n var count = 0\n for _ in 0 ..&lt; n {\n count += 1\n }\n return count\n}\n</code></pre> time_complexity.js<pre><code>/* 線形オーダー */\nfunction linear(n) {\n let count = 0;\n for (let i = 0; i &lt; n; i++) count++;\n return count;\n}\n</code></pre> time_complexity.ts<pre><code>/* 線形オーダー */\nfunction linear(n: number): number {\n let count = 0;\n for (let i = 0; i &lt; n; i++) count++;\n return count;\n}\n</code></pre> time_complexity.dart<pre><code>/* 線形オーダー */\nint linear(int n) {\n int count = 0;\n for (var i = 0; i &lt; n; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.rs<pre><code>/* 線形オーダー */\nfn linear(n: i32) -&gt; i32 {\n let mut count = 0;\n for _ in 0..n {\n count += 1;\n }\n count\n}\n</code></pre> time_complexity.c<pre><code>/* 線形オーダー */\nint linear(int n) {\n int count = 0;\n for (int i = 0; i &lt; n; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.kt<pre><code>/* 線形オーダー */\nfun linear(n: Int): Int {\n var count = 0\n for (i in 0..&lt;n)\n count++\n return count\n}\n</code></pre> time_complexity.rb<pre><code>### 線形オーダー ###\ndef linear(n)\n count = 0\n (0...n).each { count += 1 }\n count\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>配列や連結リストの走査も、時間計算量は \\(O(n)\\) です。ここで \\(n\\) は配列や連結リストの長さです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py<pre><code>def array_traversal(nums: list[int]) -&gt; int:\n \"\"\"線形オーダー(配列の走査)\"\"\"\n count = 0\n # ループの回数は配列の長さに比例する\n for num in nums:\n count += 1\n return count\n</code></pre> time_complexity.cpp<pre><code>/* 線形オーダー(配列の走査) */\nint arrayTraversal(vector&lt;int&gt; &amp;nums) {\n int count = 0;\n // ループの回数は配列の長さに比例する\n for (int num : nums) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.java<pre><code>/* 線形オーダー(配列の走査) */\nint arrayTraversal(int[] nums) {\n int count = 0;\n // ループの回数は配列の長さに比例する\n for (int num : nums) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.cs<pre><code>/* 線形オーダー(配列の走査) */\nint ArrayTraversal(int[] nums) {\n int count = 0;\n // ループの回数は配列の長さに比例する\n foreach (int num in nums) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.go<pre><code>/* 線形オーダー(配列の走査) */\nfunc arrayTraversal(nums []int) int {\n count := 0\n // ループの回数は配列の長さに比例する\n for range nums {\n count++\n }\n return count\n}\n</code></pre> time_complexity.swift<pre><code>/* 線形オーダー(配列の走査) */\nfunc arrayTraversal(nums: [Int]) -&gt; Int {\n var count = 0\n // ループの回数は配列の長さに比例する\n for _ in nums {\n count += 1\n }\n return count\n}\n</code></pre> time_complexity.js<pre><code>/* 線形オーダー(配列の走査) */\nfunction arrayTraversal(nums) {\n let count = 0;\n // ループの回数は配列の長さに比例する\n for (let i = 0; i &lt; nums.length; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.ts<pre><code>/* 線形オーダー(配列の走査) */\nfunction arrayTraversal(nums: number[]): number {\n let count = 0;\n // ループの回数は配列の長さに比例する\n for (let i = 0; i &lt; nums.length; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.dart<pre><code>/* 線形オーダー(配列の走査) */\nint arrayTraversal(List&lt;int&gt; nums) {\n int count = 0;\n // ループの回数は配列の長さに比例する\n for (var _num in nums) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.rs<pre><code>/* 線形オーダー(配列の走査) */\nfn array_traversal(nums: &amp;[i32]) -&gt; i32 {\n let mut count = 0;\n // ループの回数は配列の長さに比例する\n for _ in nums {\n count += 1;\n }\n count\n}\n</code></pre> time_complexity.c<pre><code>/* 線形オーダー(配列の走査) */\nint arrayTraversal(int *nums, int n) {\n int count = 0;\n // ループの回数は配列の長さに比例する\n for (int i = 0; i &lt; n; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.kt<pre><code>/* 線形オーダー(配列の走査) */\nfun arrayTraversal(nums: IntArray): Int {\n var count = 0\n // ループの回数は配列の長さに比例する\n for (num in nums) {\n count++\n }\n return count\n}\n</code></pre> time_complexity.rb<pre><code>### 線形オーダー(配列の走査)###\ndef array_traversal(nums)\n count = 0\n\n # ループの回数は配列の長さに比例する\n for num in nums\n count += 1\n end\n\n count\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>入力の大きさ \\(n\\) が何を表すかは、入力の種類に応じて決める必要があります。最初の例では変数 \\(n\\) 自体が入力の大きさで、2 番目の例では配列の長さ \\(n\\) が入力の大きさです。</p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#3-on2","level":3,"title":"3.   二乗のオーダー \\(O(n^2)\\)","text":"<p>二乗のオーダーでは、操作回数は入力の大きさ \\(n\\) の二乗に比例して増えます。入れ子のループでよく現れ、外側と内側がそれぞれ \\(O(n)\\) なら、全体は \\(O(n^2)\\) です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py<pre><code>def quadratic(n: int) -&gt; int:\n \"\"\"二次オーダー\"\"\"\n count = 0\n # ループの回数はデータの大きさ n の二乗に比例する\n for i in range(n):\n for j in range(n):\n count += 1\n return count\n</code></pre> time_complexity.cpp<pre><code>/* 二次オーダー */\nint quadratic(int n) {\n int count = 0;\n // ループの回数はデータの大きさ n の二乗に比例する\n for (int i = 0; i &lt; n; i++) {\n for (int j = 0; j &lt; n; j++) {\n count++;\n }\n }\n return count;\n}\n</code></pre> time_complexity.java<pre><code>/* 二次オーダー */\nint quadratic(int n) {\n int count = 0;\n // ループの回数はデータの大きさ n の二乗に比例する\n for (int i = 0; i &lt; n; i++) {\n for (int j = 0; j &lt; n; j++) {\n count++;\n }\n }\n return count;\n}\n</code></pre> time_complexity.cs<pre><code>/* 二次オーダー */\nint Quadratic(int n) {\n int count = 0;\n // ループの回数はデータの大きさ n の二乗に比例する\n for (int i = 0; i &lt; n; i++) {\n for (int j = 0; j &lt; n; j++) {\n count++;\n }\n }\n return count;\n}\n</code></pre> time_complexity.go<pre><code>/* 二次オーダー */\nfunc quadratic(n int) int {\n count := 0\n // ループの回数はデータの大きさ n の二乗に比例する\n for i := 0; i &lt; n; i++ {\n for j := 0; j &lt; n; j++ {\n count++\n }\n }\n return count\n}\n</code></pre> time_complexity.swift<pre><code>/* 二次オーダー */\nfunc quadratic(n: Int) -&gt; Int {\n var count = 0\n // ループの回数はデータの大きさ n の二乗に比例する\n for _ in 0 ..&lt; n {\n for _ in 0 ..&lt; n {\n count += 1\n }\n }\n return count\n}\n</code></pre> time_complexity.js<pre><code>/* 二次オーダー */\nfunction quadratic(n) {\n let count = 0;\n // ループの回数はデータの大きさ n の二乗に比例する\n for (let i = 0; i &lt; n; i++) {\n for (let j = 0; j &lt; n; j++) {\n count++;\n }\n }\n return count;\n}\n</code></pre> time_complexity.ts<pre><code>/* 二次オーダー */\nfunction quadratic(n: number): number {\n let count = 0;\n // ループの回数はデータの大きさ n の二乗に比例する\n for (let i = 0; i &lt; n; i++) {\n for (let j = 0; j &lt; n; j++) {\n count++;\n }\n }\n return count;\n}\n</code></pre> time_complexity.dart<pre><code>/* 二次オーダー */\nint quadratic(int n) {\n int count = 0;\n // ループの回数はデータの大きさ n の二乗に比例する\n for (int i = 0; i &lt; n; i++) {\n for (int j = 0; j &lt; n; j++) {\n count++;\n }\n }\n return count;\n}\n</code></pre> time_complexity.rs<pre><code>/* 二次オーダー */\nfn quadratic(n: i32) -&gt; i32 {\n let mut count = 0;\n // ループの回数はデータの大きさ n の二乗に比例する\n for _ in 0..n {\n for _ in 0..n {\n count += 1;\n }\n }\n count\n}\n</code></pre> time_complexity.c<pre><code>/* 二次オーダー */\nint quadratic(int n) {\n int count = 0;\n // ループの回数はデータの大きさ n の二乗に比例する\n for (int i = 0; i &lt; n; i++) {\n for (int j = 0; j &lt; n; j++) {\n count++;\n }\n }\n return count;\n}\n</code></pre> time_complexity.kt<pre><code>/* 二次オーダー */\nfun quadratic(n: Int): Int {\n var count = 0\n // ループの回数はデータの大きさ n の二乗に比例する\n for (i in 0..&lt;n) {\n for (j in 0..&lt;n) {\n count++\n }\n }\n return count\n}\n</code></pre> time_complexity.rb<pre><code>### 二次オーダー ###\ndef quadratic(n)\n count = 0\n\n # ループの回数はデータの大きさ n の二乗に比例する\n for i in 0...n\n for j in 0...n\n count += 1\n end\n end\n\n count\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 2-10 で、定数、線形、二乗の三つの時間計算量を比較します。</p> <p></p> <p> 図 2-10   定数、線形、二乗の時間計算量 </p> <p>バブルソートでは、外側のループが \\(n - 1\\) 回、内側が \\(n-1\\)、\\(n-2\\)、\\(\\dots\\)、\\(2\\)、\\(1\\) 回、平均 \\(n / 2\\) 回実行されます。したがって、時間計算量は \\(O((n - 1) n / 2) = O(n^2)\\) です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py<pre><code>def bubble_sort(nums: list[int]) -&gt; int:\n \"\"\"二次オーダー(バブルソート)\"\"\"\n count = 0 # カウンタ\n # 外側のループ:未ソート区間は [0, i]\n for i in range(len(nums) - 1, 0, -1):\n # 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j in range(i):\n if nums[j] &gt; nums[j + 1]:\n # nums[j] と nums[j + 1] を交換する\n tmp: int = nums[j]\n nums[j] = nums[j + 1]\n nums[j + 1] = tmp\n count += 3 # 要素の交換は 3 回の単位操作からなる\n return count\n</code></pre> time_complexity.cpp<pre><code>/* 二次オーダー(バブルソート) */\nint bubbleSort(vector&lt;int&gt; &amp;nums) {\n int count = 0; // カウンタ\n // 外側のループ:未ソート区間は [0, i]\n for (int i = nums.size() - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // 要素の交換は 3 回の単位操作からなる\n }\n }\n }\n return count;\n}\n</code></pre> time_complexity.java<pre><code>/* 二次オーダー(バブルソート) */\nint bubbleSort(int[] nums) {\n int count = 0; // カウンタ\n // 外側のループ:未ソート区間は [0, i]\n for (int i = nums.length - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // 要素の交換は 3 回の単位操作からなる\n }\n }\n }\n return count;\n}\n</code></pre> time_complexity.cs<pre><code>/* 二次オーダー(バブルソート) */\nint BubbleSort(int[] nums) {\n int count = 0; // カウンタ\n // 外側のループ:未ソート区間は [0, i]\n for (int i = nums.Length - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n count += 3; // 要素の交換は 3 回の単位操作からなる\n }\n }\n }\n return count;\n}\n</code></pre> time_complexity.go<pre><code>/* 二次オーダー(バブルソート) */\nfunc bubbleSort(nums []int) int {\n count := 0 // カウンタ\n // 外側のループ:未ソート区間は [0, i]\n for i := len(nums) - 1; i &gt; 0; i-- {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j := 0; j &lt; i; j++ {\n if nums[j] &gt; nums[j+1] {\n // nums[j] と nums[j + 1] を交換する\n tmp := nums[j]\n nums[j] = nums[j+1]\n nums[j+1] = tmp\n count += 3 // 要素の交換は 3 回の単位操作からなる\n }\n }\n }\n return count\n}\n</code></pre> time_complexity.swift<pre><code>/* 二次オーダー(バブルソート) */\nfunc bubbleSort(nums: inout [Int]) -&gt; Int {\n var count = 0 // カウンタ\n // 外側のループ:未ソート区間は [0, i]\n for i in nums.indices.dropFirst().reversed() {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j in 0 ..&lt; i {\n if nums[j] &gt; nums[j + 1] {\n // nums[j] と nums[j + 1] を交換する\n let tmp = nums[j]\n nums[j] = nums[j + 1]\n nums[j + 1] = tmp\n count += 3 // 要素の交換は 3 回の単位操作からなる\n }\n }\n }\n return count\n}\n</code></pre> time_complexity.js<pre><code>/* 二次オーダー(バブルソート) */\nfunction bubbleSort(nums) {\n let count = 0; // カウンタ\n // 外側のループ:未ソート区間は [0, i]\n for (let i = nums.length - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (let j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // 要素の交換は 3 回の単位操作からなる\n }\n }\n }\n return count;\n}\n</code></pre> time_complexity.ts<pre><code>/* 二次オーダー(バブルソート) */\nfunction bubbleSort(nums: number[]): number {\n let count = 0; // カウンタ\n // 外側のループ:未ソート区間は [0, i]\n for (let i = nums.length - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (let j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // 要素の交換は 3 回の単位操作からなる\n }\n }\n }\n return count;\n}\n</code></pre> time_complexity.dart<pre><code>/* 二次オーダー(バブルソート) */\nint bubbleSort(List&lt;int&gt; nums) {\n int count = 0; // カウンタ\n // 外側のループ:未ソート区間は [0, i]\n for (var i = nums.length - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (var j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // 要素の交換は 3 回の単位操作からなる\n }\n }\n }\n return count;\n}\n</code></pre> time_complexity.rs<pre><code>/* 二次オーダー(バブルソート) */\nfn bubble_sort(nums: &amp;mut [i32]) -&gt; i32 {\n let mut count = 0; // カウンタ\n\n // 外側のループ:未ソート区間は [0, i]\n for i in (1..nums.len()).rev() {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j in 0..i {\n if nums[j] &gt; nums[j + 1] {\n // nums[j] と nums[j + 1] を交換する\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // 要素の交換は 3 回の単位操作からなる\n }\n }\n }\n count\n}\n</code></pre> time_complexity.c<pre><code>/* 二次オーダー(バブルソート) */\nint bubbleSort(int *nums, int n) {\n int count = 0; // カウンタ\n // 外側のループ:未ソート区間は [0, i]\n for (int i = n - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n count += 3; // 要素の交換は 3 回の単位操作からなる\n }\n }\n }\n return count;\n}\n</code></pre> time_complexity.kt<pre><code>/* 二次オーダー(バブルソート) */\nfun bubbleSort(nums: IntArray): Int {\n var count = 0 // カウンタ\n // 外側のループ:未ソート区間は [0, i]\n for (i in nums.size - 1 downTo 1) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (j in 0..&lt;i) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n val temp = nums[j]\n nums[j] = nums[j + 1]\n nums[j + 1] = temp\n count += 3 // 要素の交換は 3 回の単位操作からなる\n }\n }\n }\n return count\n}\n</code></pre> time_complexity.rb<pre><code>### 二次オーダー(バブルソート)###\ndef bubble_sort(nums)\n count = 0 # カウンタ\n\n # 外側のループ:未ソート区間は [0, i]\n for i in (nums.length - 1).downto(0)\n # 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j in 0...i\n if nums[j] &gt; nums[j + 1]\n # nums[j] と nums[j + 1] を交換する\n tmp = nums[j]\n nums[j] = nums[j + 1]\n nums[j + 1] = tmp\n count += 3 # 要素の交換は 3 回の単位操作からなる\n end\n end\n end\n\n count\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#4-o2n","level":3,"title":"4.   指数のオーダー \\(O(2^n)\\)","text":"<p>生物の「細胞分裂」は、指数的な増加の典型例です。最初に \\(1\\) 個の細胞があり、1 回分裂すると \\(2\\) 個、2 回では \\(4\\) 個となり、\\(n\\) 回後には \\(2^n\\) 個になります。</p> <p>図 2-11 と次のコードは細胞分裂を模擬しており、時間計算量は \\(O(2^n)\\) です。入力 \\(n\\) は分裂するラウンド数、戻り値 <code>count</code> は分裂の総回数を表します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py<pre><code>def exponential(n: int) -&gt; int:\n \"\"\"指数オーダー(ループによる実装)\"\"\"\n count = 0\n base = 1\n # 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for _ in range(n):\n for _ in range(base):\n count += 1\n base *= 2\n # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count\n</code></pre> time_complexity.cpp<pre><code>/* 指数オーダー(ループによる実装) */\nint exponential(int n) {\n int count = 0, base = 1;\n // 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for (int i = 0; i &lt; n; i++) {\n for (int j = 0; j &lt; base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n</code></pre> time_complexity.java<pre><code>/* 指数オーダー(ループによる実装) */\nint exponential(int n) {\n int count = 0, base = 1;\n // 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for (int i = 0; i &lt; n; i++) {\n for (int j = 0; j &lt; base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n</code></pre> time_complexity.cs<pre><code>/* 指数オーダー(ループによる実装) */\nint Exponential(int n) {\n int count = 0, bas = 1;\n // 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for (int i = 0; i &lt; n; i++) {\n for (int j = 0; j &lt; bas; j++) {\n count++;\n }\n bas *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n</code></pre> time_complexity.go<pre><code>/* 指数オーダー(ループによる実装)*/\nfunc exponential(n int) int {\n count, base := 0, 1\n // 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for i := 0; i &lt; n; i++ {\n for j := 0; j &lt; base; j++ {\n count++\n }\n base *= 2\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count\n}\n</code></pre> time_complexity.swift<pre><code>/* 指数オーダー(ループによる実装) */\nfunc exponential(n: Int) -&gt; Int {\n var count = 0\n var base = 1\n // 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for _ in 0 ..&lt; n {\n for _ in 0 ..&lt; base {\n count += 1\n }\n base *= 2\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count\n}\n</code></pre> time_complexity.js<pre><code>/* 指数オーダー(ループによる実装) */\nfunction exponential(n) {\n let count = 0,\n base = 1;\n // 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for (let i = 0; i &lt; n; i++) {\n for (let j = 0; j &lt; base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n</code></pre> time_complexity.ts<pre><code>/* 指数オーダー(ループによる実装) */\nfunction exponential(n: number): number {\n let count = 0,\n base = 1;\n // 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for (let i = 0; i &lt; n; i++) {\n for (let j = 0; j &lt; base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n</code></pre> time_complexity.dart<pre><code>/* 指数オーダー(ループによる実装) */\nint exponential(int n) {\n int count = 0, base = 1;\n // 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for (var i = 0; i &lt; n; i++) {\n for (var j = 0; j &lt; base; j++) {\n count++;\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n</code></pre> time_complexity.rs<pre><code>/* 指数オーダー(ループによる実装) */\nfn exponential(n: i32) -&gt; i32 {\n let mut count = 0;\n let mut base = 1;\n // 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for _ in 0..n {\n for _ in 0..base {\n count += 1\n }\n base *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n count\n}\n</code></pre> time_complexity.c<pre><code>/* 指数オーダー(ループによる実装) */\nint exponential(int n) {\n int count = 0;\n int bas = 1;\n // 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for (int i = 0; i &lt; n; i++) {\n for (int j = 0; j &lt; bas; j++) {\n count++;\n }\n bas *= 2;\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count;\n}\n</code></pre> time_complexity.kt<pre><code>/* 指数オーダー(ループによる実装) */\nfun exponential(n: Int): Int {\n var count = 0\n var base = 1\n // 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n for (i in 0..&lt;n) {\n for (j in 0..&lt;base) {\n count++\n }\n base *= 2\n }\n // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n return count\n}\n</code></pre> time_complexity.rb<pre><code>### 指数オーダー(ループによる実装)###\ndef exponential(n)\n count, base = 0, 1\n\n # 各回で細胞が 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する\n (0...n).each do\n (0...base).each { count += 1 }\n base *= 2\n end\n\n # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1\n count\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p></p> <p> 図 2-11   指数の時間計算量 </p> <p>実際のアルゴリズムでは、指数のオーダーは再帰関数によく現れます。次のコードは、再帰的に二つに分かれ、\\(n\\) 回分岐したら止まります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py<pre><code>def exp_recur(n: int) -&gt; int:\n \"\"\"指数オーダー(再帰による実装)\"\"\"\n if n == 1:\n return 1\n return exp_recur(n - 1) + exp_recur(n - 1) + 1\n</code></pre> time_complexity.cpp<pre><code>/* 指数オーダー(再帰による実装) */\nint expRecur(int n) {\n if (n == 1)\n return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n</code></pre> time_complexity.java<pre><code>/* 指数オーダー(再帰による実装) */\nint expRecur(int n) {\n if (n == 1)\n return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n</code></pre> time_complexity.cs<pre><code>/* 指数オーダー(再帰による実装) */\nint ExpRecur(int n) {\n if (n == 1) return 1;\n return ExpRecur(n - 1) + ExpRecur(n - 1) + 1;\n}\n</code></pre> time_complexity.go<pre><code>/* 指数オーダー(再帰による実装)*/\nfunc expRecur(n int) int {\n if n == 1 {\n return 1\n }\n return expRecur(n-1) + expRecur(n-1) + 1\n}\n</code></pre> time_complexity.swift<pre><code>/* 指数オーダー(再帰による実装) */\nfunc expRecur(n: Int) -&gt; Int {\n if n == 1 {\n return 1\n }\n return expRecur(n: n - 1) + expRecur(n: n - 1) + 1\n}\n</code></pre> time_complexity.js<pre><code>/* 指数オーダー(再帰による実装) */\nfunction expRecur(n) {\n if (n === 1) return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n</code></pre> time_complexity.ts<pre><code>/* 指数オーダー(再帰による実装) */\nfunction expRecur(n: number): number {\n if (n === 1) return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n</code></pre> time_complexity.dart<pre><code>/* 指数オーダー(再帰による実装) */\nint expRecur(int n) {\n if (n == 1) return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n</code></pre> time_complexity.rs<pre><code>/* 指数オーダー(再帰による実装) */\nfn exp_recur(n: i32) -&gt; i32 {\n if n == 1 {\n return 1;\n }\n exp_recur(n - 1) + exp_recur(n - 1) + 1\n}\n</code></pre> time_complexity.c<pre><code>/* 指数オーダー(再帰による実装) */\nint expRecur(int n) {\n if (n == 1)\n return 1;\n return expRecur(n - 1) + expRecur(n - 1) + 1;\n}\n</code></pre> time_complexity.kt<pre><code>/* 指数オーダー(再帰による実装) */\nfun expRecur(n: Int): Int {\n if (n == 1) {\n return 1\n }\n return expRecur(n - 1) + expRecur(n - 1) + 1\n}\n</code></pre> time_complexity.rb<pre><code>### 指数オーダー(再帰による実装)###\ndef exp_recur(n)\n return 1 if n == 1\n exp_recur(n - 1) + exp_recur(n - 1) + 1\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>指数のオーダーは非常に速く増え、全探索やバックトラッキングなどの総当たり法によく現れます。大規模な問題では許容できないため、通常は動的計画法や貪欲法などを使う必要があります。</p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#5-olog-n","level":3,"title":"5.   対数のオーダー \\(O(\\log n)\\)","text":"<p>指数とは逆に、対数のオーダーは「毎回半分に縮む」場合を表します。入力の大きさを \\(n\\) とすると、毎回半分になるため、ループ回数は \\(\\log_2 n\\)、すなわち \\(2^n\\) の逆関数になります。</p> <p>図 2-12 と次のコードは「毎回半分にする」過程を模擬しています。時間計算量は \\(O(\\log_2 n)\\)、略して \\(O(\\log n)\\) です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py<pre><code>def logarithmic(n: int) -&gt; int:\n \"\"\"対数オーダー(ループによる実装)\"\"\"\n count = 0\n while n &gt; 1:\n n = n / 2\n count += 1\n return count\n</code></pre> time_complexity.cpp<pre><code>/* 対数オーダー(ループによる実装) */\nint logarithmic(int n) {\n int count = 0;\n while (n &gt; 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.java<pre><code>/* 対数オーダー(ループによる実装) */\nint logarithmic(int n) {\n int count = 0;\n while (n &gt; 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.cs<pre><code>/* 対数オーダー(ループによる実装) */\nint Logarithmic(int n) {\n int count = 0;\n while (n &gt; 1) {\n n /= 2;\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.go<pre><code>/* 対数オーダー(ループによる実装)*/\nfunc logarithmic(n int) int {\n count := 0\n for n &gt; 1 {\n n = n / 2\n count++\n }\n return count\n}\n</code></pre> time_complexity.swift<pre><code>/* 対数オーダー(ループによる実装) */\nfunc logarithmic(n: Int) -&gt; Int {\n var count = 0\n var n = n\n while n &gt; 1 {\n n = n / 2\n count += 1\n }\n return count\n}\n</code></pre> time_complexity.js<pre><code>/* 対数オーダー(ループによる実装) */\nfunction logarithmic(n) {\n let count = 0;\n while (n &gt; 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.ts<pre><code>/* 対数オーダー(ループによる実装) */\nfunction logarithmic(n: number): number {\n let count = 0;\n while (n &gt; 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.dart<pre><code>/* 対数オーダー(ループによる実装) */\nint logarithmic(int n) {\n int count = 0;\n while (n &gt; 1) {\n n = n ~/ 2;\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.rs<pre><code>/* 対数オーダー(ループによる実装) */\nfn logarithmic(mut n: i32) -&gt; i32 {\n let mut count = 0;\n while n &gt; 1 {\n n = n / 2;\n count += 1;\n }\n count\n}\n</code></pre> time_complexity.c<pre><code>/* 対数オーダー(ループによる実装) */\nint logarithmic(int n) {\n int count = 0;\n while (n &gt; 1) {\n n = n / 2;\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.kt<pre><code>/* 対数オーダー(ループによる実装) */\nfun logarithmic(n: Int): Int {\n var n1 = n\n var count = 0\n while (n1 &gt; 1) {\n n1 /= 2\n count++\n }\n return count\n}\n</code></pre> time_complexity.rb<pre><code>### 対数オーダー(ループによる実装)###\ndef logarithmic(n)\n count = 0\n\n while n &gt; 1\n n /= 2\n count += 1\n end\n\n count\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p></p> <p> 図 2-12   対数の時間計算量 </p> <p>指数と同様、対数のオーダーも再帰関数によく現れます。次のコードは、高さ \\(\\log_2 n\\) の再帰木を作ります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py<pre><code>def log_recur(n: int) -&gt; int:\n \"\"\"対数オーダー(再帰による実装)\"\"\"\n if n &lt;= 1:\n return 0\n return log_recur(n / 2) + 1\n</code></pre> time_complexity.cpp<pre><code>/* 対数オーダー(再帰による実装) */\nint logRecur(int n) {\n if (n &lt;= 1)\n return 0;\n return logRecur(n / 2) + 1;\n}\n</code></pre> time_complexity.java<pre><code>/* 対数オーダー(再帰による実装) */\nint logRecur(int n) {\n if (n &lt;= 1)\n return 0;\n return logRecur(n / 2) + 1;\n}\n</code></pre> time_complexity.cs<pre><code>/* 対数オーダー(再帰による実装) */\nint LogRecur(int n) {\n if (n &lt;= 1) return 0;\n return LogRecur(n / 2) + 1;\n}\n</code></pre> time_complexity.go<pre><code>/* 対数オーダー(再帰による実装)*/\nfunc logRecur(n int) int {\n if n &lt;= 1 {\n return 0\n }\n return logRecur(n/2) + 1\n}\n</code></pre> time_complexity.swift<pre><code>/* 対数オーダー(再帰による実装) */\nfunc logRecur(n: Int) -&gt; Int {\n if n &lt;= 1 {\n return 0\n }\n return logRecur(n: n / 2) + 1\n}\n</code></pre> time_complexity.js<pre><code>/* 対数オーダー(再帰による実装) */\nfunction logRecur(n) {\n if (n &lt;= 1) return 0;\n return logRecur(n / 2) + 1;\n}\n</code></pre> time_complexity.ts<pre><code>/* 対数オーダー(再帰による実装) */\nfunction logRecur(n: number): number {\n if (n &lt;= 1) return 0;\n return logRecur(n / 2) + 1;\n}\n</code></pre> time_complexity.dart<pre><code>/* 対数オーダー(再帰による実装) */\nint logRecur(int n) {\n if (n &lt;= 1) return 0;\n return logRecur(n ~/ 2) + 1;\n}\n</code></pre> time_complexity.rs<pre><code>/* 対数オーダー(再帰による実装) */\nfn log_recur(n: i32) -&gt; i32 {\n if n &lt;= 1 {\n return 0;\n }\n log_recur(n / 2) + 1\n}\n</code></pre> time_complexity.c<pre><code>/* 対数オーダー(再帰による実装) */\nint logRecur(int n) {\n if (n &lt;= 1)\n return 0;\n return logRecur(n / 2) + 1;\n}\n</code></pre> time_complexity.kt<pre><code>/* 対数オーダー(再帰による実装) */\nfun logRecur(n: Int): Int {\n if (n &lt;= 1)\n return 0\n return logRecur(n / 2) + 1\n}\n</code></pre> time_complexity.rb<pre><code>### 対数オーダー(再帰による実装)###\ndef log_recur(n)\n return 0 unless n &gt; 1\n log_recur(n / 2) + 1\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>対数のオーダーは、分割統治のアルゴリズムによく現れ、「一つを複数に分ける」「複雑なものを単純にする」という考え方を表します。増加が遅く、定数のオーダーに次いで理想的な時間計算量です。</p> <p>\\(O(\\log n)\\) の底は何ですか?</p> <p>厳密には、「\\(m\\) 分の 1 にする」場合の時間計算量は \\(O(\\log_m n)\\) です。底の変換公式を使えば、異なる底でも同じ時間計算量になることがわかります。</p> \\[ O(\\log_m n) = O(\\log_k n / \\log_k m) = O(\\log_k n) \\] <p>つまり、底 \\(m\\) は計算量を変えずに変換できます。そのため通常は底 \\(m\\) を省き、対数のオーダーを \\(O(\\log n)\\) と書きます。</p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#6-on-log-n","level":3,"title":"6.   線形対数のオーダー \\(O(n \\log n)\\)","text":"<p>線形対数のオーダーは、二つのループの時間計算量がそれぞれ \\(O(\\log n)\\) と \\(O(n)\\) の入れ子の処理によく現れます。関連するコードを次に示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py<pre><code>def linear_log_recur(n: int) -&gt; int:\n \"\"\"線形対数オーダー\"\"\"\n if n &lt;= 1:\n return 1\n # 2 つに分割し、部分問題の大きさを半分にする\n count = linear_log_recur(n // 2) + linear_log_recur(n // 2)\n # 現在の部分問題には n 回の操作が含まれる\n for _ in range(n):\n count += 1\n return count\n</code></pre> time_complexity.cpp<pre><code>/* 線形対数オーダー */\nint linearLogRecur(int n) {\n if (n &lt;= 1)\n return 1;\n int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (int i = 0; i &lt; n; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.java<pre><code>/* 線形対数オーダー */\nint linearLogRecur(int n) {\n if (n &lt;= 1)\n return 1;\n int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (int i = 0; i &lt; n; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.cs<pre><code>/* 線形対数オーダー */\nint LinearLogRecur(int n) {\n if (n &lt;= 1) return 1;\n int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2);\n for (int i = 0; i &lt; n; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.go<pre><code>/* 線形対数オーダー */\nfunc linearLogRecur(n int) int {\n if n &lt;= 1 {\n return 1\n }\n count := linearLogRecur(n/2) + linearLogRecur(n/2)\n for i := 0; i &lt; n; i++ {\n count++\n }\n return count\n}\n</code></pre> time_complexity.swift<pre><code>/* 線形対数オーダー */\nfunc linearLogRecur(n: Int) -&gt; Int {\n if n &lt;= 1 {\n return 1\n }\n var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2)\n for _ in stride(from: 0, to: n, by: 1) {\n count += 1\n }\n return count\n}\n</code></pre> time_complexity.js<pre><code>/* 線形対数オーダー */\nfunction linearLogRecur(n) {\n if (n &lt;= 1) return 1;\n let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (let i = 0; i &lt; n; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.ts<pre><code>/* 線形対数オーダー */\nfunction linearLogRecur(n: number): number {\n if (n &lt;= 1) return 1;\n let count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (let i = 0; i &lt; n; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.dart<pre><code>/* 線形対数オーダー */\nint linearLogRecur(int n) {\n if (n &lt;= 1) return 1;\n int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2);\n for (var i = 0; i &lt; n; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.rs<pre><code>/* 線形対数オーダー */\nfn linear_log_recur(n: i32) -&gt; i32 {\n if n &lt;= 1 {\n return 1;\n }\n let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2);\n for _ in 0..n {\n count += 1;\n }\n return count;\n}\n</code></pre> time_complexity.c<pre><code>/* 線形対数オーダー */\nint linearLogRecur(int n) {\n if (n &lt;= 1)\n return 1;\n int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);\n for (int i = 0; i &lt; n; i++) {\n count++;\n }\n return count;\n}\n</code></pre> time_complexity.kt<pre><code>/* 線形対数オーダー */\nfun linearLogRecur(n: Int): Int {\n if (n &lt;= 1)\n return 1\n var count = linearLogRecur(n / 2) + linearLogRecur(n / 2)\n for (i in 0..&lt;n) {\n count++\n }\n return count\n}\n</code></pre> time_complexity.rb<pre><code>### 線形対数オーダー ###\ndef linear_log_recur(n)\n return 1 unless n &gt; 1\n\n count = linear_log_recur(n / 2) + linear_log_recur(n / 2)\n (0...n).each { count += 1 }\n\n count\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 2-13 に、線形対数のオーダーが生じる仕組みを示します。二分木の各層の総操作回数は \\(n\\) で、層数は \\(\\log_2 n + 1\\) なので、時間計算量は \\(O(n \\log n)\\) です。</p> <p></p> <p> 図 2-13   線形対数の時間計算量 </p> <p>クイックソート、マージソート、ヒープソートなど、主要なソートの時間計算量は通常 \\(O(n \\log n)\\) です。</p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#7-on","level":3,"title":"7.   階乗のオーダー \\(O(n!)\\)","text":"<p>階乗のオーダーは、数学の「順列列挙」問題に対応します。互いに異なる \\(n\\) 個の要素のすべての並べ方を求めると、その数は次のようになります。</p> \\[ n! = n \\times (n - 1) \\times (n - 2) \\times \\dots \\times 2 \\times 1 \\] <p>階乗のオーダーの処理は、通常、再帰で実装します。図 2-14 と次のコードでは、最初の層で \\(n\\) 個、次の層で \\(n - 1\\) 個に分岐し、これを第 \\(n\\) 層まで続けます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby time_complexity.py<pre><code>def factorial_recur(n: int) -&gt; int:\n \"\"\"階乗オーダー(再帰による実装)\"\"\"\n if n == 0:\n return 1\n count = 0\n # 1 個から n 個に分裂する\n for _ in range(n):\n count += factorial_recur(n - 1)\n return count\n</code></pre> time_complexity.cpp<pre><code>/* 階乗オーダー(再帰による実装) */\nint factorialRecur(int n) {\n if (n == 0)\n return 1;\n int count = 0;\n // 1 個から n 個に分裂する\n for (int i = 0; i &lt; n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n</code></pre> time_complexity.java<pre><code>/* 階乗オーダー(再帰による実装) */\nint factorialRecur(int n) {\n if (n == 0)\n return 1;\n int count = 0;\n // 1 個から n 個に分裂する\n for (int i = 0; i &lt; n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n</code></pre> time_complexity.cs<pre><code>/* 階乗オーダー(再帰による実装) */\nint FactorialRecur(int n) {\n if (n == 0) return 1;\n int count = 0;\n // 1 個から n 個に分裂する\n for (int i = 0; i &lt; n; i++) {\n count += FactorialRecur(n - 1);\n }\n return count;\n}\n</code></pre> time_complexity.go<pre><code>/* 階乗オーダー(再帰による実装) */\nfunc factorialRecur(n int) int {\n if n == 0 {\n return 1\n }\n count := 0\n // 1 個から n 個に分裂する\n for i := 0; i &lt; n; i++ {\n count += factorialRecur(n - 1)\n }\n return count\n}\n</code></pre> time_complexity.swift<pre><code>/* 階乗オーダー(再帰による実装) */\nfunc factorialRecur(n: Int) -&gt; Int {\n if n == 0 {\n return 1\n }\n var count = 0\n // 1 個から n 個に分裂する\n for _ in 0 ..&lt; n {\n count += factorialRecur(n: n - 1)\n }\n return count\n}\n</code></pre> time_complexity.js<pre><code>/* 階乗オーダー(再帰による実装) */\nfunction factorialRecur(n) {\n if (n === 0) return 1;\n let count = 0;\n // 1 個から n 個に分裂する\n for (let i = 0; i &lt; n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n</code></pre> time_complexity.ts<pre><code>/* 階乗オーダー(再帰による実装) */\nfunction factorialRecur(n: number): number {\n if (n === 0) return 1;\n let count = 0;\n // 1 個から n 個に分裂する\n for (let i = 0; i &lt; n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n</code></pre> time_complexity.dart<pre><code>/* 階乗オーダー(再帰による実装) */\nint factorialRecur(int n) {\n if (n == 0) return 1;\n int count = 0;\n // 1 個から n 個に分裂する\n for (var i = 0; i &lt; n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n</code></pre> time_complexity.rs<pre><code>/* 階乗オーダー(再帰による実装) */\nfn factorial_recur(n: i32) -&gt; i32 {\n if n == 0 {\n return 1;\n }\n let mut count = 0;\n // 1 個から n 個に分裂する\n for _ in 0..n {\n count += factorial_recur(n - 1);\n }\n count\n}\n</code></pre> time_complexity.c<pre><code>/* 階乗オーダー(再帰による実装) */\nint factorialRecur(int n) {\n if (n == 0)\n return 1;\n int count = 0;\n for (int i = 0; i &lt; n; i++) {\n count += factorialRecur(n - 1);\n }\n return count;\n}\n</code></pre> time_complexity.kt<pre><code>/* 階乗オーダー(再帰による実装) */\nfun factorialRecur(n: Int): Int {\n if (n == 0)\n return 1\n var count = 0\n // 1 個から n 個に分裂する\n for (i in 0..&lt;n) {\n count += factorialRecur(n - 1)\n }\n return count\n}\n</code></pre> time_complexity.rb<pre><code>### 階乗オーダー(再帰による実装)###\ndef factorial_recur(n)\n return 1 if n == 0\n\n count = 0\n # 1 個から n 個に分裂する\n (0...n).each { count += factorial_recur(n - 1) }\n\n count\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p></p> <p> 図 2-14   階乗の時間計算量 </p> <p>\\(n \\geq 4\\) なら常に \\(n! &gt; 2^n\\) なので、階乗は指数より速く増えます。したがって、\\(n\\) が大きい場合には、これも許容できません。</p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_computational_complexity/time_complexity/#235","level":2,"title":"2.3.5   最悪・最良・平均時間計算量","text":"<p>アルゴリズムの時間効率は一定とは限らず、入力データの分布によって変わります。長さ \\(n\\) の配列 <code>nums</code> に、\\(1\\) から \\(n\\) の数が一つずつ、ランダムな順に入っているとします。要素 \\(1\\) のインデックスを返す問題を考えると、次のことがわかります。</p> <ul> <li><code>nums = [?, ?, ..., 1]</code>、つまり末尾が \\(1\\) なら、配列全体を走査するため、最悪時間計算量 \\(O(n)\\) になります。</li> <li><code>nums = [1, ?, ?, ...]</code>、つまり先頭が \\(1\\) なら、配列がどれほど長くても、それ以上走査する必要がなく、最良時間計算量 \\(\\Omega(1)\\) になります。</li> </ul> <p>「最悪時間計算量」は関数の漸近上界に対応し、ビッグ \\(O\\) 記法で表します。対応する「最良時間計算量」は漸近下界で、\\(\\Omega\\) 記法を使います。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby worst_best_time_complexity.py<pre><code>def random_numbers(n: int) -&gt; list[int]:\n \"\"\"要素が 1, 2, ..., n で、順序をシャッフルした配列を生成する\"\"\"\n # 配列 nums =: 1, 2, 3, ..., n を生成する\n nums = [i for i in range(1, n + 1)]\n # 配列の要素をランダムに並べ替える\n random.shuffle(nums)\n return nums\n\ndef find_one(nums: list[int]) -&gt; int:\n \"\"\"配列 nums 内で数値 1 のインデックスを探索する\"\"\"\n for i in range(len(nums)):\n # 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n # 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if nums[i] == 1:\n return i\n return -1\n</code></pre> worst_best_time_complexity.cpp<pre><code>/* 要素が { 1, 2, ..., n } で、順序をシャッフルした配列を生成する */\nvector&lt;int&gt; randomNumbers(int n) {\n vector&lt;int&gt; nums(n);\n // 配列 nums = { 1, 2, 3, ..., n } を生成する\n for (int i = 0; i &lt; n; i++) {\n nums[i] = i + 1;\n }\n // システム時刻を使って乱数のシードを生成する\n unsigned seed = chrono::system_clock::now().time_since_epoch().count();\n // 配列の要素をランダムに並べ替える\n shuffle(nums.begin(), nums.end(), default_random_engine(seed));\n return nums;\n}\n\n/* 配列 nums 内で数値 1 のインデックスを探索する */\nint findOne(vector&lt;int&gt; &amp;nums) {\n for (int i = 0; i &lt; nums.size(); i++) {\n // 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n // 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n</code></pre> worst_best_time_complexity.java<pre><code>/* 要素が { 1, 2, ..., n } で、順序をシャッフルした配列を生成する */\nint[] randomNumbers(int n) {\n Integer[] nums = new Integer[n];\n // 配列 nums = { 1, 2, 3, ..., n } を生成する\n for (int i = 0; i &lt; n; i++) {\n nums[i] = i + 1;\n }\n // 配列の要素をランダムに並べ替える\n Collections.shuffle(Arrays.asList(nums));\n // Integer[] -&gt; int[]\n int[] res = new int[n];\n for (int i = 0; i &lt; n; i++) {\n res[i] = nums[i];\n }\n return res;\n}\n\n/* 配列 nums 内で数値 1 のインデックスを探索する */\nint findOne(int[] nums) {\n for (int i = 0; i &lt; nums.length; i++) {\n // 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n // 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n</code></pre> worst_best_time_complexity.cs<pre><code>/* 要素が { 1, 2, ..., n } で、順序をシャッフルした配列を生成する */\nint[] RandomNumbers(int n) {\n int[] nums = new int[n];\n // 配列 nums = { 1, 2, 3, ..., n } を生成する\n for (int i = 0; i &lt; n; i++) {\n nums[i] = i + 1;\n }\n\n // 配列の要素をランダムに並べ替える\n for (int i = 0; i &lt; nums.Length; i++) {\n int index = new Random().Next(i, nums.Length);\n (nums[i], nums[index]) = (nums[index], nums[i]);\n }\n return nums;\n}\n\n/* 配列 nums 内で数値 1 のインデックスを探索する */\nint FindOne(int[] nums) {\n for (int i = 0; i &lt; nums.Length; i++) {\n // 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n // 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n</code></pre> worst_best_time_complexity.go<pre><code>/* 要素が { 1, 2, ..., n } で、順序をシャッフルした配列を生成する */\nfunc randomNumbers(n int) []int {\n nums := make([]int, n)\n // 配列 nums = { 1, 2, 3, ..., n } を生成する\n for i := 0; i &lt; n; i++ {\n nums[i] = i + 1\n }\n // 配列の要素をランダムに並べ替える\n rand.Shuffle(len(nums), func(i, j int) {\n nums[i], nums[j] = nums[j], nums[i]\n })\n return nums\n}\n\n/* 配列 nums 内で数値 1 のインデックスを探索する */\nfunc findOne(nums []int) int {\n for i := 0; i &lt; len(nums); i++ {\n // 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n // 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if nums[i] == 1 {\n return i\n }\n }\n return -1\n}\n</code></pre> worst_best_time_complexity.swift<pre><code>/* 要素が { 1, 2, ..., n } で、順序をシャッフルした配列を生成する */\nfunc randomNumbers(n: Int) -&gt; [Int] {\n // 配列 nums = { 1, 2, 3, ..., n } を生成する\n var nums = Array(1 ... n)\n // 配列の要素をランダムに並べ替える\n nums.shuffle()\n return nums\n}\n\n/* 配列 nums 内で数値 1 のインデックスを探索する */\nfunc findOne(nums: [Int]) -&gt; Int {\n for i in nums.indices {\n // 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n // 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if nums[i] == 1 {\n return i\n }\n }\n return -1\n}\n</code></pre> worst_best_time_complexity.js<pre><code>/* 要素が { 1, 2, ..., n } で、順序をシャッフルした配列を生成する */\nfunction randomNumbers(n) {\n const nums = Array(n);\n // 配列 nums = { 1, 2, 3, ..., n } を生成する\n for (let i = 0; i &lt; n; i++) {\n nums[i] = i + 1;\n }\n // 配列の要素をランダムに並べ替える\n for (let i = 0; i &lt; n; i++) {\n const r = Math.floor(Math.random() * (i + 1));\n const temp = nums[i];\n nums[i] = nums[r];\n nums[r] = temp;\n }\n return nums;\n}\n\n/* 配列 nums 内で数値 1 のインデックスを探索する */\nfunction findOne(nums) {\n for (let i = 0; i &lt; nums.length; i++) {\n // 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n // 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if (nums[i] === 1) {\n return i;\n }\n }\n return -1;\n}\n</code></pre> worst_best_time_complexity.ts<pre><code>/* 要素が { 1, 2, ..., n } で、順序をシャッフルした配列を生成する */\nfunction randomNumbers(n: number): number[] {\n const nums = Array(n);\n // 配列 nums = { 1, 2, 3, ..., n } を生成する\n for (let i = 0; i &lt; n; i++) {\n nums[i] = i + 1;\n }\n // 配列の要素をランダムに並べ替える\n for (let i = 0; i &lt; n; i++) {\n const r = Math.floor(Math.random() * (i + 1));\n const temp = nums[i];\n nums[i] = nums[r];\n nums[r] = temp;\n }\n return nums;\n}\n\n/* 配列 nums 内で数値 1 のインデックスを探索する */\nfunction findOne(nums: number[]): number {\n for (let i = 0; i &lt; nums.length; i++) {\n // 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n // 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if (nums[i] === 1) {\n return i;\n }\n }\n return -1;\n}\n</code></pre> worst_best_time_complexity.dart<pre><code>/* 要素が { 1, 2, ..., n } で、順序をシャッフルした配列を生成する */\nList&lt;int&gt; randomNumbers(int n) {\n final nums = List.filled(n, 0);\n // 配列 nums = { 1, 2, 3, ..., n } を生成する\n for (var i = 0; i &lt; n; i++) {\n nums[i] = i + 1;\n }\n // 配列の要素をランダムに並べ替える\n nums.shuffle();\n\n return nums;\n}\n\n/* 配列 nums 内で数値 1 のインデックスを探索する */\nint findOne(List&lt;int&gt; nums) {\n for (var i = 0; i &lt; nums.length; i++) {\n // 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n // 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if (nums[i] == 1) return i;\n }\n\n return -1;\n}\n</code></pre> worst_best_time_complexity.rs<pre><code>/* 要素が { 1, 2, ..., n } で、順序をシャッフルした配列を生成する */\nfn random_numbers(n: i32) -&gt; Vec&lt;i32&gt; {\n // 配列 nums = { 1, 2, 3, ..., n } を生成する\n let mut nums = (1..=n).collect::&lt;Vec&lt;i32&gt;&gt;();\n // 配列の要素をランダムに並べ替える\n nums.shuffle(&amp;mut thread_rng());\n nums\n}\n\n/* 配列 nums 内で数値 1 のインデックスを探索する */\nfn find_one(nums: &amp;[i32]) -&gt; Option&lt;usize&gt; {\n for i in 0..nums.len() {\n // 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n // 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if nums[i] == 1 {\n return Some(i);\n }\n }\n None\n}\n</code></pre> worst_best_time_complexity.c<pre><code>/* 要素が { 1, 2, ..., n } で、順序をシャッフルした配列を生成する */\nint *randomNumbers(int n) {\n // ヒープ領域を確保する(要素数 n 、要素型 int の一次元可変長配列を作成する)\n int *nums = (int *)malloc(n * sizeof(int));\n // 配列 nums = { 1, 2, 3, ..., n } を生成する\n for (int i = 0; i &lt; n; i++) {\n nums[i] = i + 1;\n }\n // 配列の要素をランダムに並べ替える\n for (int i = n - 1; i &gt; 0; i--) {\n int j = rand() % (i + 1);\n int temp = nums[i];\n nums[i] = nums[j];\n nums[j] = temp;\n }\n return nums;\n}\n\n/* 配列 nums 内で数値 1 のインデックスを探索する */\nint findOne(int *nums, int n) {\n for (int i = 0; i &lt; n; i++) {\n // 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n // 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if (nums[i] == 1)\n return i;\n }\n return -1;\n}\n</code></pre> worst_best_time_complexity.kt<pre><code>/* 要素が { 1, 2, ..., n } で、順序をシャッフルした配列を生成する */\nfun randomNumbers(n: Int): Array&lt;Int?&gt; {\n val nums = IntArray(n)\n // 配列 nums = { 1, 2, 3, ..., n } を生成する\n for (i in 0..&lt;n) {\n nums[i] = i + 1\n }\n // 配列の要素をランダムに並べ替える\n nums.shuffle()\n val res = arrayOfNulls&lt;Int&gt;(n)\n for (i in 0..&lt;n) {\n res[i] = nums[i]\n }\n return res\n}\n\n/* 配列 nums 内で数値 1 のインデックスを探索する */\nfun findOne(nums: Array&lt;Int?&gt;): Int {\n for (i in nums.indices) {\n // 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n // 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n if (nums[i] == 1)\n return i\n }\n return -1\n}\n</code></pre> worst_best_time_complexity.rb<pre><code>### 要素が 1, 2, ..., n で、順序をシャッフルした配列を生成する ###\ndef random_numbers(n)\n # 配列 nums =: 1, 2, 3, ..., n を生成する\n nums = Array.new(n) { |i| i + 1 }\n # 配列の要素をランダムに並べ替える\n nums.shuffle!\nend\n\n### 配列 nums 内で数値 1 のインデックスを探索する ###\ndef find_one(nums)\n for i in 0...nums.length\n # 要素 1 が配列の先頭にある場合、最良時間計算量 O(1) となる\n # 要素 1 が配列の末尾にある場合、最悪時間計算量 O(n) となる\n return i if nums[i] == 1\n end\n\n -1\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>実際には、最良時間計算量はあまり使いません。通常、ごく低い確率でしか起こらず、誤解を招くことがあるためです。最悪時間計算量の方が実用的であり、効率の安全な目安を与えてくれるので、安心してアルゴリズムを使えます。</p> <p>上の例から、最悪と最良は「特殊なデータ分布」でしか現れず、その確率が低い場合は実際の効率を十分に反映しないとわかります。一方、平均時間計算量は、ランダムな入力での実行効率を表し、\\(\\Theta\\) 記法を使います。</p> <p>一部のアルゴリズムでは、ランダムな分布での平均を簡単に求められます。上の例では配列がランダムに並んでおり、\\(1\\) がどの位置にある確率も等しいので、平均ループ回数は配列の長さの半分 \\(n / 2\\) です。平均時間計算量は \\(\\Theta(n / 2) = \\Theta(n)\\) になります。</p> <p>複雑なアルゴリズムでは、データの分布全体での期待値を求めにくく、平均時間計算量の計算は難しくなります。その場合は通常、最悪時間計算量を効率の基準にします。</p> <p>\\(\\Theta\\) 記号をあまり見かけないのはなぜですか?</p> <p>\\(O\\) 記号が広く親しまれているためか、平均時間計算量にもよく使われます。ただし、厳密にはこの使い方は正式なものではありません。本書やほかの資料で「平均時間計算量 \\(O(n)\\)」という表現を見たら、\\(\\Theta(n)\\) と読み替えてください。</p>","path":["第 2 章   計算量の解析","2.3   時間計算量"],"tags":[]},{"location":"chapter_data_structure/","level":1,"title":"第 3 章   データ構造","text":"<p>概要</p> <p>データ構造は、頑丈でありながら多彩な形を持つ骨組みのようです。</p> <p>データを秩序立てて整理する設計図となり、その上でアルゴリズムが生き生きと働きます。</p>","path":["第 3 章   データ構造"],"tags":[]},{"location":"chapter_data_structure/#_1","level":2,"title":"この章の内容","text":"<ul> <li>3.1   データ構造の分類</li> <li>3.2   基本データ型</li> <li>3.3   数値の符号化 *</li> <li>3.4   文字エンコーディング *</li> <li>3.5   まとめ</li> <li>3.6   練習問題</li> </ul>","path":["第 3 章   データ構造"],"tags":[]},{"location":"chapter_data_structure/basic_data_types/","level":1,"title":"3.2   基本データ型","text":"<p>コンピュータのデータというと、文章、画像、動画、音声、3D モデルなどが思い浮かびます。形はさまざまですが、いずれも基本データ型を組み合わせて作られています。</p> <p>基本データ型は、CPU が直接演算できる型で、アルゴリズムでもそのまま使います。主に次のものがあります。</p> <ul> <li>整数型:<code>byte</code>、<code>short</code>、<code>int</code>、<code>long</code>。</li> <li>浮動小数点型:<code>float</code>、<code>double</code>。小数を表します。</li> <li>文字型:<code>char</code>。各言語の文字、句読点、さらには絵文字などを表します。</li> <li>ブール型:<code>bool</code>。「はい」「いいえ」の判断を表します。</li> </ul> <p>基本データ型は、コンピュータ内では二進数として保存されます。二進数の 1 桁が \\(1\\) ビットです。現代のほとんどの OS では、\\(1\\) バイト(byte)は \\(8\\) ビット(bit)で構成されます。</p> <p>基本データ型が取り得る値の範囲は、使う空間の大きさによって決まります。Java を例に見てみましょう。</p> <ul> <li>整数型 <code>byte</code> は \\(1\\) バイト = \\(8\\) ビットを使い、\\(2^{8}\\) 個の数を表せます。</li> <li>整数型 <code>int</code> は \\(4\\) バイト = \\(32\\) ビットを使い、\\(2^{32}\\) 個の数を表せます。</li> </ul> <p>表 3-1 に、Java の基本データ型の使用量、値の範囲、デフォルト値を示します。暗記する必要はなく、大まかに理解し、必要なときに表を確認すれば十分です。</p> <p> 表 3-1   基本データ型の使用量と値の範囲 </p> 型 記号 使用量 最小値 最大値 デフォルト値 整数 <code>byte</code> 1 バイト \\(-2^7\\) (\\(-128\\)) \\(2^7 - 1\\) (\\(127\\)) \\(0\\) <code>short</code> 2 バイト \\(-2^{15}\\) \\(2^{15} - 1\\) \\(0\\) <code>int</code> 4 バイト \\(-2^{31}\\) \\(2^{31} - 1\\) \\(0\\) <code>long</code> 8 バイト \\(-2^{63}\\) \\(2^{63} - 1\\) \\(0\\) 浮動小数点数 <code>float</code> 4 バイト \\(1.175 \\times 10^{-38}\\) \\(3.403 \\times 10^{38}\\) \\(0.0\\text{f}\\) <code>double</code> 8 バイト \\(2.225 \\times 10^{-308}\\) \\(1.798 \\times 10^{308}\\) \\(0.0\\) 文字 <code>char</code> 2 バイト \\(0\\) \\(2^{16} - 1\\) \\(0\\) ブール <code>bool</code> 1 バイト \\(\\text{false}\\) \\(\\text{true}\\) \\(\\text{false}\\) <p>この表は Java の基本データ型を対象にしています。言語ごとに型の定義があり、使用量、値の範囲、デフォルト値が異なる場合があります。</p> <ul> <li>Python の整数型 <code>int</code> は、利用可能なメモリの範囲内で任意の大きさを扱えます。<code>float</code> は 64 ビットの倍精度で、<code>char</code> 型はなく、1 文字は長さ 1 の文字列 <code>str</code> です。</li> <li>C と C++ は基本データ型の大きさを厳密には定めておらず、実装や環境によって異なります。表 3-1 は、Linux や macOS を含む 64 ビット Unix 系 OS で使われる LP64 データモデルに従っています。</li> <li>C と C++ の <code>char</code> は 1 バイトです。多くの言語では、文字の大きさは文字コード方式に依存します。詳しくは「文字エンコーディング」の節で説明します。</li> <li>ブール値は 1 ビット(\\(0\\) または \\(1\\))で表せますが、メモリでは通常、1 バイトとして保存されます。現代の CPU が通常、1 バイトを最小のアドレス指定単位とするためです。</li> </ul> <p>では、基本データ型とデータ構造はどう関係するのでしょうか。データ構造は、コンピュータ内でデータを整理・格納する方法です。この言葉の中心は「データ」ではなく「構造」にあります。</p> <p>「数が一列に並んだもの」を表すなら、自然に配列が思い浮かびます。配列の線形構造が、隣接関係や順序を表せるためです。格納するのが整数 <code>int</code>、小数 <code>float</code>、文字 <code>char</code> のどれかは、「データ構造」とは別のことです。</p> <p>つまり、基本データ型はデータの「内容の型」を、データ構造は「整理の仕方」を提供します。次のコードでは、同じデータ構造(配列)を使い、<code>int</code>、<code>float</code>、<code>char</code>、<code>bool</code> など、異なる基本データ型を格納・表現します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code># さまざまな基本データ型で配列を初期化\nnumbers: list[int] = [0] * 5\ndecimals: list[float] = [0.0] * 5\n# Python の文字は、実際には長さ 1 の文字列\ncharacters: list[str] = ['0'] * 5\nbools: list[bool] = [False] * 5\n# Python のリストには、さまざまな基本データ型やオブジェクトへの参照を自由に格納できる\ndata = [0, 0.0, 'a', False, ListNode(0)]\n</code></pre> <pre><code>// さまざまな基本データ型で配列を初期化\nint numbers[5];\nfloat decimals[5];\nchar characters[5];\nbool bools[5];\n</code></pre> <pre><code>// さまざまな基本データ型で配列を初期化\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nboolean[] bools = new boolean[5];\n</code></pre> <pre><code>// さまざまな基本データ型で配列を初期化\nint[] numbers = new int[5];\nfloat[] decimals = new float[5];\nchar[] characters = new char[5];\nbool[] bools = new bool[5];\n</code></pre> <pre><code>// さまざまな基本データ型で配列を初期化\nvar numbers = [5]int{}\nvar decimals = [5]float64{}\nvar characters = [5]byte{}\nvar bools = [5]bool{}\n</code></pre> <pre><code>// さまざまな基本データ型で配列を初期化\nlet numbers = Array(repeating: 0, count: 5)\nlet decimals = Array(repeating: 0.0, count: 5)\nlet characters: [Character] = Array(repeating: \"a\", count: 5)\nlet bools = Array(repeating: false, count: 5)\n</code></pre> <pre><code>// JavaScript の配列には、さまざまな基本データ型やオブジェクトを自由に格納できる\nconst array = [0, 0.0, 'a', false];\n</code></pre> <pre><code>// さまざまな基本データ型で配列を初期化\nconst numbers: number[] = [];\nconst characters: string[] = [];\nconst bools: boolean[] = [];\n</code></pre> <pre><code>// さまざまな基本データ型で配列を初期化\nList&lt;int&gt; numbers = List.filled(5, 0);\nList&lt;double&gt; decimals = List.filled(5, 0.0);\nList&lt;String&gt; characters = List.filled(5, 'a');\nList&lt;bool&gt; bools = List.filled(5, false);\n</code></pre> <pre><code>// さまざまな基本データ型で配列を初期化\nlet numbers: Vec&lt;i32&gt; = vec![0; 5];\nlet decimals: Vec&lt;f32&gt; = vec![0.0; 5];\nlet characters: Vec&lt;char&gt; = vec!['0'; 5];\nlet bools: Vec&lt;bool&gt; = vec![false; 5];\n</code></pre> <pre><code>// さまざまな基本データ型で配列を初期化\nint numbers[10];\nfloat decimals[10];\nchar characters[10];\nbool bools[10];\n</code></pre> <pre><code>// さまざまな基本データ型で配列を初期化\nval numbers = IntArray(5)\nval decinals = FloatArray(5)\nval characters = CharArray(5)\nval bools = BooleanArray(5)\n</code></pre> <pre><code># Ruby のリストには、さまざまな基本データ型やオブジェクトへの参照を自由に格納できる\ndata = [0, 0.0, 'a', false, ListNode(0)]\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 3 章   データ構造","3.2   基本データ型"],"tags":[]},{"location":"chapter_data_structure/character_encoding/","level":1,"title":"3.4   文字エンコーディング *","text":"<p>コンピュータでは、すべてのデータが二進数の形で保存されており、文字 <code>char</code> も例外ではありません。文字を表すには、それぞれの文字と二進数の一対一の対応を定めた「文字集合」を用意する必要があります。文字集合があれば、コンピュータは対応表を参照して二進数を文字に変換できます。</p>","path":["第 3 章   データ構造","3.4   文字エンコーディング *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#341-ascii","level":2,"title":"3.4.1   ASCII 文字集合","text":"<p>ASCII コードは最初期の文字集合で、正式名称は American Standard Code for Information Interchange(情報交換用米国標準コード)です。7 ビットの二進数(1 バイトの下位 7 ビット)で 1 文字を表し、最大 128 種類の文字を表現できます。図 3-6 のように、ASCII コードには英字の大文字と小文字、数字の 0 ~ 9、一部の句読点や記号、改行やタブなどの制御文字が含まれています。</p> <p></p> <p> 図 3-6   ASCII コード </p> <p>しかし、ASCII コードで表せるのは英語だけです。コンピュータの利用が世界に広がるにつれて、より多くの言語を表現できる EASCII 文字集合が生まれました。ASCII の 7 ビットを 8 ビットに拡張したもので、256 種類の文字を表現できます。</p> <p>世界各地では、それぞれの地域に適した EASCII 文字集合が次々に登場しました。いずれも先頭の 128 文字は ASCII コードと共通ですが、後半の 128 文字は各言語の需要に合わせて異なる定義になっています。</p>","path":["第 3 章   データ構造","3.4   文字エンコーディング *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#342-gbk","level":2,"title":"3.4.2   GBK 文字集合","text":"<p>その後、EASCII コードでも、多くの言語が必要とする文字数には対応できないことがわかりました。例えば漢字は 10 万字近くあり、日常的に使うものだけでも数千字に上ります。中国の国家標準総局は 1980 年に GB2312 文字集合を発表しました。6763 字の漢字を収録し、コンピュータによる漢字処理の基本的な需要を満たすものでした。</p> <p>ただし、GB2312 では一部のまれな漢字や繁体字を扱えません。GBK 文字集合は GB2312 を拡張したもので、合計 21886 字の漢字を収録しています。GBK の符号化方式では、ASCII 文字を 1 バイト、漢字を 2 バイトで表します。</p>","path":["第 3 章   データ構造","3.4   文字エンコーディング *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#343-unicode","level":2,"title":"3.4.3   Unicode 文字集合","text":"<p>コンピュータ技術の急速な発展とともに、多種多様な文字集合や符号化規格が生まれましたが、それに伴って多くの問題も生じました。一つは、これらの文字集合が通常は特定の言語の文字しか定義しておらず、多言語環境では正しく動作しないことです。もう一つは、同じ言語にも複数の文字集合の規格があり、2 台のコンピュータが異なる符号化規格を使っていると、情報のやり取りで文字化けが起こることです。</p> <p>当時の研究者たちは、世界中のあらゆる言語や記号を収録する、十分に網羅的な文字集合を作れば、多言語環境や文字化けの問題を解決できるのではないかと考えました。この発想から、大規模で網羅的な文字集合 Unicode が誕生しました。</p> <p>Unicode は中国語では「统一码」と呼ばれ、理論上は 100 万以上の文字を収容できます。世界中の文字を統一された文字集合に取り込み、さまざまな言語の文字を処理・表示するための共通の文字集合を提供することで、符号化規格の違いによる文字化けを減らすことを目指しています。1991 年の公開以来、新しい言語や文字が継続的に追加されています。2022 年 9 月時点では、各言語の文字、記号、さらには絵文字など、149186 文字が収録されています。</p> <p>共通の文字集合である Unicode は、本質的には各文字に一意の「コードポイント」(文字番号)を割り当てるものです。その範囲は U+0000 から U+10FFFF で、統一された文字番号の空間を構成しています。ただし、Unicode はこれらの文字のコードポイントをコンピュータにどのように保存するかを定めていません。ここで、長さの異なる Unicode コードポイントが一つのテキストに混在するとき、システムはどのように文字を読み取るのかという疑問が生じます。例えば、長さが 2 バイトの符号が与えられたとき、それが 2 バイトの 1 文字なのか、1 バイトの 2 文字なのかを、どう判断するのでしょうか。</p> <p>この問題に対する直接的な解決策は、すべての文字を同じ長さの符号で保存することです。図 3-7 では、「Hello」の各文字は 1 バイト、「算法」の各文字は 2 バイトを占めています。上位ビットを 0 で埋めれば、「Hello 算法」のすべての文字を 2 バイトの長さに符号化できます。これにより、システムは 2 バイトごとに 1 文字を読み取り、元の語句を復元できます。</p> <p></p> <p> 図 3-7   Unicode の符号化例 </p> <p>しかし、ASCII コードからわかるように、英語の符号化に必要なのは 1 バイトだけです。上記の方式では、英語のテキストが占める容量は ASCII の場合の 2 倍となり、多くのメモリを無駄にします。そのため、より効率的な Unicode の符号化方式が必要です。</p>","path":["第 3 章   データ構造","3.4   文字エンコーディング *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#344-utf-8","level":2,"title":"3.4.4   UTF-8 エンコーディング","text":"<p>現在、UTF-8 は世界で最も広く使われている Unicode の符号化方式です。可変長の符号化方式であり、文字の複雑さに応じて 1 文字を 1 ~ 4 バイトで表します。ASCII 文字は 1 バイト、ラテン文字やギリシャ文字は 2 バイト、よく使われる中国語の文字は 3 バイト、その他の一部のまれな文字は 4 バイトを必要とします。</p> <p>UTF-8 の符号化規則は複雑ではなく、次の二つの場合に分かれます。</p> <ul> <li>長さが 1 バイトの文字では、最上位ビットを \\(0\\) にし、残りの 7 ビットに Unicode コードポイントを設定します。ASCII 文字は Unicode 文字集合の先頭の 128 コードポイントを占めています。つまり、UTF-8 は ASCII コードとの後方互換性を備えています。そのため、古い ASCII のテキストも UTF-8 として読み取れます。</li> <li>長さが \\(n\\) バイトの文字(\\(n &gt; 1\\))では、最初のバイトの上位 \\(n\\) ビットをすべて \\(1\\) にし、上位から \\(n + 1\\) 番目のビットを \\(0\\) にします。2 バイト目以降は各バイトの上位 2 ビットを \\(10\\) にし、残りのすべてのビットに文字の Unicode コードポイントを埋め込みます。</li> </ul> <p>図 3-8 は「Hello算法」に対応する UTF-8 の符号化を示しています。上位 \\(n\\) ビットがすべて \\(1\\) なので、システムは先頭から連続する \\(1\\) の数を読み取ることで、文字の長さが \\(n\\) バイトであると判断できます。</p> <p>では、後続のすべてのバイトの上位 2 ビットを \\(10\\) にするのはなぜでしょうか。実は、この \\(10\\) は検査用の印として機能します。誤ったバイト位置からテキストを読み取り始めた場合でも、バイトの先頭にある \\(10\\) によって、システムはすぐに異常を検出できます。</p> <p>\\(10\\) を検査用の印にできるのは、UTF-8 の符号化規則では、文字の先頭の 2 ビットが \\(10\\) になることはないからです。これは背理法で証明できます。ある文字の先頭の 2 ビットが \\(10\\) だと仮定すると、その文字の長さは \\(1\\) バイトであり、ASCII コードに対応することになります。しかし、ASCII コードの最上位ビットは \\(0\\) であるはずなので、仮定と矛盾します。</p> <p></p> <p> 図 3-8   UTF-8 の符号化例 </p> <p>UTF-8 のほかにも、次の二つの符号化方式がよく使われます。</p> <ul> <li>UTF-16 エンコーディング:1 文字を 2 バイトまたは 4 バイトで表します。すべての ASCII 文字と、よく使われる英語以外の文字は 2 バイトで表し、一部の文字には 4 バイトが必要です。2 バイトで表す文字では、UTF-16 の符号値は Unicode コードポイントと等しくなります。</li> <li>UTF-32 エンコーディング:すべての文字を 4 バイトで表します。そのため、UTF-32 は UTF-8 や UTF-16 より多くの容量を使い、特に ASCII 文字の割合が高いテキストではその差が大きくなります。</li> </ul> <p>保存容量の面では、UTF-8 は英語の文字をわずか 1 バイトで表せるため、非常に効率的です。一方、中国語などの一部の英語以外の文字は、UTF-16 なら 2 バイトで済むのに対し、UTF-8 では 3 バイト必要になることがあるため、UTF-16 の方が効率的です。</p> <p>互換性の面では、UTF-8 が最も汎用性が高く、多くのツールやライブラリが UTF-8 を優先的にサポートしています。</p>","path":["第 3 章   データ構造","3.4   文字エンコーディング *"],"tags":[]},{"location":"chapter_data_structure/character_encoding/#345","level":2,"title":"3.4.5   プログラミング言語の文字エンコーディング","text":"<p>従来の多くのプログラミング言語では、プログラム実行中の文字列に、UTF-16 や UTF-32 のような固定長の符号化方式を使っていました。固定長なら文字列を配列として扱うことができ、次のような利点があります。</p> <ul> <li>ランダムアクセス:UTF-16 で符号化された文字列には、容易にランダムアクセスできます。UTF-8 は可変長なので、第 \\(i\\) 文字を見つけるには、文字列の先頭から第 \\(i\\) 文字まで走査する必要があり、\\(O(n)\\) の時間がかかります。</li> <li>文字数の取得:ランダムアクセスと同様に、UTF-16 で符号化された文字列の長さを求める操作も \\(O(1)\\) です。一方、UTF-8 で符号化された文字列の長さを求めるには、文字列全体を走査する必要があります。</li> <li>文字列操作:UTF-16 で符号化された文字列では、分割、連結、挿入、削除など、多くの操作を行いやすくなります。UTF-8 で符号化された文字列でこれらの操作を行う場合は、不正な UTF-8 の符号を生じさせないようにするため、通常は追加の計算が必要です。</li> </ul> <p>実際、プログラミング言語の文字エンコーディングの設計は、さまざまな要因が関わる興味深いテーマです。</p> <ul> <li>Java の <code>String</code> 型は UTF-16 を使い、各文字が 2 バイトを占めます。これは、Java の設計当初、16 ビットであらゆる文字を表現できると考えられていたためです。しかし、その判断は誤りでした。その後、Unicode の規格が 16 ビットの範囲を超えて拡張されたため、現在の Java では、文字が「サロゲートペア」と呼ばれる二つの 16 ビット値の組で表されることがあります。</li> <li>JavaScript と TypeScript の文字列が UTF-16 を使う理由も Java と同様です。Netscape が 1995 年に JavaScript を初めて公開したとき、Unicode はまだ発展の初期段階にあり、16 ビットの符号ですべての Unicode 文字を表現できました。</li> <li>C# が UTF-16 を使う主な理由は、.NET プラットフォームを設計した Microsoft の多くの技術で、Windows を含めて UTF-16 が広く使われているためです。</li> </ul> <p>これらのプログラミング言語は文字数を少なく見積もったため、16 ビットを超える Unicode 文字を「サロゲートペア」で表さざるを得なくなりました。これはやむを得ない対応でした。一方では、サロゲートペアを含む文字列では 1 文字が 2 バイトにも 4 バイトにもなり、固定長という利点が失われます。他方では、サロゲートペアを扱うために追加のコードが必要となり、プログラミングが複雑になってデバッグも難しくなります。</p> <p>こうした理由から、異なる符号化方式を採用しているプログラミング言語もあります。</p> <ul> <li>Python の <code>str</code> は Unicode を使い、柔軟な文字列表現を採用しています。保存時の文字の長さは、文字列中の最大の Unicode コードポイントによって決まります。すべてが ASCII 文字なら各文字は 1 バイト、ASCII の範囲外の文字があってもすべてが基本多言語面(BMP)内なら各文字は 2 バイト、BMP の範囲外の文字がある場合は各文字が 4 バイトを占めます。</li> <li>Go の <code>string</code> 型は、内部で UTF-8 を使います。また、単一の Unicode コードポイントを表す <code>rune</code> 型も用意されています。</li> <li>Rust の <code>str</code> 型と <code>String</code> 型は、内部で UTF-8 を使います。また、単一の Unicode コードポイントを表す <code>char</code> 型も用意されています。</li> </ul> <p>ここまでの説明は、いずれもプログラミング言語内部での文字列の保存方法に関するものです。ファイルへの保存方法やネットワークでの転送方法とは、別の問題であることに注意してください。ファイルに保存したりネットワークで転送したりするときは、通常、互換性と容量効率を最適にするために、文字列を UTF-8 形式に符号化します。</p>","path":["第 3 章   データ構造","3.4   文字エンコーディング *"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/","level":1,"title":"3.1   データ構造の分類","text":"<p>代表的なデータ構造には、配列、連結リスト、スタック、キュー、ハッシュテーブル、木、ヒープ、グラフがあります。これらは「論理構造」と「物理構造」の二つの観点で分類できます。</p>","path":["第 3 章   データ構造","3.1   データ構造の分類"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/#311","level":2,"title":"3.1.1   論理構造:線形と非線形","text":"<p>論理構造は、データ要素同士の論理的な関係を表します。配列や連結リストでは一定の順に並び、線形の関係を表します。木では上から下へ階層的に並び、「祖先」から「子孫」へ派生する関係を表します。グラフはノードと辺からなり、複雑なネットワーク関係を表します。</p> <p>図 3-1 のように、論理構造は「線形」と「非線形」に大別できます。線形構造は直感的に理解しやすく、要素が論理的に一列に並びます。非線形構造では、そのような一列の並びにはなりません。</p> <ul> <li>線形データ構造:配列、連結リスト、スタック、キュー、ハッシュテーブル。要素間には一対一の順序関係があります。</li> <li>非線形データ構造:木、ヒープ、グラフ、ハッシュテーブル。</li> </ul> <p>非線形データ構造は、さらに木構造とネットワーク構造に分けられます。</p> <ul> <li>木構造:木、ヒープ、ハッシュテーブル。要素間は一対多の関係です。</li> <li>ネットワーク構造:グラフ。要素間は多対多の関係です。</li> </ul> <p></p> <p> 図 3-1   線形データ構造と非線形データ構造 </p>","path":["第 3 章   データ構造","3.1   データ構造の分類"],"tags":[]},{"location":"chapter_data_structure/classification_of_data_structure/#312","level":2,"title":"3.1.2   物理構造:連続と分散","text":"<p>アルゴリズムの実行中、処理対象のデータは主にメモリに保存されます。図 3-2 はメモリモジュールで、黒いチップのそれぞれにメモリ空間があります。メモリは巨大な Excel の表のように考えられ、各セルに一定量のデータを格納できます。</p> <p>システムはメモリアドレスを使って、目的の位置のデータにアクセスします。図 3-2 のように、各セルには規則に従った番号が割り当てられ、各メモリ領域が一意のアドレスを持ちます。このアドレスにより、プログラムはデータにアクセスできます。</p> <p></p> <p> 図 3-2   メモリモジュール、メモリ空間、メモリアドレス </p> <p>ヒント</p> <p>Excel の表は単純化したたとえです。実際のメモリの仕組みはもっと複雑で、アドレス空間、メモリ管理、キャッシュ、仮想メモリ、物理メモリなどが関わります。</p> <p>メモリはすべてのプログラムが利用する資源ですが、ある領域を一つのプログラムが使っていると、通常はほかのプログラムが同時に使うことはできません。そのため、データ構造とアルゴリズムの設計では、メモリ資源が重要な検討事項です。たとえば最大使用量がシステムの空きメモリを超えないようにし、大きな連続領域がなければ、分散した領域に格納できるデータ構造を選ぶ必要があります。</p> <p>図 3-3 のように、物理構造は、コンピュータのメモリ内での格納方法を表し、連続した領域への格納(配列)と、分散した領域への格納(連結リスト)に分けられます。アクセス、更新、追加、削除などの方法を根本から決めるもので、二つの構造は時間効率と空間効率の面で互いを補い合います。</p> <p></p> <p> 図 3-3   連続した領域への格納と分散した領域への格納 </p> <p>すべてのデータ構造は、配列、連結リスト、またはその組み合わせで実装されます。たとえばスタックとキューはどちらでも実装でき、ハッシュテーブルでは両方を使うことがあります。</p> <ul> <li>配列で実装できるもの:スタック、キュー、ハッシュテーブル、木、ヒープ、グラフ、行列、テンソル(次元が \\(\\geq 3\\) の配列)など。</li> <li>連結リストで実装できるもの:スタック、キュー、ハッシュテーブル、木、ヒープ、グラフなど。</li> </ul> <p>連結リストは初期化後も実行中に長さを変えられるため、「動的データ構造」とも呼ばれます。配列は初期化後に長さを変えられないため、「静的データ構造」とも呼ばれます。ただし、配列もメモリを再確保すれば長さを変えられ、一定の「動的な性質」を持たせられます。</p> <p>ヒント</p> <p>物理構造がわかりにくければ、次の章を読んでから、この節を振り返ってみてください。</p>","path":["第 3 章   データ構造","3.1   データ構造の分類"],"tags":[]},{"location":"chapter_data_structure/exercises/","level":1,"title":"3.6   練習問題","text":"","path":["第 3 章   データ構造","3.6   練習問題"],"tags":[]},{"location":"chapter_data_structure/exercises/#361","level":2,"title":"3.6.1   知識の確認","text":"","path":["第 3 章   データ構造","3.6   練習問題"],"tags":[]},{"location":"chapter_data_structure/exercises/#1","level":3,"title":"1.   日常の場面におけるデータの関係","text":"<p>データ同士の関係に基づき、次の各場面に当てはまるものを「線形構造、木構造、ネットワーク構造」から一つ選び、理由を説明してください。</p> <ol> <li>生徒たちが一列に並び、それぞれが自分の前と後ろの人だけを気にしています。</li> <li>学校が「学校 → 学年 → 学級」という階層で管理されています。</li> <li>市街地の道路が複数の交差点を結んでいます。一つの交差点から複数の別の交差点へ進め、環状の経路ができる場合もあります。</li> </ol> 解答例 <ol> <li> <p>線形構造です。先頭と末尾以外の人は、前後に一人ずつ隣接し、関係が一本の線に沿って続きます。</p> </li> <li> <p>木構造です。各学級は一つの学年に属し、各学年は学校に属します。関係が上から下へ階層的に広がります。</p> </li> <li> <p>ネットワーク構造です。一つの交差点が複数の交差点につながり、経路が環を作ることもあるため、単一の順序や厳密な階層には並べられません。</p> <p>構造を判断するときは、メモリ上の領域の大きさより先に、要素同士の関係を観察します。</p> </li> </ol>","path":["第 3 章   データ構造","3.6   練習問題"],"tags":[]},{"location":"chapter_data_structure/exercises/#2","level":3,"title":"2.   論理的な順序をメモリに格納するには","text":"<p>論理的な順序 <code>A → B → C</code> を保存するために、次の二つの簡略化したメモリ配置を考えます。</p> <ul> <li>案 1:<code>A、B、C</code> を、それぞれ番号 <code>20、21、22</code> のメモリの区画に置きます。</li> <li>案 2:<code>A、B、C</code> を、それぞれ番号 <code>20、7、31</code> のメモリの区画に置き、<code>A</code> に <code>B</code> の位置、<code>B</code> に <code>C</code> の位置を記録します。</li> </ul> <ol> <li>どちらが連続した領域への格納で、どちらが分散した領域への格納ですか。</li> <li>それぞれ、配列と連結リストのどちらに近いですか。</li> <li>案 2 では区画の番号が大小順になっていないのに、なぜ <code>A → B → C</code> の論理的な順序を表せるのでしょうか。</li> </ol> 解答例 <ol> <li> <p>案 1 は連続したメモリの区画を使うため、連続した領域への格納です。案 2 はノードが別々の位置に分散するため、分散した領域への格納です。</p> </li> <li> <p>案 1 は配列、案 2 は連結リストに近い方式です。</p> </li> <li> <p>論理的な順序は、メモリの区画番号の大小ではなく、ノード間に記録した接続関係で決まります。 <code>A</code> が記録する位置から <code>B</code> を、さらに <code>B</code> が記録する位置から <code>C</code> を見つけられるため、<code>A、B、C</code> の順にアクセスできます。</p> <p>このことからも、論理構造と物理構造は、同じデータを異なる二つの観点から捉えたものだと分かります。</p> </li> </ol>","path":["第 3 章   データ構造","3.6   練習問題"],"tags":[]},{"location":"chapter_data_structure/exercises/#3","level":3,"title":"3.   宿題の記録に使うデータ型と構造","text":"<p>ある学習グループで、4 人の生徒が宿題を提出したかどうかを座席順に記録したところ、次のようになりました。</p> <p><code>[true, false, true, true]</code></p> <ol> <li>各要素には、どの基本データ型が適していますか。</li> <li>4 個の要素が座席順に一列に並ぶことで、どの論理構造を使っていますか。</li> <li>後で各自の宿題の点数 <code>[90, 0, 85, 100]</code> を記録するように変えた場合、変わるのはデータの「内容の型」と「組織化の方法」のどちらですか。理由を説明してください。</li> </ol> 解答例 <ol> <li> <p>各要素は「はい」か「いいえ」だけを表すため、真偽値型 <code>bool</code> が適しています。</p> </li> <li> <p>要素は座席順に並ぶ線形構造で、配列に保存できます。</p> </li> <li> <p>変わるのは内容の型です。要素が真偽値から整数になります。組織化の方法は変わらず、 データは引き続き座席順に一列に並び、配列という線形構造を使えます。</p> <p>基本データ型は「何を格納するか」を表し、データ構造は「データをどう組織化するか」を表します。</p> </li> </ol>","path":["第 3 章   データ構造","3.6   練習問題"],"tags":[]},{"location":"chapter_data_structure/exercises/#362","level":2,"title":"3.6.2   プログラミング演習","text":"","path":["第 3 章   データ構造","3.6   練習問題"],"tags":[]},{"location":"chapter_data_structure/exercises/#1-1","level":3,"title":"1.   二進表現に含まれる 1 の数","text":"<p>非負整数 <code>n</code> が与えられます。その二進表現に含まれる 1 の総数を数えてください。</p> <p>ビット演算を使って解いてください。二進表現を文字列に変換したり、1 の数を直接数える組み込み関数を使ったりしてはいけません。</p> 解くためのヒント <ol> <li>n &amp; 1 で n の右端のビットを取り出し、そのビットが 1 かどうかを調べられます</li> <li>右に 1 ビットシフトすると、現在の右端のビットが捨てられます。多くの言語では演算子 &gt;&gt; を使います</li> <li>各ビットを調べて右シフトする方法を完成させてから、n &amp; (n - 1) が n の右端の 1 を 0 に変えることを確かめてみましょう</li> </ol> <p>LeetCode</p>","path":["第 3 章   データ構造","3.6   練習問題"],"tags":[]},{"location":"chapter_data_structure/number_encoding/","level":1,"title":"3.3   数値の符号化 *","text":"<p>ヒント</p> <p>本書で見出しに * が付く節は、任意の学習内容です。時間が足りない場合や難しく感じる場合は、ひとまず飛ばし、必読の節を学んだ後で取り組んでかまいません。</p>","path":["第 3 章   データ構造","3.3   数値の符号化 *"],"tags":[]},{"location":"chapter_data_structure/number_encoding/#331-1-2","level":2,"title":"3.3.1   符号絶対値表現、1 の補数表現、2 の補数表現","text":"<p>前の節の表では、どの整数型も、表せる負の数が正の数より一つ多くなっていました。たとえば <code>byte</code> の範囲は \\([-128, 127]\\) です。直感に反するこの性質には、符号絶対値表現、1 の補数表現、2 の補数表現が関わります。</p> <p>まず、数値はコンピュータ内で「2 の補数表現」により保存されることを押さえましょう。理由を考える前に、三つを定義します。</p> <ul> <li>符号絶対値表現:二進表現の最上位ビットを符号とし、\\(0\\) を正、\\(1\\) を負とします。残りのビットで数の値を表します。</li> <li>1 の補数表現:正の数は符号絶対値表現と同じです。負の数は、符号絶対値表現の符号ビット以外をすべて反転します。</li> <li>2 の補数表現:正の数は符号絶対値表現と同じです。負の数は、1 の補数表現に \\(1\\) を加えます。</li> </ul> <p>図 3-4 に、三つの表現の変換方法を示します。</p> <p></p> <p> 図 3-4   符号絶対値表現、1 の補数表現、2 の補数表現の相互変換 </p> <p>符号絶対値表現(sign-magnitude)は最も直感的ですが、制約があります。一つは、負の数をそのまま演算に使えないことです。たとえば \\(1 + (-2)\\) をこの表現で計算すると、明らかに誤った結果 \\(-3\\) になります。</p> \\[ \\begin{aligned} &amp; 1 + (-2) \\newline &amp; \\rightarrow 0000 \\; 0001 + 1000 \\; 0010 \\newline &amp; = 1000 \\; 0011 \\newline &amp; \\rightarrow -3 \\end{aligned} \\] <p>この問題を解決するため、1 の補数表現(1's complement)が導入されました。符号絶対値表現から変換して \\(1 + (-2)\\) を計算し、結果を元の表現へ戻すと、正しい答え \\(-1\\) が得られます。</p> \\[ \\begin{aligned} &amp; 1 + (-2) \\newline &amp; \\rightarrow 0000 \\; 0001 \\; \\text{(符号絶対値)} + 1000 \\; 0010 \\; \\text{(符号絶対値)} \\newline &amp; = 0000 \\; 0001 \\; \\text{(1 の補数)} + 1111 \\; 1101 \\; \\text{(1 の補数)} \\newline &amp; = 1111 \\; 1110 \\; \\text{(1 の補数)} \\newline &amp; = 1000 \\; 0001 \\; \\text{(符号絶対値)} \\newline &amp; \\rightarrow -1 \\end{aligned} \\] <p>もう一つは、ゼロの符号絶対値表現が \\(+0\\) と \\(-0\\) の二つあることです。ゼロに異なる二進符号が対応すると、曖昧さが生じる可能性があります。たとえば条件判定で正負のゼロを適切に区別しなければ、誤った結果になるかもしれません。この曖昧さに対応するには追加の判定が必要で、演算効率を下げる可能性があります。</p> \\[ \\begin{aligned} +0 &amp; \\rightarrow 0000 \\; 0000 \\newline -0 &amp; \\rightarrow 1000 \\; 0000 \\end{aligned} \\] <p>1 の補数表現にも正負のゼロの問題があるため、さらに2 の補数表現(2's complement)が導入されました。まず、負のゼロの変換を見てみましょう。</p> \\[ \\begin{aligned} -0 \\rightarrow \\; &amp; 1000 \\; 0000 \\; \\text{(符号絶対値)} \\newline = \\; &amp; 1111 \\; 1111 \\; \\text{(1 の補数)} \\newline = 1 \\; &amp; 0000 \\; 0000 \\; \\text{(2 の補数)} \\newline \\end{aligned} \\] <p>負のゼロの 1 の補数に \\(1\\) を加えると桁上がりしますが、<code>byte</code> は 8 ビットなので、9 ビット目にはみ出た \\(1\\) は捨てられます。つまり、負のゼロの 2 の補数は \\(0000 \\; 0000\\) で、正のゼロと同じです。ゼロの表現が一つになり、曖昧さが解消されます。</p> <p>最後に、<code>byte</code> の範囲 \\([-128, 127]\\) で、一つ余分な負の数 \\(-128\\) がどこから来るのかを考えます。\\([-127, +127]\\) の整数には、符号絶対値表現、1 の補数表現、2 の補数表現があり、符号絶対値表現と 2 の補数表現は相互変換できます。</p> <p>ただし、2 の補数 \\(1000 \\; 0000\\) は例外で、対応する符号絶対値表現がありません。変換手順に従うと \\(0000 \\; 0000\\) になりますが、これは数値 \\(0\\) で、その 2 の補数表現は自身であるはずなので矛盾します。この特殊な \\(1000 \\; 0000\\) は、\\(-128\\) を表すと定められています。実際、2 の補数で \\((-1) + (-127)\\) を計算すると \\(-128\\) になります。</p> \\[ \\begin{aligned} &amp; (-127) + (-1) \\newline &amp; \\rightarrow 1111 \\; 1111 \\; \\text{(符号絶対値)} + 1000 \\; 0001 \\; \\text{(符号絶対値)} \\newline &amp; = 1000 \\; 0000 \\; \\text{(1 の補数)} + 1111 \\; 1110 \\; \\text{(1 の補数)} \\newline &amp; = 1000 \\; 0001 \\; \\text{(2 の補数)} + 1111 \\; 1111 \\; \\text{(2 の補数)} \\newline &amp; = 1000 \\; 0000 \\; \\text{(2 の補数)} \\newline &amp; \\rightarrow -128 \\end{aligned} \\] <p>ここまでの計算がすべて加算であることに気づいたかもしれません。これは重要な事実、コンピュータ内部の回路は主に加算を基礎に設計されていることを示しています。加算は乗算・除算・減算などに比べて回路を実装しやすく、並列化も容易で、演算速度が速いためです。</p> <p>もちろん、加算しかできないという意味ではありません。加算と基本的な論理演算を組み合わせれば、さまざまな数学的演算を実現できます。たとえば減算 \\(a - b\\) は加算 \\(a + (-b)\\) に置き換えられ、乗算と除算も、複数回の加算や減算に変換できます。</p> <p>2 の補数を使う理由は、正負の加算を同じ回路と操作で処理でき、減算専用の回路や正負のゼロへの特別な処理が不要になるためです。これにより、回路設計が大幅に簡単になり、演算効率が高まります。</p> <p>2 の補数の設計は巧妙ですが、紙幅の都合上、ここまでにします。興味があれば、さらに詳しく調べてみてください。</p>","path":["第 3 章   データ構造","3.3   数値の符号化 *"],"tags":[]},{"location":"chapter_data_structure/number_encoding/#332","level":2,"title":"3.3.2   浮動小数点数の符号化","text":"<p><code>int</code> と <code>float</code> はどちらも 4 バイトなのに、なぜ <code>float</code> の方がはるかに広い範囲を表せるのでしょうか。小数も表すなら範囲が狭くなりそうなので、直感に反します。</p> <p>実際には、<code>float</code> が別の表現方法を使っているためです。32 ビットの二進数を次のように表します。</p> \\[ b_{31} b_{30} b_{29} \\ldots b_2 b_1 b_0 \\] <p>IEEE 754 規格によれば、32-bit の <code>float</code> は三つの部分からなります。</p> <ul> <li>符号部 \\(\\mathrm{S}\\):1 ビットで、\\(b_{31}\\) に対応します。</li> <li>指数部 \\(\\mathrm{E}\\):8 ビットで、\\(b_{30} b_{29} \\ldots b_{23}\\) に対応します。</li> <li>仮数部 \\(\\mathrm{N}\\):23 ビットで、\\(b_{22} b_{21} \\ldots b_0\\) に対応します。</li> </ul> <p>二進数で表した <code>float</code> の値は、次の式で計算します。</p> \\[ \\text {val} = (-1)^{b_{31}} \\times 2^{\\left(b_{30} b_{29} \\ldots b_{23}\\right)_2-127} \\times\\left(1 . b_{22} b_{21} \\ldots b_0\\right)_2 \\] <p>十進数の式に直すと、次のようになります。</p> \\[ \\text {val}=(-1)^{\\mathrm{S}} \\times 2^{\\mathrm{E} -127} \\times (1 + \\mathrm{N}) \\] <p>各項の範囲は次のとおりです。</p> \\[ \\begin{aligned} \\mathrm{S} \\in &amp; \\{ 0, 1\\}, \\quad \\mathrm{E} \\in \\{ 1, 2, \\dots, 254 \\} \\newline (1 + \\mathrm{N}) = &amp; (1 + \\sum_{i=1}^{23} b_{23-i} 2^{-i}) \\subset [1, 2 - 2^{-23}] \\end{aligned} \\] <p></p> <p> 図 3-5   IEEE 754 に従った float の計算例 </p> <p>図 3-5 の例では、\\(\\mathrm{S} = 0\\)、\\(\\mathrm{E} = 124\\)、\\(\\mathrm{N} = 2^{-2} + 2^{-3} = 0.375\\) なので、次の値になります。</p> \\[ \\text { val } = (-1)^0 \\times 2^{124 - 127} \\times (1 + 0.375) = 0.171875 \\] <p>これで最初の疑問に答えられます。<code>float</code> には指数部があるため、<code>int</code> よりはるかに広い範囲を表せるのです。上の計算から、最大の正の数は \\(2^{254 - 127} \\times (2 - 2^{-23}) \\approx 3.4 \\times 10^{38}\\) となり、符号を変えると最小の負の数になります。</p> <p><code>float</code> は範囲を広げる代わりに、精度を犠牲にしています。<code>int</code> は 32 ビット全体で数を表し、値は等間隔に分布します。一方、<code>float</code> は指数部があるため、数値が大きいほど、隣り合う表現可能な数の間隔も大きくなる傾向があります。</p> <p>表 3-2 のように、指数部 \\(\\mathrm{E} = 0\\) と \\(\\mathrm{E} = 255\\) は特殊な意味を持ち、ゼロ、無限大、\\(\\mathrm{NaN}\\) などを表すために使います。</p> <p> 表 3-2   指数部の意味 </p> 指数部 E 仮数部 \\(\\mathrm{N} = 0\\) 仮数部 \\(\\mathrm{N} \\ne 0\\) 計算式 \\(0\\) \\(\\pm 0\\) 非正規化数 \\((-1)^{\\mathrm{S}} \\times 2^{-126} \\times (0.\\mathrm{N})\\) \\(1, 2, \\dots, 254\\) 正規化数 正規化数 \\((-1)^{\\mathrm{S}} \\times 2^{(\\mathrm{E} -127)} \\times (1.\\mathrm{N})\\) \\(255\\) \\(\\pm \\infty\\) \\(\\mathrm{NaN}\\) <p>非正規化数は、浮動小数点数の精度を大きく改善します。最小の正の正規化数は \\(2^{-126}\\)、最小の正の非正規化数は \\(2^{-126} \\times 2^{-23}\\) です。</p> <p>倍精度の <code>double</code> も <code>float</code> に似た方法で表現するため、ここでは説明を省きます。</p>","path":["第 3 章   データ構造","3.3   数値の符号化 *"],"tags":[]},{"location":"chapter_data_structure/summary/","level":1,"title":"3.5   まとめ","text":"","path":["第 3 章   データ構造","3.5   まとめ"],"tags":[]},{"location":"chapter_data_structure/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>データ構造は論理構造と物理構造の二つの観点で分類できます。論理構造は要素間の関係、物理構造はメモリ内での格納方法を表します。</li> <li>主な論理構造には、線形、木、ネットワークがあります。通常は線形(配列、連結リスト、スタック、キュー)と非線形(木、グラフ、ヒープ)に分けます。ハッシュテーブルの実装には、線形と非線形の両方が含まれる場合があります。</li> <li>プログラムの実行時、データはメモリに保存されます。各領域にはメモリアドレスがあり、プログラムはそれを使ってデータにアクセスします。</li> <li>物理構造は主に、連続した領域への格納(配列)と分散した領域への格納(連結リスト)に分けられます。すべてのデータ構造は、この二つまたはその組み合わせで実装されます。</li> <li>基本データ型には、整数の <code>byte</code>、<code>short</code>、<code>int</code>、<code>long</code>、浮動小数点の <code>float</code>、<code>double</code>、文字の <code>char</code>、ブールの <code>bool</code> があります。値の範囲は、使用する空間と表現方法で決まります。</li> <li>符号絶対値表現、1 の補数表現、2 の補数表現は、数値を符号化する三つの方法で、相互に変換できます。整数の符号絶対値表現では、最上位ビットが符号、残りが数の値を表します。</li> <li>整数はコンピュータ内で 2 の補数表現で保存されます。正負の加算を同じ方法で処理でき、減算専用のハードウェア回路が不要になり、正負のゼロという曖昧さもなくなります。</li> <li>浮動小数点数の符号化は、符号 1 ビット、指数 8 ビット、仮数 23 ビットで構成されます。指数部があるため整数よりはるかに広い範囲を表せますが、精度を犠牲にします。</li> <li>ASCII は初期の英語用文字集合で、長さは 1 バイト、128 文字を収録しています。GBK はよく使われる中国語の文字集合で、2 万字以上の漢字を収録しています。Unicode は、世界の各言語の文字を収めた完全な文字集合の標準を目指し、符号化方式の違いによる文字化けの解消を図っています。</li> <li>UTF-8 は最も普及した Unicode の符号化方式で、汎用性に優れています。可変長で拡張性が高く、記憶領域を効率よく使えます。UTF-16 と UTF-32 は固定長の符号化方式です。中国語では UTF-16 の方が UTF-8 より少ない空間で済みます。Java や C# などは標準で UTF-16 を使います。</li> </ul>","path":["第 3 章   データ構造","3.5   まとめ"],"tags":[]},{"location":"chapter_data_structure/summary/#2-q-a","level":3,"title":"2.   Q &amp; A","text":"<p>Q:ハッシュテーブルに、線形と非線形のデータ構造の両方が含まれるのはなぜですか。</p> <p>ハッシュテーブルの基盤は配列ですが、衝突の解決には「チェイン法」(後の「ハッシュ衝突」の節で説明します)を使うことがあります。各バケットが連結リストを指し、その長さがしきい値を超えると、木(通常は赤黒木)に変換する場合があります。</p> <p>格納の観点では、基盤となる配列の各バケットには、一つの値、連結リスト、木のいずれかが入る可能性があります。そのため、線形の構造(配列、連結リスト)と非線形の構造(木)が同時に含まれる場合があります。</p> <p>Q:<code>char</code> 型の長さは 1 バイトですか。</p> <p><code>char</code> の長さは、言語が使う符号化方式で決まります。たとえば Java、JavaScript、TypeScript、C# は UTF-16(Unicode コードポイントを保存する方式)を使うため、<code>char</code> の長さは 2 バイトです。</p> <p>Q:配列で実装したデータ構造を「静的」と呼ぶのは曖昧ではありませんか。スタックのプッシュやポップは「動的」な操作です。</p> <p>スタックのデータ操作は確かに動的ですが、構造自体は「静的」(長さが固定)です。配列を使った構造で要素を動的に追加・削除できても、容量は固定されています。確保済みの容量を超えたら、より大きな配列を作り、元の内容をコピーする必要があります。</p> <p>Q:スタックやキューを作るときに大きさを指定していないのに、なぜ「静的データ構造」なのですか。</p> <p>高水準言語では、初期容量の設定をクラスの内部で自動的に行うため、自分で指定する必要はありません。たとえば Java の <code>ArrayList</code> の初期容量は通常 10 です。容量拡張も自動で行われます。詳しくは後の「リスト」の節で説明します。</p> <p>Q:符号絶対値表現から 2 の補数表現への変換は「反転してから 1 を足す」ので、逆変換は「1 を引いてから反転する」はずです。逆変換にも「反転してから 1 を足す」が使えるのはなぜですか。</p> <p>相互変換は、実際には「補数」を求める操作だからです。まず補数を定義します。\\(a + b = c\\) なら、\\(a\\) は \\(b\\) の \\(c\\) に対する補数であり、逆に \\(b\\) も \\(a\\) の \\(c\\) に対する補数です。</p> <p>\\(n = 4\\) ビットの二進数 \\(0010\\) を、符号ビットを考慮せず符号絶対値表現とみなすと、対応する 2 の補数は「反転してから 1 を足す」ことで得られます。</p> \\[ 0010 \\rightarrow 1101 \\rightarrow 1110 \\] <p>元の値と補数の和は \\(0010 + 1110 = 10000\\) です。つまり、補数 \\(1110\\) は、元の値 \\(0010\\) の \\(10000\\) に対する「補数」です。「反転してから 1 を足す」は、実際には \\(10000\\) に対する補数を求める操作なのです。</p> <p>では、\\(1110\\) の \\(10000\\) に対する「補数」は何でしょうか。同じく「反転してから 1 を足す」で求められます。</p> \\[ 1110 \\rightarrow 0001 \\rightarrow 0010 \\] <p>つまり、元の表現と 2 の補数表現は、互いに \\(10000\\) に対する「補数」なので、どちらへの変換も同じ操作(反転してから 1 を足す)で行えます。</p> <p>もちろん、\\(1110\\) から元の表現を求めるには、「1 を引いてから反転する」という逆操作も使えます。</p> \\[ 1110 \\rightarrow 1101 \\rightarrow 0010 \\] <p>「反転してから 1 を足す」と「1 を引いてから反転する」は、どちらも \\(10000\\) に対する補数を求める操作であり、同等なのです。</p> <p>本質的には、「反転」は \\(1111\\) に対する補数を求める操作です(常に <code>元の値 + 1 の補数 = 1111</code> が成り立ちます)。さらに 1 を足して得る 2 の補数は、\\(10000\\) に対する補数になります。</p> <p>ここでは \\(n = 4\\) を例にしましたが、任意のビット数の二進数に一般化できます。</p>","path":["第 3 章   データ構造","3.5   まとめ"],"tags":[]},{"location":"chapter_divide_and_conquer/","level":1,"title":"第 12 章   分割統治","text":"<p>概要</p> <p>難しい問題も段階的に分解すると、分解するたびに簡単になっていきます。</p> <p>分けて解くという考え方は、単純なことから始めれば複雑な問題も解きほぐせるという、大切な事実を教えてくれます。</p>","path":["第 12 章   分割統治"],"tags":[]},{"location":"chapter_divide_and_conquer/#_1","level":2,"title":"この章の内容","text":"<ul> <li>12.1   分割統治アルゴリズム</li> <li>12.2   分割統治による探索戦略</li> <li>12.3   二分木の構築問題</li> <li>12.4   ハノイの塔の問題</li> <li>12.5   まとめ</li> <li>12.6   練習問題</li> </ul>","path":["第 12 章   分割統治"],"tags":[]},{"location":"chapter_divide_and_conquer/binary_search_recur/","level":1,"title":"12.2   分割統治による探索戦略","text":"<p>すでに学んだように、探索アルゴリズムは大きく二つに分類できます。</p> <ul> <li>全探索:データ構造を走査して探索し、時間計算量は \\(O(n)\\) です。</li> <li>適応的探索:データの特有の編成方法や事前情報を利用し、時間計算量を \\(O(\\log n)\\)、さらには \\(O(1)\\) にできます。</li> </ul> <p>実際、時間計算量が \\(O(\\log n)\\) の探索アルゴリズムは、通常、分割統治戦略に基づいています。二分探索や木がその例です。</p> <ul> <li>二分探索は各ステップで、問題(配列内で目的の要素を探すこと)を小さな問題(配列の半分で目的の要素を探すこと)に分解します。配列が空になるか、目的の要素が見つかるまで続けます。</li> <li>木は分割統治の代表例です。二分探索木、AVL 木、ヒープなどのデータ構造では、各種操作の時間計算量が \\(O(\\log n)\\) になります。</li> </ul> <p>二分探索の分割統治戦略は、次のように整理できます。</p> <ul> <li>問題を分解できる:中央の要素と目的の要素を比較することで、元の問題(配列内での探索)を部分問題(配列の半分での探索)に再帰的に分解します。</li> <li>部分問題が独立している:二分探索では各回に一つの部分問題だけを扱い、ほかの部分問題の影響を受けません。</li> <li>部分問題の解を統合する必要がない:二分探索の目的は特定の要素を見つけることなので、部分問題の解を統合する必要はありません。部分問題が解けると、元の問題も同時に解けます。</li> </ul> <p>分割統治によって探索効率が上がる本質的な理由は、全探索では 1 回に一つの候補しか除外できないのに対し、分割統治による探索では 1 回に候補の半分を除外できることです。</p>","path":["第 12 章   分割統治","12.2   分割統治による探索戦略"],"tags":[]},{"location":"chapter_divide_and_conquer/binary_search_recur/#1","level":3,"title":"1.   分割統治による二分探索の実装","text":"<p>前の章では、二分探索を反復処理(イテレーション)で実装しました。ここでは、分割統治(再帰)で実装します。</p> <p>問題</p> <p>長さ \\(n\\) のソート済み配列 <code>nums</code> が与えられます。すべての要素は互いに異なります。要素 <code>target</code> を探してください。</p> <p>分割統治の観点から、探索区間 \\([i, j]\\) に対応する部分問題を \\(f(i, j)\\) と表します。</p> <p>元の問題 \\(f(0, n-1)\\) から始め、次の手順で二分探索を行います。</p> <ol> <li>探索区間 \\([i, j]\\) の中央 \\(m\\) を求め、それを使って探索区間の半分を除外します。</li> <li>大きさが半分になった部分問題 \\(f(i, m-1)\\) または \\(f(m+1, j)\\) を再帰的に解きます。</li> <li>手順 <code>1.</code> と <code>2.</code> を繰り返し、<code>target</code> が見つかるか、区間が空になったら戻ります。</li> </ol> <p>図 12-4 に、配列内の要素 \\(6\\) を二分探索する際の分割統治の流れを示します。</p> <p></p> <p> 図 12-4   二分探索における分割統治の流れ </p> <p>実装では、問題 \\(f(i, j)\\) を解く再帰関数 <code>dfs()</code> を定義します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_recur.py<pre><code>def dfs(nums: list[int], target: int, i: int, j: int) -&gt; int:\n \"\"\"二分探索:問題 f(i, j)\"\"\"\n # 区間が空なら目的の要素が存在しないため、-1 を返す\n if i &gt; j:\n return -1\n # 中央のインデックス m を計算する\n m = (i + j) // 2\n if nums[m] &lt; target:\n # 部分問題 f(m+1, j) を再帰的に解く\n return dfs(nums, target, m + 1, j)\n elif nums[m] &gt; target:\n # 部分問題 f(i, m-1) を再帰的に解く\n return dfs(nums, target, i, m - 1)\n else:\n # 目的の要素が見つかったので、そのインデックスを返す\n return m\n\ndef binary_search(nums: list[int], target: int) -&gt; int:\n \"\"\"二分探索\"\"\"\n n = len(nums)\n # 問題 f(0, n-1) を解く\n return dfs(nums, target, 0, n - 1)\n</code></pre> binary_search_recur.cpp<pre><code>/* 二分探索:問題 f(i, j) */\nint dfs(vector&lt;int&gt; &amp;nums, int target, int i, int j) {\n // 区間が空なら目的の要素が存在しないため、-1 を返す\n if (i &gt; j) {\n return -1;\n }\n // 中央のインデックス m を計算する\n int m = (i + j) / 2;\n if (nums[m] &lt; target) {\n // 部分問題 f(m+1, j) を再帰的に解く\n return dfs(nums, target, m + 1, j);\n } else if (nums[m] &gt; target) {\n // 部分問題 f(i, m-1) を再帰的に解く\n return dfs(nums, target, i, m - 1);\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n}\n\n/* 二分探索 */\nint binarySearch(vector&lt;int&gt; &amp;nums, int target) {\n int n = nums.size();\n // 問題 f(0, n-1) を解く\n return dfs(nums, target, 0, n - 1);\n}\n</code></pre> binary_search_recur.java<pre><code>/* 二分探索:問題 f(i, j) */\nint dfs(int[] nums, int target, int i, int j) {\n // 区間が空なら目的の要素が存在しないため、-1 を返す\n if (i &gt; j) {\n return -1;\n }\n // 中央のインデックス m を計算する\n int m = (i + j) / 2;\n if (nums[m] &lt; target) {\n // 部分問題 f(m+1, j) を再帰的に解く\n return dfs(nums, target, m + 1, j);\n } else if (nums[m] &gt; target) {\n // 部分問題 f(i, m-1) を再帰的に解く\n return dfs(nums, target, i, m - 1);\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n}\n\n/* 二分探索 */\nint binarySearch(int[] nums, int target) {\n int n = nums.length;\n // 問題 f(0, n-1) を解く\n return dfs(nums, target, 0, n - 1);\n}\n</code></pre> binary_search_recur.cs<pre><code>/* 二分探索:問題 f(i, j) */\nint DFS(int[] nums, int target, int i, int j) {\n // 区間が空なら目的の要素が存在しないため、-1 を返す\n if (i &gt; j) {\n return -1;\n }\n // 中央のインデックス m を計算する\n int m = (i + j) / 2;\n if (nums[m] &lt; target) {\n // 部分問題 f(m+1, j) を再帰的に解く\n return DFS(nums, target, m + 1, j);\n } else if (nums[m] &gt; target) {\n // 部分問題 f(i, m-1) を再帰的に解く\n return DFS(nums, target, i, m - 1);\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n}\n\n/* 二分探索 */\nint BinarySearch(int[] nums, int target) {\n int n = nums.Length;\n // 問題 f(0, n-1) を解く\n return DFS(nums, target, 0, n - 1);\n}\n</code></pre> binary_search_recur.go<pre><code>/* 二分探索:問題 f(i, j) */\nfunc dfs(nums []int, target, i, j int) int {\n // 区間が空なら目的の要素が存在しないため、-1 を返す\n if i &gt; j {\n return -1\n }\n // 中央のインデックスを計算する\n m := i + ((j - i) &gt;&gt; 1)\n //中央の要素と目的の要素の大小を判定する\n if nums[m] &lt; target {\n // 小さい場合、配列の右半分を再帰的に探索する\n // 部分問題 f(m+1, j) を再帰的に解く\n return dfs(nums, target, m+1, j)\n } else if nums[m] &gt; target {\n // 大きい場合、配列の左半分を再帰的に探索する\n // 部分問題 f(i, m-1) を再帰的に解く\n return dfs(nums, target, i, m-1)\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m\n }\n}\n\n/* 二分探索 */\nfunc binarySearch(nums []int, target int) int {\n n := len(nums)\n return dfs(nums, target, 0, n-1)\n}\n</code></pre> binary_search_recur.swift<pre><code>/* 二分探索:問題 f(i, j) */\nfunc dfs(nums: [Int], target: Int, i: Int, j: Int) -&gt; Int {\n // 区間が空なら目的の要素が存在しないため、-1 を返す\n if i &gt; j {\n return -1\n }\n // 中央のインデックス m を計算する\n let m = (i + j) / 2\n if nums[m] &lt; target {\n // 部分問題 f(m+1, j) を再帰的に解く\n return dfs(nums: nums, target: target, i: m + 1, j: j)\n } else if nums[m] &gt; target {\n // 部分問題 f(i, m-1) を再帰的に解く\n return dfs(nums: nums, target: target, i: i, j: m - 1)\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m\n }\n}\n\n/* 二分探索 */\nfunc binarySearch(nums: [Int], target: Int) -&gt; Int {\n // 問題 f(0, n-1) を解く\n dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1)\n}\n</code></pre> binary_search_recur.js<pre><code>/* 二分探索:問題 f(i, j) */\nfunction dfs(nums, target, i, j) {\n // 区間が空なら目的の要素が存在しないため、-1 を返す\n if (i &gt; j) {\n return -1;\n }\n // 中央のインデックス m を計算する\n const m = i + ((j - i) &gt;&gt; 1);\n if (nums[m] &lt; target) {\n // 部分問題 f(m+1, j) を再帰的に解く\n return dfs(nums, target, m + 1, j);\n } else if (nums[m] &gt; target) {\n // 部分問題 f(i, m-1) を再帰的に解く\n return dfs(nums, target, i, m - 1);\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n}\n\n/* 二分探索 */\nfunction binarySearch(nums, target) {\n const n = nums.length;\n // 問題 f(0, n-1) を解く\n return dfs(nums, target, 0, n - 1);\n}\n</code></pre> binary_search_recur.ts<pre><code>/* 二分探索:問題 f(i, j) */\nfunction dfs(nums: number[], target: number, i: number, j: number): number {\n // 区間が空なら目的の要素が存在しないため、-1 を返す\n if (i &gt; j) {\n return -1;\n }\n // 中央のインデックス m を計算する\n const m = i + ((j - i) &gt;&gt; 1);\n if (nums[m] &lt; target) {\n // 部分問題 f(m+1, j) を再帰的に解く\n return dfs(nums, target, m + 1, j);\n } else if (nums[m] &gt; target) {\n // 部分問題 f(i, m-1) を再帰的に解く\n return dfs(nums, target, i, m - 1);\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n}\n\n/* 二分探索 */\nfunction binarySearch(nums: number[], target: number): number {\n const n = nums.length;\n // 問題 f(0, n-1) を解く\n return dfs(nums, target, 0, n - 1);\n}\n</code></pre> binary_search_recur.dart<pre><code>/* 二分探索:問題 f(i, j) */\nint dfs(List&lt;int&gt; nums, int target, int i, int j) {\n // 区間が空なら目的の要素が存在しないため、-1 を返す\n if (i &gt; j) {\n return -1;\n }\n // 中央のインデックス m を計算する\n int m = (i + j) ~/ 2;\n if (nums[m] &lt; target) {\n // 部分問題 f(m+1, j) を再帰的に解く\n return dfs(nums, target, m + 1, j);\n } else if (nums[m] &gt; target) {\n // 部分問題 f(i, m-1) を再帰的に解く\n return dfs(nums, target, i, m - 1);\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n}\n\n/* 二分探索 */\nint binarySearch(List&lt;int&gt; nums, int target) {\n int n = nums.length;\n // 問題 f(0, n-1) を解く\n return dfs(nums, target, 0, n - 1);\n}\n</code></pre> binary_search_recur.rs<pre><code>/* 二分探索:問題 f(i, j) */\nfn dfs(nums: &amp;[i32], target: i32, i: i32, j: i32) -&gt; i32 {\n // 区間が空なら目的の要素が存在しないため、-1 を返す\n if i &gt; j {\n return -1;\n }\n let m: i32 = i + (j - i) / 2;\n if nums[m as usize] &lt; target {\n // 部分問題 f(m+1, j) を再帰的に解く\n return dfs(nums, target, m + 1, j);\n } else if nums[m as usize] &gt; target {\n // 部分問題 f(i, m-1) を再帰的に解く\n return dfs(nums, target, i, m - 1);\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n}\n\n/* 二分探索 */\nfn binary_search(nums: &amp;[i32], target: i32) -&gt; i32 {\n let n = nums.len() as i32;\n // 問題 f(0, n-1) を解く\n dfs(nums, target, 0, n - 1)\n}\n</code></pre> binary_search_recur.c<pre><code>/* 二分探索:問題 f(i, j) */\nint dfs(int nums[], int target, int i, int j) {\n // 区間が空なら目的の要素が存在しないため、-1 を返す\n if (i &gt; j) {\n return -1;\n }\n // 中央のインデックス m を計算する\n int m = (i + j) / 2;\n if (nums[m] &lt; target) {\n // 部分問題 f(m+1, j) を再帰的に解く\n return dfs(nums, target, m + 1, j);\n } else if (nums[m] &gt; target) {\n // 部分問題 f(i, m-1) を再帰的に解く\n return dfs(nums, target, i, m - 1);\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n}\n\n/* 二分探索 */\nint binarySearch(int nums[], int target, int numsSize) {\n int n = numsSize;\n // 問題 f(0, n-1) を解く\n return dfs(nums, target, 0, n - 1);\n}\n</code></pre> binary_search_recur.kt<pre><code>/* 二分探索:問題 f(i, j) */\nfun dfs(\n nums: IntArray,\n target: Int,\n i: Int,\n j: Int\n): Int {\n // 区間が空なら目的の要素が存在しないため、-1 を返す\n if (i &gt; j) {\n return -1\n }\n // 中央のインデックス m を計算する\n val m = (i + j) / 2\n return if (nums[m] &lt; target) {\n // 部分問題 f(m+1, j) を再帰的に解く\n dfs(nums, target, m + 1, j)\n } else if (nums[m] &gt; target) {\n // 部分問題 f(i, m-1) を再帰的に解く\n dfs(nums, target, i, m - 1)\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n m\n }\n}\n\n/* 二分探索 */\nfun binarySearch(nums: IntArray, target: Int): Int {\n val n = nums.size\n // 問題 f(0, n-1) を解く\n return dfs(nums, target, 0, n - 1)\n}\n</code></pre> binary_search_recur.rb<pre><code>### 二分探索:問題 f(i, j) ###\ndef dfs(nums, target, i, j)\n # 区間が空なら目的の要素が存在しないため、-1 を返す\n return -1 if i &gt; j\n\n # 中央のインデックス m を計算する\n m = (i + j) / 2\n\n if nums[m] &lt; target\n # 部分問題 f(m+1, j) を再帰的に解く\n return dfs(nums, target, m + 1, j)\n elsif nums[m] &gt; target\n # 部分問題 f(i, m-1) を再帰的に解く\n return dfs(nums, target, i, m - 1)\n else\n # 目的の要素が見つかったので、そのインデックスを返す\n return m\n end\nend\n\n### 二分探索 ###\ndef binary_search(nums, target)\n n = nums.length\n # 問題 f(0, n-1) を解く\n dfs(nums, target, 0, n - 1)\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 12 章   分割統治","12.2   分割統治による探索戦略"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/","level":1,"title":"12.3   二分木の構築問題","text":"<p>問題</p> <p>二分木の行きがけ順走査 <code>preorder</code> と通りがけ順走査 <code>inorder</code> が与えられます。これらから二分木を構築し、根ノードを返してください。二分木には同じ値を持つノードがないものとします(図 12-5 参照)。</p> <p></p> <p> 図 12-5   二分木の構築に使うデータの例 </p>","path":["第 12 章   分割統治","12.3   二分木の構築問題"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#1","level":3,"title":"1.   分割統治で解ける問題かどうかを判断する","text":"<p>元の問題は、<code>preorder</code> と <code>inorder</code> から二分木を構築することであり、典型的な分割統治の問題です。</p> <ul> <li>問題を分解できる:分割統治の観点から、元の問題を「左部分木を構築する」「右部分木を構築する」という二つの部分問題と、「根ノードを初期化する」という一つの操作に分けられます。各部分木(部分問題)も同じ方法でさらに小さな部分木(部分問題)へ分割でき、最小の部分問題(空の部分木)に到達したら終了します。</li> <li>部分問題が独立している:左部分木と右部分木は互いに独立しており、重なりません。左部分木を構築するときは、通りがけ順走査と行きがけ順走査のうち、左部分木に対応する部分だけに注目すれば十分です。右部分木も同様です。</li> <li>部分問題の解を統合できる:左部分木と右部分木(部分問題の解)が得られたら、それらを根ノードにつなぐことで、元の問題の解を得られます。</li> </ul>","path":["第 12 章   分割統治","12.3   二分木の構築問題"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#2","level":3,"title":"2.   部分木をどのように分割するか","text":"<p>以上から、この問題は分割統治で解けます。では、行きがけ順走査 <code>preorder</code> と通りがけ順走査 <code>inorder</code> から、どのように左部分木と右部分木を切り分ければよいのでしょうか。</p> <p>定義によれば、<code>preorder</code> と <code>inorder</code> はいずれも三つの部分に分けられます。</p> <ul> <li>行きがけ順走査:<code>[ 根ノード | 左部分木 | 右部分木 ]</code>。たとえば図 12-5 の木では <code>[ 3 | 9 | 2 1 7 ]</code> です。</li> <li>通りがけ順走査:<code>[ 左部分木 | 根ノード | 右部分木 ]</code>。たとえば図 12-5 の木では <code>[ 9 | 3 | 1 2 7 ]</code> です。</li> </ul> <p>上図のデータを例に、図 12-6 の手順で分割できます。</p> <ol> <li>行きがけ順走査の先頭要素 3 が根ノードの値です。</li> <li>根ノード 3 の <code>inorder</code> 内のインデックスを探します。そのインデックスを使うと、<code>inorder</code> を <code>[ 9 | 3 | 1 2 7 ]</code> に分割できます。</li> <li><code>inorder</code> の分割結果から、左部分木と右部分木のノード数がそれぞれ 1 と 3 であることがわかります。そのため、<code>preorder</code> を <code>[ 3 | 9 | 2 1 7 ]</code> に分割できます。</li> </ol> <p></p> <p> 図 12-6   行きがけ順走査と通りがけ順走査で部分木を分割する </p>","path":["第 12 章   分割統治","12.3   二分木の構築問題"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#3","level":3,"title":"3.   変数を使って部分木の区間を表す","text":"<p>この分割方法により、<code>preorder</code> と <code>inorder</code> の中で、根ノード、左部分木、右部分木に対応するインデックスの区間がわかりました。これらの区間を表すため、いくつかのポインタ変数を使います。</p> <ul> <li>現在の木の根ノードが <code>preorder</code> 内にあるインデックスを \\(i\\) とします。</li> <li>現在の木の根ノードが <code>inorder</code> 内にあるインデックスを \\(m\\) とします。</li> <li>現在の木に対応する <code>inorder</code> 内のインデックス区間を \\([l, r]\\) とします。</li> </ul> <p>表 12-1 のように、これらの変数を使って、根ノードの <code>preorder</code> 内のインデックスと、部分木の <code>inorder</code> 内のインデックス区間を表せます。</p> <p> 表 12-1   行きがけ順走査と通りがけ順走査における根ノードと部分木のインデックス </p> 根ノードの <code>preorder</code> 内のインデックス 部分木の <code>inorder</code> 内のインデックス区間 現在の木 \\(i\\) \\([l, r]\\) 左部分木 \\(i + 1\\) \\([l, m-1]\\) 右部分木 \\(i + 1 + (m - l)\\) \\([m+1, r]\\) <p>右部分木の根ノードのインデックスに含まれる \\((m-l)\\) は、「左部分木のノード数」を表すことに注意してください。図 12-7 とあわせて考えると理解しやすくなります。</p> <p></p> <p> 図 12-7   根ノードと左右の部分木のインデックス区間 </p>","path":["第 12 章   分割統治","12.3   二分木の構築問題"],"tags":[]},{"location":"chapter_divide_and_conquer/build_binary_tree_problem/#4","level":3,"title":"4.   コードの実装","text":"<p>\\(m\\) を効率よく調べられるよう、ハッシュテーブル <code>hmap</code> に、配列 <code>inorder</code> の要素からインデックスへの対応を保存します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby build_tree.py<pre><code>def dfs(\n preorder: list[int],\n inorder_map: dict[int, int],\n i: int,\n l: int,\n r: int,\n) -&gt; TreeNode | None:\n \"\"\"二分木の構築:分割統治法\"\"\"\n # 部分木の区間が空の場合、終了する\n if r - l &lt; 0:\n return None\n # 根ノードを初期化する\n root = TreeNode(preorder[i])\n # m を調べ、左右の部分木に分割する\n m = inorder_map[preorder[i]]\n # 部分問題:左部分木の構築\n root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)\n # 部分問題:右部分木の構築\n root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)\n # 根ノードを返す\n return root\n\ndef build_tree(preorder: list[int], inorder: list[int]) -&gt; TreeNode | None:\n \"\"\"二分木を構築する\"\"\"\n # inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n inorder_map = {val: i for i, val in enumerate(inorder)}\n root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1)\n return root\n</code></pre> build_tree.cpp<pre><code>/* 二分木の構築:分割統治法 */\nTreeNode *dfs(vector&lt;int&gt; &amp;preorder, unordered_map&lt;int, int&gt; &amp;inorderMap, int i, int l, int r) {\n // 部分木の区間が空の場合、終了する\n if (r - l &lt; 0)\n return NULL;\n // 根ノードを初期化する\n TreeNode *root = new TreeNode(preorder[i]);\n // m を調べ、左右の部分木に分割する\n int m = inorderMap[preorder[i]];\n // 部分問題:左部分木の構築\n root-&gt;left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n // 部分問題:右部分木の構築\n root-&gt;right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n // 根ノードを返す\n return root;\n}\n\n/* 二分木を構築する */\nTreeNode *buildTree(vector&lt;int&gt; &amp;preorder, vector&lt;int&gt; &amp;inorder) {\n // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n unordered_map&lt;int, int&gt; inorderMap;\n for (int i = 0; i &lt; inorder.size(); i++) {\n inorderMap[inorder[i]] = i;\n }\n TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1);\n return root;\n}\n</code></pre> build_tree.java<pre><code>/* 二分木の構築:分割統治法 */\nTreeNode dfs(int[] preorder, Map&lt;Integer, Integer&gt; inorderMap, int i, int l, int r) {\n // 部分木の区間が空の場合、終了する\n if (r - l &lt; 0)\n return null;\n // 根ノードを初期化する\n TreeNode root = new TreeNode(preorder[i]);\n // m を調べ、左右の部分木に分割する\n int m = inorderMap.get(preorder[i]);\n // 部分問題:左部分木の構築\n root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n // 部分問題:右部分木の構築\n root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n // 根ノードを返す\n return root;\n}\n\n/* 二分木を構築する */\nTreeNode buildTree(int[] preorder, int[] inorder) {\n // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n Map&lt;Integer, Integer&gt; inorderMap = new HashMap&lt;&gt;();\n for (int i = 0; i &lt; inorder.length; i++) {\n inorderMap.put(inorder[i], i);\n }\n TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n return root;\n}\n</code></pre> build_tree.cs<pre><code>/* 二分木の構築:分割統治法 */\nTreeNode? DFS(int[] preorder, Dictionary&lt;int, int&gt; inorderMap, int i, int l, int r) {\n // 部分木の区間が空の場合、終了する\n if (r - l &lt; 0)\n return null;\n // 根ノードを初期化する\n TreeNode root = new(preorder[i]);\n // m を調べ、左右の部分木に分割する\n int m = inorderMap[preorder[i]];\n // 部分問題:左部分木の構築\n root.left = DFS(preorder, inorderMap, i + 1, l, m - 1);\n // 部分問題:右部分木の構築\n root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n // 根ノードを返す\n return root;\n}\n\n/* 二分木を構築する */\nTreeNode? BuildTree(int[] preorder, int[] inorder) {\n // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n Dictionary&lt;int, int&gt; inorderMap = [];\n for (int i = 0; i &lt; inorder.Length; i++) {\n inorderMap.TryAdd(inorder[i], i);\n }\n TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1);\n return root;\n}\n</code></pre> build_tree.go<pre><code>/* 二分木の構築:分割統治法 */\nfunc dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode {\n // 部分木の区間が空の場合、終了する\n if r-l &lt; 0 {\n return nil\n }\n // 根ノードを初期化する\n root := NewTreeNode(preorder[i])\n // m を調べ、左右の部分木に分割する\n m := inorderMap[preorder[i]]\n // 部分問題:左部分木の構築\n root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1)\n // 部分問題:右部分木の構築\n root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r)\n // 根ノードを返す\n return root\n}\n\n/* 二分木を構築する */\nfunc buildTree(preorder, inorder []int) *TreeNode {\n // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n inorderMap := make(map[int]int, len(inorder))\n for i := 0; i &lt; len(inorder); i++ {\n inorderMap[inorder[i]] = i\n }\n\n root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1)\n return root\n}\n</code></pre> build_tree.swift<pre><code>/* 二分木の構築:分割統治法 */\nfunc dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -&gt; TreeNode? {\n // 部分木の区間が空の場合、終了する\n if r - l &lt; 0 {\n return nil\n }\n // 根ノードを初期化する\n let root = TreeNode(x: preorder[i])\n // m を調べ、左右の部分木に分割する\n let m = inorderMap[preorder[i]]!\n // 部分問題:左部分木の構築\n root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1)\n // 部分問題:右部分木の構築\n root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r)\n // 根ノードを返す\n return root\n}\n\n/* 二分木を構築する */\nfunc buildTree(preorder: [Int], inorder: [Int]) -&gt; TreeNode? {\n // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }\n return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1)\n}\n</code></pre> build_tree.js<pre><code>/* 二分木の構築:分割統治法 */\nfunction dfs(preorder, inorderMap, i, l, r) {\n // 部分木の区間が空の場合、終了する\n if (r - l &lt; 0) return null;\n // 根ノードを初期化する\n const root = new TreeNode(preorder[i]);\n // m を調べ、左右の部分木に分割する\n const m = inorderMap.get(preorder[i]);\n // 部分問題:左部分木の構築\n root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n // 部分問題:右部分木の構築\n root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n // 根ノードを返す\n return root;\n}\n\n/* 二分木を構築する */\nfunction buildTree(preorder, inorder) {\n // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n let inorderMap = new Map();\n for (let i = 0; i &lt; inorder.length; i++) {\n inorderMap.set(inorder[i], i);\n }\n const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n return root;\n}\n</code></pre> build_tree.ts<pre><code>/* 二分木の構築:分割統治法 */\nfunction dfs(\n preorder: number[],\n inorderMap: Map&lt;number, number&gt;,\n i: number,\n l: number,\n r: number\n): TreeNode | null {\n // 部分木の区間が空の場合、終了する\n if (r - l &lt; 0) return null;\n // 根ノードを初期化する\n const root: TreeNode = new TreeNode(preorder[i]);\n // m を調べ、左右の部分木に分割する\n const m = inorderMap.get(preorder[i]);\n // 部分問題:左部分木の構築\n root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n // 部分問題:右部分木の構築\n root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n // 根ノードを返す\n return root;\n}\n\n/* 二分木を構築する */\nfunction buildTree(preorder: number[], inorder: number[]): TreeNode | null {\n // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n let inorderMap = new Map&lt;number, number&gt;();\n for (let i = 0; i &lt; inorder.length; i++) {\n inorderMap.set(inorder[i], i);\n }\n const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n return root;\n}\n</code></pre> build_tree.dart<pre><code>/* 二分木の構築:分割統治法 */\nTreeNode? dfs(\n List&lt;int&gt; preorder,\n Map&lt;int, int&gt; inorderMap,\n int i,\n int l,\n int r,\n) {\n // 部分木の区間が空の場合、終了する\n if (r - l &lt; 0) {\n return null;\n }\n // 根ノードを初期化する\n TreeNode? root = TreeNode(preorder[i]);\n // m を調べ、左右の部分木に分割する\n int m = inorderMap[preorder[i]]!;\n // 部分問題:左部分木の構築\n root.left = dfs(preorder, inorderMap, i + 1, l, m - 1);\n // 部分問題:右部分木の構築\n root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);\n // 根ノードを返す\n return root;\n}\n\n/* 二分木を構築する */\nTreeNode? buildTree(List&lt;int&gt; preorder, List&lt;int&gt; inorder) {\n // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n Map&lt;int, int&gt; inorderMap = {};\n for (int i = 0; i &lt; inorder.length; i++) {\n inorderMap[inorder[i]] = i;\n }\n TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1);\n return root;\n}\n</code></pre> build_tree.rs<pre><code>/* 二分木の構築:分割統治法 */\nfn dfs(\n preorder: &amp;[i32],\n inorder_map: &amp;HashMap&lt;i32, i32&gt;,\n i: i32,\n l: i32,\n r: i32,\n) -&gt; Option&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt; {\n // 部分木の区間が空の場合、終了する\n if r - l &lt; 0 {\n return None;\n }\n // 根ノードを初期化する\n let root = TreeNode::new(preorder[i as usize]);\n // m を調べ、左右の部分木に分割する\n let m = inorder_map.get(&amp;preorder[i as usize]).unwrap();\n // 部分問題:左部分木の構築\n root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1);\n // 部分問題:右部分木の構築\n root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r);\n // 根ノードを返す\n Some(root)\n}\n\n/* 二分木を構築する */\nfn build_tree(preorder: &amp;[i32], inorder: &amp;[i32]) -&gt; Option&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt; {\n // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n let mut inorder_map: HashMap&lt;i32, i32&gt; = HashMap::new();\n for i in 0..inorder.len() {\n inorder_map.insert(inorder[i], i as i32);\n }\n let root = dfs(preorder, &amp;inorder_map, 0, 0, inorder.len() as i32 - 1);\n root\n}\n</code></pre> build_tree.c<pre><code>/* 二分木の構築:分割統治法 */\nTreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) {\n // 部分木の区間が空の場合、終了する\n if (r - l &lt; 0)\n return NULL;\n // 根ノードを初期化する\n TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode));\n root-&gt;val = preorder[i];\n root-&gt;left = NULL;\n root-&gt;right = NULL;\n // m を調べ、左右の部分木に分割する\n int m = inorderMap[preorder[i]];\n // 部分問題:左部分木の構築\n root-&gt;left = dfs(preorder, inorderMap, i + 1, l, m - 1, size);\n // 部分問題:右部分木の構築\n root-&gt;right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size);\n // 根ノードを返す\n return root;\n}\n\n/* 二分木を構築する */\nTreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) {\n // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE);\n for (int i = 0; i &lt; inorderSize; i++) {\n inorderMap[inorder[i]] = i;\n }\n TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize);\n free(inorderMap);\n return root;\n}\n</code></pre> build_tree.kt<pre><code>/* 二分木の構築:分割統治法 */\nfun dfs(\n preorder: IntArray,\n inorderMap: Map&lt;Int?, Int?&gt;,\n i: Int,\n l: Int,\n r: Int\n): TreeNode? {\n // 部分木の区間が空の場合、終了する\n if (r - l &lt; 0) return null\n // 根ノードを初期化する\n val root = TreeNode(preorder[i])\n // m を調べ、左右の部分木に分割する\n val m = inorderMap[preorder[i]]!!\n // 部分問題:左部分木の構築\n root.left = dfs(preorder, inorderMap, i + 1, l, m - 1)\n // 部分問題:右部分木の構築\n root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r)\n // 根ノードを返す\n return root\n}\n\n/* 二分木を構築する */\nfun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? {\n // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n val inorderMap = HashMap&lt;Int?, Int?&gt;()\n for (i in inorder.indices) {\n inorderMap[inorder[i]] = i\n }\n val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1)\n return root\n}\n</code></pre> build_tree.rb<pre><code>### 二分木の構築:分割統治法 ###\ndef dfs(preorder, inorder_map, i, l, r)\n # 部分木の区間が空の場合、終了する\n return if r - l &lt; 0\n\n # 根ノードを初期化する\n root = TreeNode.new(preorder[i])\n # m を調べ、左右の部分木に分割する\n m = inorder_map[preorder[i]]\n # 部分問題:左部分木の構築\n root.left = dfs(preorder, inorder_map, i + 1, l, m - 1)\n # 部分問題:右部分木の構築\n root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r)\n\n # 根ノードを返す\n root\nend\n\n### 二分木を構築する ###\ndef build_tree(preorder, inorder)\n # inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する\n inorder_map = {}\n inorder.each_with_index { |val, i| inorder_map[val] = i }\n dfs(preorder, inorder_map, 0, 0, inorder.length - 1)\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 12-8 に、二分木を構築する再帰の流れを示します。各ノードは再帰呼び出しを下に進める過程で作られ、各辺(参照)は上に戻る過程で作られます。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 12-8   二分木を構築する再帰の流れ </p> <p>各再帰関数内での行きがけ順走査 <code>preorder</code> と通りがけ順走査 <code>inorder</code> の分割結果を図 12-9 に示します。</p> <p></p> <p> 図 12-9   各再帰関数内での分割結果 </p> <p>木のノード数を \\(n\\) とすると、各ノードの初期化(再帰関数 <code>dfs()</code> の 1 回の実行)には \\(O(1)\\) 時間かかります。したがって、全体の時間計算量は \\(O(n)\\) です。</p> <p>ハッシュテーブルには <code>inorder</code> の要素からインデックスへの対応を保存するため、空間計算量は \\(O(n)\\) です。最悪の場合、つまり二分木が連結リストに退化した場合は、再帰の深さが \\(n\\) となり、スタックフレームに \\(O(n)\\) の空間を使います。したがって、全体の空間計算量は \\(O(n)\\) です。</p>","path":["第 12 章   分割統治","12.3   二分木の構築問題"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/","level":1,"title":"12.1   分割統治アルゴリズム","text":"<p>分割統治(divide and conquer)とは、文字どおり「分けて解く」ことであり、非常に重要で広く使われるアルゴリズム戦略です。通常は再帰で実装し、「分割」と「統合」の二つの段階からなります。</p> <ol> <li>分割の段階:元の問題を二つ以上の部分問題に再帰的に分解し、最小の部分問題に到達したら終了します。</li> <li>統合の段階:解がわかっている最小の部分問題から始め、部分問題の解を下から上へと統合して、元の問題の解を組み立てます。</li> </ol> <p>図 12-1 の「マージソート」は、分割統治戦略の典型的な応用例です。</p> <ol> <li>分割:元の配列(元の問題)を二つの部分配列(部分問題)に再帰的に分割し、部分配列の要素が一つ(最小の部分問題)になるまで続けます。</li> <li>統合:ソート済みの部分配列(部分問題の解)を下から上へと統合し、元の配列をソートした結果(元の問題の解)を得ます。</li> </ol> <p></p> <p> 図 12-1   マージソートの分割統治戦略 </p>","path":["第 12 章   分割統治","12.1   分割統治アルゴリズム"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1211","level":2,"title":"12.1.1   分割統治で解ける問題の見分け方","text":"<p>ある問題が分割統治に適しているかどうかは、通常、次の点を基準に判断できます。</p> <ol> <li>問題を分解できる:元の問題を、同じような形のより小さな部分問題に分解でき、同じ方法で再帰的に分割できます。</li> <li>部分問題が独立している:部分問題同士が重ならず、互いに依存せず、独立に解けます。</li> <li>部分問題の解を統合できる:部分問題の解を統合することで、元の問題の解が得られます。</li> </ol> <p>マージソートがこの三つの基準を満たすことは明らかです。</p> <ol> <li>問題を分解できる:配列(元の問題)を二つの部分配列(部分問題)へ再帰的に分割します。</li> <li>部分問題が独立している:各部分配列は独立にソートできます(部分問題を独立に解けます)。</li> <li>部分問題の解を統合できる:二つのソート済み部分配列(部分問題の解)を、一つのソート済み配列(元の問題の解)に統合できます。</li> </ol>","path":["第 12 章   分割統治","12.1   分割統治アルゴリズム"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1212","level":2,"title":"12.1.2   分割統治による効率の向上","text":"<p>分割統治はアルゴリズムの問題を解くうえで有効なだけでなく、効率も高められることがよくあります。ソートアルゴリズムのうち、クイックソート、マージソート、ヒープソートが、選択ソート、バブルソート、挿入ソートより高速なのは、分割統治戦略を利用しているためです。</p> <p>ここで、分割統治がアルゴリズムを効率化できるのはなぜか、その根本的な理由は何か、という疑問が湧きます。言い換えると、大きな問題を複数の部分問題に分解し、それらを解き、解を統合するという手順が、元の問題を直接解くより効率的なのはなぜでしょうか。操作回数と並列計算という二つの面から考えてみましょう。</p>","path":["第 12 章   分割統治","12.1   分割統治アルゴリズム"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1","level":3,"title":"1.   操作回数の削減","text":"<p>「バブルソート」を例にすると、長さ \\(n\\) の配列の処理には \\(O(n^2)\\) 時間かかります。図 12-2 のように、配列を中央で二つの部分配列に分けるとします。分割に \\(O(n)\\) 時間、各部分配列のソートに \\(O((n / 2)^2)\\) 時間、二つの部分配列の統合に \\(O(n)\\) 時間かかるので、全体の時間計算量は次のようになります。</p> \\[ O(n + (\\frac{n}{2})^2 \\times 2 + n) = O(\\frac{n^2}{2} + 2n) \\] <p></p> <p> 図 12-2   配列を分割する前と後のバブルソート </p> <p>次の不等式を計算してみます。左辺と右辺はそれぞれ、分割前と分割後の総操作回数です。</p> \\[ \\begin{aligned} n^2 &amp; &gt; \\frac{n^2}{2} + 2n \\newline n^2 - \\frac{n^2}{2} - 2n &amp; &gt; 0 \\newline n(n - 4) &amp; &gt; 0 \\end{aligned} \\] <p>つまり、\\(n &gt; 4\\) のときは分割後の操作回数が少なくなり、ソートの効率が上がると考えられます。ただし、分割後の時間計算量も依然として二乗のオーダー \\(O(n^2)\\) であり、計算量の定数部分が小さくなっただけです。</p> <p>さらに、部分配列を中央で二つに分ける操作を繰り返し、要素が一つになるまで続けたらどうなるでしょうか。これはまさに「マージソート」の考え方であり、時間計算量は \\(O(n \\log n)\\) になります。</p> <p>では、分割点をもっと増やして、元の配列を \\(k\\) 個の部分配列に均等に分けたらどうでしょうか。これは「バケットソート」によく似ています。大量のデータのソートに適しており、理論上は時間計算量を \\(O(n + k)\\) にできます。</p>","path":["第 12 章   分割統治","12.1   分割統治アルゴリズム"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#2","level":3,"title":"2.   並列計算による効率化","text":"<p>分割統治でできる部分問題は互いに独立しているため、通常は並列に解けます。つまり、分割統治はアルゴリズムの時間計算量を減らすだけでなく、OS による並列化にも適しています。</p> <p>並列化は、複数のコアやプロセッサを持つ環境で特に効果を発揮します。システムが複数の部分問題を同時に処理し、計算資源をより十分に利用できるため、全体の実行時間を大幅に短縮できます。</p> <p>たとえば図 12-3 の「バケットソート」では、大量のデータを各バケットに均等に分配します。各バケットのソートをそれぞれの計算ユニットに割り当て、完了後に結果を統合できます。</p> <p></p> <p> 図 12-3   バケットソートの並列計算 </p>","path":["第 12 章   分割統治","12.1   分割統治アルゴリズム"],"tags":[]},{"location":"chapter_divide_and_conquer/divide_and_conquer/#1213","level":2,"title":"12.1.3   分割統治の主な用途","text":"<p>分割統治は、多くの古典的なアルゴリズムの問題を解くために使えます。</p> <ul> <li>最近点対の探索:まず点の集合を二つに分け、それぞれの中で最も近い点のペアを求め、最後に二つの集合にまたがる最も近い点のペアを求めます。</li> <li>大きな整数の乗算:たとえば Karatsuba 法は、大きな整数同士の乗算を、より小さな整数の乗算と加算に分解します。</li> <li>行列の乗算:たとえば Strassen 法は、大きな行列の乗算を、複数の小さな行列の乗算と加算に分解します。</li> <li>ハノイの塔の問題:再帰で解くことができ、分割統治戦略の典型的な応用です。</li> <li>転倒対の計数:数列で前の数が後ろの数より大きいとき、その二つの数は転倒対をなします。転倒対を求める問題は、分割統治の考え方を使い、マージソートを利用して解けます。</li> </ul> <p>また、分割統治はアルゴリズムとデータ構造の設計にも広く使われています。</p> <ul> <li>二分探索:ソート済み配列を中央のインデックスで二つに分け、目的の値と中央の要素を比較して、どちらの半分を除外するか決めます。残った区間に対して同じ二分操作を行います。</li> <li>マージソート:この節の冒頭で説明したため、ここでは繰り返しません。</li> <li>クイックソート:ピボットを一つ選び、それより小さい要素の部分配列と、大きい要素の部分配列に分けます。それぞれに同じ分割操作を行い、部分配列の要素が一つになるまで続けます。</li> <li>バケットソート:データを複数のバケットに分配し、各バケット内の要素をソートしてから、バケットごとに順に取り出して、ソート済み配列を得ます。</li> <li>木:二分探索木、AVL 木、赤黒木、B 木、B+ 木などでは、探索・挿入・削除などの操作を、分割統治戦略の応用として捉えられます。</li> <li>ヒープ:ヒープは特殊な完全二分木です。挿入、削除、ヒープ化などの各種操作には、分割統治の考え方が内在しています。</li> <li>ハッシュテーブル:分割統治を直接使うわけではありませんが、ハッシュ衝突の解決方法には間接的に使うものがあります。たとえば、チェイン法では長い連結リストを赤黒木に変換し、探索効率を高めます。</li> </ul> <p>このように、分割統治はさまざまなアルゴリズムやデータ構造に自然に溶け込んでいる考え方なのです。</p>","path":["第 12 章   分割統治","12.1   分割統治アルゴリズム"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/","level":1,"title":"12.6   練習問題","text":"","path":["第 12 章   分割統治","12.6   練習問題"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1261","level":2,"title":"12.6.1   知識の確認","text":"","path":["第 12 章   分割統治","12.6   練習問題"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1","level":3,"title":"1.   分割統治に適した作業","text":"<p>ある生徒が「まず半分に分け、それぞれを解き、結果を結合する」方法で次の作業を行おうとしています。 それぞれを「分割統治に適している」「分割統治はできるが総作業量は減らない」「二つを独立に解けない」のいずれかに分類し、理由を説明してください。</p> <ol> <li>未整列配列をソートします。</li> <li>配列の最大値を求めます。</li> <li>一連のスタック操作 <code>push(x)</code>、<code>pop()</code> を順に実行し、各 <code>pop()</code> で得た要素を出力します。</li> </ol> 解答例 <ol> <li>適しています。半分に分割し、それぞれを独立にソートして、\\(O(n)\\) 時間で結合します。これはマージソートそのものです。</li> <li>分割統治はできますが、総作業量は減りません。左右の半分を合わせると、全 \\(n\\) 要素を調べる必要があり、 直接走査する場合と同じ \\(O(n)\\) 時間です。</li> <li>二つを独立には解けません。後半開始時のスタックの内容は前半の実行結果に依存するため、 互いの結果を知らずに独立して完了することはできません。</li> </ol>","path":["第 12 章   分割統治","12.6   練習問題"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#2","level":3,"title":"2.   高速べき乗で計算を減らす仕組み","text":"<p>次の再帰関数は、分割統治で \\(x^n\\) を計算します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby fast_power.py<pre><code>def fast_pow(x: int, n: int) -&gt; int:\n \"\"\"高速べき乗法\"\"\"\n if n == 0:\n return 1\n half = fast_pow(x, n // 2)\n if n % 2 == 0:\n return half * half\n return half * half * x\n</code></pre> fast_power.cpp<pre><code>/* 高速べき乗法 */\nint fastPow(int x, int n) {\n if (n == 0) {\n return 1;\n }\n int half = fastPow(x, n / 2);\n if (n % 2 == 0) {\n return half * half;\n }\n return half * half * x;\n}\n</code></pre> fast_power.java<pre><code>/* 高速べき乗法 */\nint fastPow(int x, int n) {\n if (n == 0) {\n return 1;\n }\n int half = fastPow(x, n / 2);\n if (n % 2 == 0) {\n return half * half;\n }\n return half * half * x;\n}\n</code></pre> fast_power.cs<pre><code>/* 高速べき乗法 */\nint FastPow(int x, int n) {\n if (n == 0) {\n return 1;\n }\n int half = FastPow(x, n / 2);\n if (n % 2 == 0) {\n return half * half;\n }\n return half * half * x;\n}\n</code></pre> fast_power.go<pre><code>/* 高速べき乗法 */\nfunc fastPow(x, n int) int {\n if n == 0 {\n return 1\n }\n half := fastPow(x, n/2)\n if n%2 == 0 {\n return half * half\n }\n return half * half * x\n}\n</code></pre> fast_power.swift<pre><code>/* 高速べき乗法 */\nfunc fastPow(x: Int, n: Int) -&gt; Int {\n if n == 0 {\n return 1\n }\n let half = fastPow(x: x, n: n / 2)\n if n % 2 == 0 {\n return half * half\n }\n return half * half * x\n}\n</code></pre> fast_power.js<pre><code>/* 高速べき乗法 */\nfunction fastPow(x, n) {\n if (n === 0) {\n return 1;\n }\n const half = fastPow(x, Math.floor(n / 2));\n if (n % 2 === 0) {\n return half * half;\n }\n return half * half * x;\n}\n</code></pre> fast_power.ts<pre><code>/* 高速べき乗法 */\nfunction fastPow(x: number, n: number): number {\n if (n === 0) {\n return 1;\n }\n const half = fastPow(x, Math.floor(n / 2));\n if (n % 2 === 0) {\n return half * half;\n }\n return half * half * x;\n}\n</code></pre> fast_power.dart<pre><code>/* 高速べき乗法 */\nint fastPow(int x, int n) {\n if (n == 0) {\n return 1;\n }\n int half = fastPow(x, n ~/ 2);\n if (n % 2 == 0) {\n return half * half;\n }\n return half * half * x;\n}\n</code></pre> fast_power.rs<pre><code>/* 高速べき乗法 */\nfn fast_pow(x: i32, n: i32) -&gt; i32 {\n if n == 0 {\n return 1;\n }\n let half = fast_pow(x, n / 2);\n if n % 2 == 0 {\n return half * half;\n }\n half * half * x\n}\n</code></pre> fast_power.c<pre><code>/* 高速べき乗法 */\nint fastPow(int x, int n) {\n if (n == 0) {\n return 1;\n }\n int half = fastPow(x, n / 2);\n if (n % 2 == 0) {\n return half * half;\n }\n return half * half * x;\n}\n</code></pre> fast_power.kt<pre><code>/* 高速べき乗法 */\nfun fastPow(x: Int, n: Int): Int {\n if (n == 0) {\n return 1\n }\n val half = fastPow(x, n / 2)\n if (n % 2 == 0) {\n return half * half\n }\n return half * half * x\n}\n</code></pre> fast_power.rb<pre><code>### 高速べき乗法 ###\ndef fast_pow(x, n)\n return 1 if n == 0\n\n half = fast_pow(x, n / 2)\n return half * half if n.even?\n\n half * half * x\nend\n</code></pre> <p><code>x = 3</code>、<code>n = 5</code> として、この関数で計算します。</p> <ol> <li>再帰呼び出しで、引数 <code>n</code> は順にどの値になりますか。</li> <li>最も深い呼び出しから戻るとき、各呼び出しは順にどの値を返しますか。</li> <li>乗算の両側で同じ部分問題を一回ずつ呼び出さず、先に再帰の結果を <code>half</code> に保存するのはなぜですか。</li> </ol> 解答例 <ol> <li> <p>引数は順に <code>5 → 2 → 1 → 0</code> となり、終了条件に達するまで、毎回指数を半分にします。</p> </li> <li> <p><code>n = 0</code> で 1、<code>n = 1</code> で \\(1×1×3=3\\)、 <code>n = 2</code> で \\(3×3=9\\)、<code>n = 5</code> で \\(9×9×3=243\\) を返します。</p> </li> <li> <p>乗算の両側で同じ部分問題を呼び出すと、二回の再帰でまったく同じ計算を行います。 先に結果を <code>half</code> に保存すれば、各層での再帰は一回だけとなり、再帰の深さは約 \\(\\log n\\) です。 二回呼び出すと、大量の重複計算が生じます。</p> </li> </ol>","path":["第 12 章   分割統治","12.6   練習問題"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#3","level":3,"title":"3.   走査列を左右の部分木に分ける","text":"<p>重複するノードのない二分木の行きがけ順と通りがけ順の走査列は、次のとおりです。</p> <ul> <li>行きがけ順:<code>[A, B, D, E, C]</code></li> <li>通りがけ順:<code>[D, B, E, A, C]</code></li> </ul> <p>根の層での分割だけを行ってください。それ以上再帰したり、木全体を描いたりする必要はありません。</p> <ol> <li>根ノードはどれですか。</li> <li>左右の部分木は、通りがけ順の列のどの部分に対応しますか。</li> <li>左右の部分木は、行きがけ順の列のどの部分に対応しますか。根の直接の子はどれですか。</li> </ol> 解答例 <ol> <li> <p>行きがけ順の最初のノードが根なので、根は <code>A</code> です。</p> </li> <li> <p><code>A</code> が通りがけ順の列を二つに分けます。左部分木は <code>[D, B, E]</code>、右部分木は <code>[C]</code> です。</p> </li> <li> <p>左部分木には 3 個のノードがあるため、行きがけ順で根 <code>A</code> の後ろの 3 要素が左部分木に属し、 <code>[B, D, E]</code> となります。残りの <code>[C]</code> が右部分木です。 したがって、根の左の子は <code>B</code>、右の子は <code>C</code> です。</p> </li> </ol>","path":["第 12 章   分割統治","12.6   練習問題"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1262","level":2,"title":"12.6.2   プログラミング演習","text":"","path":["第 12 章   分割統治","12.6   練習問題"],"tags":[]},{"location":"chapter_divide_and_conquer/exercises/#1_1","level":3,"title":"1.   高速べき乗","text":"<p>実数 <code>x</code> と整数 <code>n</code> が与えられます。言語に組み込まれたべき乗関数を呼び出さず、\\(x^n\\) を計算してください。 再帰的な分割統治を使い、毎回指数を半分にし、計算済みの部分問題の結果を再利用してください。 この問題では <code>x = 0</code> の場合も含めて \\(x^0=1\\) とします。<code>n &lt; 0</code> の場合は <code>x != 0</code> が保証され、答えを \\((1/x)^{-n}\\) に変形できます。</p> 解くためのヒント <ol> <li>n が 0 なら答えは 1 です</li> <li>x の n // 2 乗を計算したら half に保存し、同じ再帰を二度呼び出さないようにします</li> <li>n &lt; 0 の場合は、まず x を 1 / x に、n を -n に変えます。C++ や Java では、n を先に 64 ビット整数へ変換すると、32 ビット整数の最小値の符号を反転するときのオーバーフローを防げます</li> </ol> <p>LeetCode</p>","path":["第 12 章   分割統治","12.6   練習問題"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/","level":1,"title":"12.4   ハノイの塔の問題","text":"<p>マージソートと二分木の構築では、元の問題をそれぞれ半分の大きさの二つの部分問題に分解しました。一方、ハノイの塔の問題では、別の分解戦略を使います。</p> <p>問題</p> <p>3 本の柱 <code>A</code>、<code>B</code>、<code>C</code> が与えられます。初期状態では、柱 <code>A</code> に \\(n\\) 枚の円盤があり、上から下へ、小さい順に重なっています。この \\(n\\) 枚を、元の順序を保ったまま柱 <code>C</code> に移してください(図 12-10 参照)。円盤を動かす際は、次の規則に従います。</p> <ol> <li>円盤は柱の一番上から取り出し、別の柱の一番上に置くことしかできません。</li> <li>一度に動かせる円盤は 1 枚だけです。</li> <li>小さな円盤は、常に大きな円盤の上に置かなければなりません。</li> </ol> <p></p> <p> 図 12-10   ハノイの塔の問題の例 </p> <p>大きさ \\(i\\) のハノイの塔の問題を \\(f(i)\\) と表します。たとえば \\(f(3)\\) は、\\(3\\) 枚の円盤を <code>A</code> から <code>C</code> に移す問題です。</p>","path":["第 12 章   分割統治","12.4   ハノイの塔の問題"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#1","level":3,"title":"1.   基本的な場合を考える","text":"<p>図 12-11 のように、問題 \\(f(1)\\)、つまり円盤が 1 枚だけの場合は、その円盤を <code>A</code> から <code>C</code> に直接移せば完了です。</p> &lt;1&gt;&lt;2&gt; <p></p> <p></p> <p> 図 12-11   大きさ 1 の問題の解 </p> <p>図 12-12 のように、問題 \\(f(2)\\)、つまり円盤が 2 枚の場合は、小さな円盤を常に大きな円盤の上に置くため、<code>B</code> を利用して移す必要があります。</p> <ol> <li>まず、上にある小さな円盤を <code>A</code> から <code>B</code> に移します。</li> <li>次に、大きな円盤を <code>A</code> から <code>C</code> に移します。</li> <li>最後に、小さな円盤を <code>B</code> から <code>C</code> に移します。</li> </ol> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt; <p></p> <p></p> <p></p> <p></p> <p> 図 12-12   大きさ 2 の問題の解 </p> <p>問題 \\(f(2)\\) を解く手順は、<code>B</code> を使って 2 枚の円盤を <code>A</code> から <code>C</code> に移すこととまとめられます。このとき、<code>C</code> を目的の柱、<code>B</code> を補助の柱と呼びます。</p>","path":["第 12 章   分割統治","12.4   ハノイの塔の問題"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#2","level":3,"title":"2.   部分問題への分解","text":"<p>問題 \\(f(3)\\)、つまり円盤が 3 枚の場合は、少し複雑になります。</p> <p>\\(f(1)\\) と \\(f(2)\\) の解はわかっているので、分割統治の観点から、<code>A</code> の上側の 2 枚を一つのまとまりとみなし、図 12-13 の手順を実行します。これで 3 枚の円盤を <code>A</code> から <code>C</code> に移せます。</p> <ol> <li><code>B</code> を目的の柱、<code>C</code> を補助の柱として、2 枚の円盤を <code>A</code> から <code>B</code> に移します。</li> <li><code>A</code> に残った 1 枚を、<code>A</code> から <code>C</code> に直接移します。</li> <li><code>C</code> を目的の柱、<code>A</code> を補助の柱として、2 枚の円盤を <code>B</code> から <code>C</code> に移します。</li> </ol> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt; <p></p> <p></p> <p></p> <p></p> <p> 図 12-13   大きさ 3 の問題の解 </p> <p>本質的には、問題 \\(f(3)\\) を、二つの部分問題 \\(f(2)\\) と、一つの部分問題 \\(f(1)\\) に分割しています。この三つを順に解けば、元の問題も解けます。部分問題は独立しており、解を統合できるということです。</p> <p>ここまでの考察から、図 12-14 のように、ハノイの塔を解く分割統治戦略をまとめられます。元の問題 \\(f(n)\\) を、二つの部分問題 \\(f(n-1)\\) と一つの部分問題 \\(f(1)\\) に分割し、次の順で解きます。</p> <ol> <li><code>C</code> を使って \\(n-1\\) 枚の円盤を <code>A</code> から <code>B</code> に移します。</li> <li>残りの \\(1\\) 枚を <code>A</code> から <code>C</code> に直接移します。</li> <li><code>A</code> を使って \\(n-1\\) 枚の円盤を <code>B</code> から <code>C</code> に移します。</li> </ol> <p>二つの部分問題 \\(f(n-1)\\) は、同じ方法で再帰的に分割でき、最小の部分問題 \\(f(1)\\) に到達するまで続けます。\\(f(1)\\) の解はわかっており、1 回移動するだけです。</p> <p></p> <p> 図 12-14   ハノイの塔を解く分割統治戦略 </p>","path":["第 12 章   分割統治","12.4   ハノイの塔の問題"],"tags":[]},{"location":"chapter_divide_and_conquer/hanota_problem/#3","level":3,"title":"3.   コードの実装","text":"<p>コードでは、再帰関数 <code>dfs(i, src, buf, tar)</code> を定義します。この関数は、柱 <code>src</code> の上側にある \\(i\\) 枚の円盤を、補助の柱 <code>buf</code> を使って目的の柱 <code>tar</code> に移します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hanota.py<pre><code>def move(src: list[int], tar: list[int]):\n \"\"\"円盤を 1 枚移動する\"\"\"\n # src の一番上から円盤を 1 枚取り出す\n pan = src.pop()\n # tar の一番上に円盤を置く\n tar.append(pan)\n\ndef dfs(i: int, src: list[int], buf: list[int], tar: list[int]):\n \"\"\"ハノイの塔の問題 f(i) を解く\"\"\"\n # src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if i == 1:\n move(src, tar)\n return\n # 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfs(i - 1, src, tar, buf)\n # 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move(src, tar)\n # 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfs(i - 1, buf, src, tar)\n\ndef solve_hanota(A: list[int], B: list[int], C: list[int]):\n \"\"\"ハノイの塔の問題を解く\"\"\"\n n = len(A)\n # A の上から n 枚の円盤を、B を使って C に移す\n dfs(n, A, B, C)\n</code></pre> hanota.cpp<pre><code>/* 円盤を 1 枚移動する */\nvoid move(vector&lt;int&gt; &amp;src, vector&lt;int&gt; &amp;tar) {\n // src の一番上から円盤を 1 枚取り出す\n int pan = src.back();\n src.pop_back();\n // tar の一番上に円盤を置く\n tar.push_back(pan);\n}\n\n/* ハノイの塔の問題 f(i) を解く */\nvoid dfs(int i, vector&lt;int&gt; &amp;src, vector&lt;int&gt; &amp;buf, vector&lt;int&gt; &amp;tar) {\n // src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if (i == 1) {\n move(src, tar);\n return;\n }\n // 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfs(i - 1, src, tar, buf);\n // 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move(src, tar);\n // 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfs(i - 1, buf, src, tar);\n}\n\n/* ハノイの塔の問題を解く */\nvoid solveHanota(vector&lt;int&gt; &amp;A, vector&lt;int&gt; &amp;B, vector&lt;int&gt; &amp;C) {\n int n = A.size();\n // A の上から n 枚の円盤を、B を使って C に移す\n dfs(n, A, B, C);\n}\n</code></pre> hanota.java<pre><code>/* 円盤を 1 枚移動する */\nvoid move(List&lt;Integer&gt; src, List&lt;Integer&gt; tar) {\n // src の一番上から円盤を 1 枚取り出す\n Integer pan = src.remove(src.size() - 1);\n // tar の一番上に円盤を置く\n tar.add(pan);\n}\n\n/* ハノイの塔の問題 f(i) を解く */\nvoid dfs(int i, List&lt;Integer&gt; src, List&lt;Integer&gt; buf, List&lt;Integer&gt; tar) {\n // src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if (i == 1) {\n move(src, tar);\n return;\n }\n // 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfs(i - 1, src, tar, buf);\n // 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move(src, tar);\n // 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfs(i - 1, buf, src, tar);\n}\n\n/* ハノイの塔の問題を解く */\nvoid solveHanota(List&lt;Integer&gt; A, List&lt;Integer&gt; B, List&lt;Integer&gt; C) {\n int n = A.size();\n // A の上から n 枚の円盤を、B を使って C に移す\n dfs(n, A, B, C);\n}\n</code></pre> hanota.cs<pre><code>/* 円盤を 1 枚移動する */\nvoid Move(List&lt;int&gt; src, List&lt;int&gt; tar) {\n // src の一番上から円盤を 1 枚取り出す\n int pan = src[^1];\n src.RemoveAt(src.Count - 1);\n // tar の一番上に円盤を置く\n tar.Add(pan);\n}\n\n/* ハノイの塔の問題 f(i) を解く */\nvoid DFS(int i, List&lt;int&gt; src, List&lt;int&gt; buf, List&lt;int&gt; tar) {\n // src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if (i == 1) {\n Move(src, tar);\n return;\n }\n // 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n DFS(i - 1, src, tar, buf);\n // 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n Move(src, tar);\n // 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n DFS(i - 1, buf, src, tar);\n}\n\n/* ハノイの塔の問題を解く */\nvoid SolveHanota(List&lt;int&gt; A, List&lt;int&gt; B, List&lt;int&gt; C) {\n int n = A.Count;\n // A の上から n 枚の円盤を、B を使って C に移す\n DFS(n, A, B, C);\n}\n</code></pre> hanota.go<pre><code>/* 円盤を 1 枚移動する */\nfunc move(src, tar *list.List) {\n // src の一番上から円盤を 1 枚取り出す\n pan := src.Back()\n // tar の一番上に円盤を置く\n tar.PushBack(pan.Value)\n // src の一番上の円盤を取り除く\n src.Remove(pan)\n}\n\n/* ハノイの塔の問題 f(i) を解く */\nfunc dfsHanota(i int, src, buf, tar *list.List) {\n // src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if i == 1 {\n move(src, tar)\n return\n }\n // 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfsHanota(i-1, src, tar, buf)\n // 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move(src, tar)\n // 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfsHanota(i-1, buf, src, tar)\n}\n\n/* ハノイの塔の問題を解く */\nfunc solveHanota(A, B, C *list.List) {\n n := A.Len()\n // A の上から n 枚の円盤を、B を使って C に移す\n dfsHanota(n, A, B, C)\n}\n</code></pre> hanota.swift<pre><code>/* 円盤を 1 枚移動する */\nfunc move(src: inout [Int], tar: inout [Int]) {\n // src の一番上から円盤を 1 枚取り出す\n let pan = src.popLast()!\n // tar の一番上に円盤を置く\n tar.append(pan)\n}\n\n/* ハノイの塔の問題 f(i) を解く */\nfunc dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) {\n // src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if i == 1 {\n move(src: &amp;src, tar: &amp;tar)\n return\n }\n // 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfs(i: i - 1, src: &amp;src, buf: &amp;tar, tar: &amp;buf)\n // 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move(src: &amp;src, tar: &amp;tar)\n // 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfs(i: i - 1, src: &amp;buf, buf: &amp;src, tar: &amp;tar)\n}\n\n/* ハノイの塔の問題を解く */\nfunc solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) {\n let n = A.count\n // リストの末尾が棒の一番上に対応する\n // src の上から n 枚の円盤を、B を使って C に移す\n dfs(i: n, src: &amp;A, buf: &amp;B, tar: &amp;C)\n}\n</code></pre> hanota.js<pre><code>/* 円盤を 1 枚移動する */\nfunction move(src, tar) {\n // src の一番上から円盤を 1 枚取り出す\n const pan = src.pop();\n // tar の一番上に円盤を置く\n tar.push(pan);\n}\n\n/* ハノイの塔の問題 f(i) を解く */\nfunction dfs(i, src, buf, tar) {\n // src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if (i === 1) {\n move(src, tar);\n return;\n }\n // 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfs(i - 1, src, tar, buf);\n // 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move(src, tar);\n // 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfs(i - 1, buf, src, tar);\n}\n\n/* ハノイの塔の問題を解く */\nfunction solveHanota(A, B, C) {\n const n = A.length;\n // A の上から n 枚の円盤を、B を使って C に移す\n dfs(n, A, B, C);\n}\n</code></pre> hanota.ts<pre><code>/* 円盤を 1 枚移動する */\nfunction move(src: number[], tar: number[]): void {\n // src の一番上から円盤を 1 枚取り出す\n const pan = src.pop();\n // tar の一番上に円盤を置く\n tar.push(pan);\n}\n\n/* ハノイの塔の問題 f(i) を解く */\nfunction dfs(i: number, src: number[], buf: number[], tar: number[]): void {\n // src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if (i === 1) {\n move(src, tar);\n return;\n }\n // 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfs(i - 1, src, tar, buf);\n // 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move(src, tar);\n // 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfs(i - 1, buf, src, tar);\n}\n\n/* ハノイの塔の問題を解く */\nfunction solveHanota(A: number[], B: number[], C: number[]): void {\n const n = A.length;\n // A の上から n 枚の円盤を、B を使って C に移す\n dfs(n, A, B, C);\n}\n</code></pre> hanota.dart<pre><code>/* 円盤を 1 枚移動する */\nvoid move(List&lt;int&gt; src, List&lt;int&gt; tar) {\n // src の一番上から円盤を 1 枚取り出す\n int pan = src.removeLast();\n // tar の一番上に円盤を置く\n tar.add(pan);\n}\n\n/* ハノイの塔の問題 f(i) を解く */\nvoid dfs(int i, List&lt;int&gt; src, List&lt;int&gt; buf, List&lt;int&gt; tar) {\n // src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if (i == 1) {\n move(src, tar);\n return;\n }\n // 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfs(i - 1, src, tar, buf);\n // 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move(src, tar);\n // 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfs(i - 1, buf, src, tar);\n}\n\n/* ハノイの塔の問題を解く */\nvoid solveHanota(List&lt;int&gt; A, List&lt;int&gt; B, List&lt;int&gt; C) {\n int n = A.length;\n // A の上から n 枚の円盤を、B を使って C に移す\n dfs(n, A, B, C);\n}\n</code></pre> hanota.rs<pre><code>/* 円盤を 1 枚移動する */\nfn move_pan(src: &amp;mut Vec&lt;i32&gt;, tar: &amp;mut Vec&lt;i32&gt;) {\n // src の一番上から円盤を 1 枚取り出す\n let pan = src.pop().unwrap();\n // tar の一番上に円盤を置く\n tar.push(pan);\n}\n\n/* ハノイの塔の問題 f(i) を解く */\nfn dfs(i: i32, src: &amp;mut Vec&lt;i32&gt;, buf: &amp;mut Vec&lt;i32&gt;, tar: &amp;mut Vec&lt;i32&gt;) {\n // src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if i == 1 {\n move_pan(src, tar);\n return;\n }\n // 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfs(i - 1, src, tar, buf);\n // 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move_pan(src, tar);\n // 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfs(i - 1, buf, src, tar);\n}\n\n/* ハノイの塔の問題を解く */\nfn solve_hanota(A: &amp;mut Vec&lt;i32&gt;, B: &amp;mut Vec&lt;i32&gt;, C: &amp;mut Vec&lt;i32&gt;) {\n let n = A.len() as i32;\n // A の上から n 枚の円盤を、B を使って C に移す\n dfs(n, A, B, C);\n}\n</code></pre> hanota.c<pre><code>/* 円盤を 1 枚移動する */\nvoid move(int *src, int *srcSize, int *tar, int *tarSize) {\n // src の一番上から円盤を 1 枚取り出す\n int pan = src[*srcSize - 1];\n src[*srcSize - 1] = 0;\n (*srcSize)--;\n // tar の一番上に円盤を置く\n tar[*tarSize] = pan;\n (*tarSize)++;\n}\n\n/* ハノイの塔の問題 f(i) を解く */\nvoid dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) {\n // src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if (i == 1) {\n move(src, srcSize, tar, tarSize);\n return;\n }\n // 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize);\n // 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move(src, srcSize, tar, tarSize);\n // 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize);\n}\n\n/* ハノイの塔の問題を解く */\nvoid solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) {\n // A の上から n 枚の円盤を、B を使って C に移す\n dfs(*ASize, A, ASize, B, BSize, C, CSize);\n}\n</code></pre> hanota.kt<pre><code>/* 円盤を 1 枚移動する */\nfun move(src: MutableList&lt;Int&gt;, tar: MutableList&lt;Int&gt;) {\n // src の一番上から円盤を 1 枚取り出す\n val pan = src.removeAt(src.size - 1)\n // tar の一番上に円盤を置く\n tar.add(pan)\n}\n\n/* ハノイの塔の問題 f(i) を解く */\nfun dfs(i: Int, src: MutableList&lt;Int&gt;, buf: MutableList&lt;Int&gt;, tar: MutableList&lt;Int&gt;) {\n // src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if (i == 1) {\n move(src, tar)\n return\n }\n // 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfs(i - 1, src, tar, buf)\n // 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move(src, tar)\n // 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfs(i - 1, buf, src, tar)\n}\n\n/* ハノイの塔の問題を解く */\nfun solveHanota(A: MutableList&lt;Int&gt;, B: MutableList&lt;Int&gt;, C: MutableList&lt;Int&gt;) {\n val n = A.size\n // A の上から n 枚の円盤を、B を使って C に移す\n dfs(n, A, B, C)\n}\n</code></pre> hanota.rb<pre><code>### 円盤を 1 枚移動する ###\ndef move(src, tar)\n # src の一番上から円盤を 1 枚取り出す\n pan = src.pop\n # tar の一番上に円盤を置く\n tar &lt;&lt; pan\nend\n\n### ハノイの塔の問題 f(i) を解く ###\ndef dfs(i, src, buf, tar)\n # src に円盤が 1 枚だけ残っている場合、そのまま tar に移す\n if i == 1\n move(src, tar)\n return\n end\n\n # 部分問題 f(i-1) :src の上から i-1 枚の円盤を、tar を使って buf に移す\n dfs(i - 1, src, tar, buf)\n # 部分問題 f(1) :src に残った 1 枚の円盤を tar に移す\n move(src, tar)\n # 部分問題 f(i-1) :buf の上から i-1 枚の円盤を、src を使って tar に移す\n dfs(i - 1, buf, src, tar)\nend\n\n### ハノイの塔の問題を解く ###\ndef solve_hanota(_A, _B, _C)\n n = _A.length\n # A の上から n 枚の円盤を、B を使って C に移す\n dfs(n, _A, _B, _C)\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 12-15 のように、ハノイの塔の問題は高さ \\(n\\) の再帰木をなします。各ノードは一つの部分問題を表し、1 回の <code>dfs()</code> の呼び出しに対応します。したがって、時間計算量は \\(O(2^n)\\)、空間計算量は \\(O(n)\\) です。</p> <p></p> <p> 図 12-15   ハノイの塔の問題の再帰木 </p> <p>引用</p> <p>ハノイの塔の問題は、古い伝説に由来します。古代インドのある寺院に、3 本の高いダイヤモンドの柱と、大きさの異なる \\(64\\) 枚の金の円盤がありました。僧侶たちは円盤を動かし続け、最後の円盤を正しい位置に置いた瞬間に、この世界が終わると信じていました。</p> <p>しかし、仮に毎秒 1 回ずつ動かしても、合計で約 \\(2^{64} \\approx 1.84×10^{19}\\) 秒、すなわち約 \\(5850\\) 億年かかります。これは現在推定されている宇宙の年齢をはるかに超えます。したがって、この伝説が本当だとしても、世界の終わりを心配する必要はなさそうです。</p>","path":["第 12 章   分割統治","12.4   ハノイの塔の問題"],"tags":[]},{"location":"chapter_divide_and_conquer/summary/","level":1,"title":"12.5   まとめ","text":"","path":["第 12 章   分割統治","12.5   まとめ"],"tags":[]},{"location":"chapter_divide_and_conquer/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>分割統治は、分割と統合という二つの段階からなる、よく使われるアルゴリズムの設計戦略です。通常は再帰で実装します。</li> <li>分割統治で解けるかどうかは、問題を分解できるか、部分問題が独立しているか、部分問題の解を統合できるか、という点から判断します。</li> <li>マージソートは分割統治の典型例です。配列を同じ長さの二つの部分配列へ再帰的に分割し、要素が一つになったら段階的に統合して、ソートを完了します。</li> <li>分割統治戦略を取り入れると、アルゴリズムの効率が向上することがよくあります。操作回数を減らせるうえ、システムによる並列化にも適しているためです。</li> <li>分割統治は多くのアルゴリズムの問題を解くだけでなく、データ構造やアルゴリズムの設計にも広く使われており、いたるところにその考え方が見られます。</li> <li>適応的探索は全探索より効率的です。時間計算量が \\(O(\\log n)\\) の探索アルゴリズムは、通常、分割統治戦略に基づいています。</li> <li>二分探索も分割統治の典型例ですが、部分問題の解を統合する段階はありません。再帰による分割統治で二分探索を実装できます。</li> <li>二分木の構築問題では、木の構築(元の問題)を、左部分木と右部分木の構築(部分問題)に分割できます。これは、行きがけ順走査と通りがけ順走査のインデックス区間を分割することで実現できます。</li> <li>ハノイの塔の問題では、大きさ \\(n\\) の問題を、大きさ \\(n-1\\) の二つの部分問題と、大きさ \\(1\\) の一つの部分問題に分割できます。この三つを順に解けば、元の問題も解けます。</li> </ul>","path":["第 12 章   分割統治","12.5   まとめ"],"tags":[]},{"location":"chapter_dynamic_programming/","level":1,"title":"第 14 章   動的計画法","text":"<p>概要</p> <p>小川は河川に合流し、河川は大海へと注ぎます。</p> <p>動的計画法は、小さな問題の解を集めて大きな問題の答えを作り、問題解決という対岸へ一歩ずつ導いてくれます。</p>","path":["第 14 章   動的計画法"],"tags":[]},{"location":"chapter_dynamic_programming/#_1","level":2,"title":"この章の内容","text":"<ul> <li>14.1   動的計画法への第一歩</li> <li>14.2   動的計画法の問題の特徴</li> <li>14.3   動的計画法の解き方</li> <li>14.4   0-1 ナップサック問題</li> <li>14.5   個数制限なしナップサック問題</li> <li>14.6   編集距離問題</li> <li>14.7   まとめ</li> <li>14.8   練習問題</li> </ul>","path":["第 14 章   動的計画法"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/","level":1,"title":"14.2   動的計画法の問題の特徴","text":"<p>前の節では、動的計画法が部分問題への分解によって元の問題を解く仕組みを学びました。部分問題への分解は汎用的な考え方ですが、分割統治、動的計画法、バックトラッキングでは重点が異なります。</p> <ul> <li>分割統治は、元の問題を互いに独立した複数の部分問題へ再帰的に分割し、最小の部分問題まで進みます。その後、戻りながら解を統合し、元の問題の解を得ます。</li> <li>動的計画法も問題を再帰的に分解します。分割統治との主な違いは、部分問題が互いに依存し、分解の過程で多くの重複した部分問題が現れることです。</li> <li>バックトラッキングは試行と後戻りを繰り返してすべての可能な解を列挙し、枝刈りで不要な探索分岐を避けます。元の問題の解は一連の判断で構成され、各判断の直前までの部分列を一つの部分問題と捉えられます。</li> </ul> <p>実際、動的計画法は最適化問題を解くためによく使われます。そうした問題は部分問題の重複に加え、最適部分構造と、後続の判断が過去の経路に依存しない性質という二つの重要な特徴を持ちます。</p>","path":["第 14 章   動的計画法","14.2   動的計画法の問題の特徴"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/#1421","level":2,"title":"14.2.1   最適部分構造","text":"<p>階段を上る問題を少し変えて、最適部分構造を説明しやすくしてみましょう。</p> <p>最小コストで階段を上る</p> <p>階段があり、1 回で \\(1\\) 段または \\(2\\) 段上れます。各段には非負の整数が書かれており、その段で支払うコストを表します。非負整数の配列 \\(cost\\) が与えられ、\\(cost[i]\\) は \\(i\\) 段目でのコスト、\\(cost[0]\\) は地面(開始点)に対応します。一番上まで到達するための最小コストを求めてください。</p> <p>図 14-6 のように、\\(1\\)、\\(2\\)、\\(3\\) 段目のコストがそれぞれ \\(1\\)、\\(10\\)、\\(1\\) なら、地面から \\(3\\) 段目まで上る最小コストは \\(2\\) です。</p> <p></p> <p> 図 14-6   3 段目まで上る最小コスト </p> <p>\\(i\\) 段目までの累積コストを \\(dp[i]\\) とします。\\(i\\) 段目には \\(i - 1\\) 段目か \\(i - 2\\) 段目からしか来られないため、\\(dp[i]\\) は \\(dp[i - 1] + cost[i]\\) または \\(dp[i - 2] + cost[i]\\) です。コストをできるだけ小さくするには、二つのうち小さい方を選びます。</p> \\[ dp[i] = \\min(dp[i-1], dp[i-2]) + cost[i] \\] <p>ここから、最適部分構造の意味がわかります。元の問題の最適解は、部分問題の最適解から組み立てられるということです。</p> <p>この問題には明らかに最適部分構造があります。二つの部分問題の最適解 \\(dp[i-1]\\) と \\(dp[i-2]\\) からよい方を選び、それを使って元の問題 \\(dp[i]\\) の最適解を組み立てるからです。</p> <p>では、前の節の階段の問題にも最適部分構造はあるのでしょうか。目的は方法の数を求めることで、一見すると数え上げの問題です。しかし「方法の数の最大値を求める」と言い換えると、問題自体は同じなのに、最適部分構造が見えてきます。\\(n\\) 段目までの方法の最大数は、\\(n-1\\) 段目と \\(n-2\\) 段目までの方法の最大数の和です。このように、最適部分構造は柔軟に解釈でき、問題によって意味合いが異なります。</p> <p>状態遷移式と、初期状態 \\(dp[1] = cost[1]\\)、\\(dp[2] = cost[2]\\) から、動的計画法のコードが得られます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_cost_climbing_stairs_dp.py<pre><code>def min_cost_climbing_stairs_dp(cost: list[int]) -&gt; int:\n \"\"\"階段を上る最小コスト:動的計画法\"\"\"\n n = len(cost) - 1\n if n == 1 or n == 2:\n return cost[n]\n # 部分問題の解を格納する dp テーブルを初期化する\n dp = [0] * (n + 1)\n # 初期状態:最小の部分問題の解を設定する\n dp[1], dp[2] = cost[1], cost[2]\n # 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i in range(3, n + 1):\n dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n return dp[n]\n</code></pre> min_cost_climbing_stairs_dp.cpp<pre><code>/* 階段を上る最小コスト:動的計画法 */\nint minCostClimbingStairsDP(vector&lt;int&gt; &amp;cost) {\n int n = cost.size() - 1;\n if (n == 1 || n == 2)\n return cost[n];\n // 部分問題の解を格納する dp テーブルを初期化する\n vector&lt;int&gt; dp(n + 1);\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = cost[1];\n dp[2] = cost[2];\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];\n }\n return dp[n];\n}\n</code></pre> min_cost_climbing_stairs_dp.java<pre><code>/* 階段を上る最小コスト:動的計画法 */\nint minCostClimbingStairsDP(int[] cost) {\n int n = cost.length - 1;\n if (n == 1 || n == 2)\n return cost[n];\n // 部分問題の解を格納する dp テーブルを初期化する\n int[] dp = new int[n + 1];\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = cost[1];\n dp[2] = cost[2];\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n }\n return dp[n];\n}\n</code></pre> min_cost_climbing_stairs_dp.cs<pre><code>/* 階段を上る最小コスト:動的計画法 */\nint MinCostClimbingStairsDP(int[] cost) {\n int n = cost.Length - 1;\n if (n == 1 || n == 2)\n return cost[n];\n // 部分問題の解を格納する dp テーブルを初期化する\n int[] dp = new int[n + 1];\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = cost[1];\n dp[2] = cost[2];\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i];\n }\n return dp[n];\n}\n</code></pre> min_cost_climbing_stairs_dp.go<pre><code>/* 階段を上る最小コスト:動的計画法 */\nfunc minCostClimbingStairsDP(cost []int) int {\n n := len(cost) - 1\n if n == 1 || n == 2 {\n return cost[n]\n }\n min := func(a, b int) int {\n if a &lt; b {\n return a\n }\n return b\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n dp := make([]int, n+1)\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = cost[1]\n dp[2] = cost[2]\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i := 3; i &lt;= n; i++ {\n dp[i] = min(dp[i-1], dp[i-2]) + cost[i]\n }\n return dp[n]\n}\n</code></pre> min_cost_climbing_stairs_dp.swift<pre><code>/* 階段を上る最小コスト:動的計画法 */\nfunc minCostClimbingStairsDP(cost: [Int]) -&gt; Int {\n let n = cost.count - 1\n if n == 1 || n == 2 {\n return cost[n]\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n var dp = Array(repeating: 0, count: n + 1)\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = cost[1]\n dp[2] = cost[2]\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i in 3 ... n {\n dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n }\n return dp[n]\n}\n</code></pre> min_cost_climbing_stairs_dp.js<pre><code>/* 階段を上る最小コスト:動的計画法 */\nfunction minCostClimbingStairsDP(cost) {\n const n = cost.length - 1;\n if (n === 1 || n === 2) {\n return cost[n];\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n const dp = new Array(n + 1);\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = cost[1];\n dp[2] = cost[2];\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (let i = 3; i &lt;= n; i++) {\n dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n }\n return dp[n];\n}\n</code></pre> min_cost_climbing_stairs_dp.ts<pre><code>/* 階段を上る最小コスト:動的計画法 */\nfunction minCostClimbingStairsDP(cost: Array&lt;number&gt;): number {\n const n = cost.length - 1;\n if (n === 1 || n === 2) {\n return cost[n];\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n const dp = new Array(n + 1);\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = cost[1];\n dp[2] = cost[2];\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (let i = 3; i &lt;= n; i++) {\n dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];\n }\n return dp[n];\n}\n</code></pre> min_cost_climbing_stairs_dp.dart<pre><code>/* 階段を上る最小コスト:動的計画法 */\nint minCostClimbingStairsDP(List&lt;int&gt; cost) {\n int n = cost.length - 1;\n if (n == 1 || n == 2) return cost[n];\n // 部分問題の解を格納する dp テーブルを初期化する\n List&lt;int&gt; dp = List.filled(n + 1, 0);\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = cost[1];\n dp[2] = cost[2];\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];\n }\n return dp[n];\n}\n</code></pre> min_cost_climbing_stairs_dp.rs<pre><code>/* 階段を上る最小コスト:動的計画法 */\nfn min_cost_climbing_stairs_dp(cost: &amp;[i32]) -&gt; i32 {\n let n = cost.len() - 1;\n if n == 1 || n == 2 {\n return cost[n];\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n let mut dp = vec![-1; n + 1];\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = cost[1];\n dp[2] = cost[2];\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i in 3..=n {\n dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i];\n }\n dp[n]\n}\n</code></pre> min_cost_climbing_stairs_dp.c<pre><code>/* 階段を上る最小コスト:動的計画法 */\nint minCostClimbingStairsDP(int cost[], int costSize) {\n int n = costSize - 1;\n if (n == 1 || n == 2)\n return cost[n];\n // 部分問題の解を格納する dp テーブルを初期化する\n int *dp = calloc(n + 1, sizeof(int));\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = cost[1];\n dp[2] = cost[2];\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i];\n }\n int res = dp[n];\n // メモリを解放する\n free(dp);\n return res;\n}\n</code></pre> min_cost_climbing_stairs_dp.kt<pre><code>/* 階段を上る最小コスト:動的計画法 */\nfun minCostClimbingStairsDP(cost: IntArray): Int {\n val n = cost.size - 1\n if (n == 1 || n == 2) return cost[n]\n // 部分問題の解を格納する dp テーブルを初期化する\n val dp = IntArray(n + 1)\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = cost[1]\n dp[2] = cost[2]\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (i in 3..n) {\n dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]\n }\n return dp[n]\n}\n</code></pre> min_cost_climbing_stairs_dp.rb<pre><code>### 階段を上る最小コスト:動的計画法 ###\ndef min_cost_climbing_stairs_dp(cost)\n n = cost.length - 1\n return cost[n] if n == 1 || n == 2\n # 部分問題の解を格納する dp テーブルを初期化する\n dp = Array.new(n + 1, 0)\n # 初期状態:最小の部分問題の解を設定する\n dp[1], dp[2] = cost[1], cost[2]\n # 状態遷移:小さな部分問題から順に大きな部分問題を解く\n (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }\n dp[n]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-7 に、上のコードによる動的計画法の流れを示します。</p> <p></p> <p> 図 14-7   最小コストで階段を上る動的計画法の流れ </p> <p>この問題も、一次元を零次元に圧縮する空間最適化により、空間計算量を \\(O(n)\\) から \\(O(1)\\) に下げられます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_cost_climbing_stairs_dp.py<pre><code>def min_cost_climbing_stairs_dp_comp(cost: list[int]) -&gt; int:\n \"\"\"階段を上る最小コスト:空間を最適化した動的計画法\"\"\"\n n = len(cost) - 1\n if n == 1 or n == 2:\n return cost[n]\n a, b = cost[1], cost[2]\n for i in range(3, n + 1):\n a, b = b, min(a, b) + cost[i]\n return b\n</code></pre> min_cost_climbing_stairs_dp.cpp<pre><code>/* 階段を上る最小コスト:空間を最適化した動的計画法 */\nint minCostClimbingStairsDPComp(vector&lt;int&gt; &amp;cost) {\n int n = cost.size() - 1;\n if (n == 1 || n == 2)\n return cost[n];\n int a = cost[1], b = cost[2];\n for (int i = 3; i &lt;= n; i++) {\n int tmp = b;\n b = min(a, tmp) + cost[i];\n a = tmp;\n }\n return b;\n}\n</code></pre> min_cost_climbing_stairs_dp.java<pre><code>/* 階段を上る最小コスト:空間を最適化した動的計画法 */\nint minCostClimbingStairsDPComp(int[] cost) {\n int n = cost.length - 1;\n if (n == 1 || n == 2)\n return cost[n];\n int a = cost[1], b = cost[2];\n for (int i = 3; i &lt;= n; i++) {\n int tmp = b;\n b = Math.min(a, tmp) + cost[i];\n a = tmp;\n }\n return b;\n}\n</code></pre> min_cost_climbing_stairs_dp.cs<pre><code>/* 階段を上る最小コスト:空間を最適化した動的計画法 */\nint MinCostClimbingStairsDPComp(int[] cost) {\n int n = cost.Length - 1;\n if (n == 1 || n == 2)\n return cost[n];\n int a = cost[1], b = cost[2];\n for (int i = 3; i &lt;= n; i++) {\n int tmp = b;\n b = Math.Min(a, tmp) + cost[i];\n a = tmp;\n }\n return b;\n}\n</code></pre> min_cost_climbing_stairs_dp.go<pre><code>/* 階段を上る最小コスト:空間を最適化した動的計画法 */\nfunc minCostClimbingStairsDPComp(cost []int) int {\n n := len(cost) - 1\n if n == 1 || n == 2 {\n return cost[n]\n }\n min := func(a, b int) int {\n if a &lt; b {\n return a\n }\n return b\n }\n // 初期状態:最小の部分問題の解を設定する\n a, b := cost[1], cost[2]\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i := 3; i &lt;= n; i++ {\n tmp := b\n b = min(a, tmp) + cost[i]\n a = tmp\n }\n return b\n}\n</code></pre> min_cost_climbing_stairs_dp.swift<pre><code>/* 階段を上る最小コスト:空間を最適化した動的計画法 */\nfunc minCostClimbingStairsDPComp(cost: [Int]) -&gt; Int {\n let n = cost.count - 1\n if n == 1 || n == 2 {\n return cost[n]\n }\n var (a, b) = (cost[1], cost[2])\n for i in 3 ... n {\n (a, b) = (b, min(a, b) + cost[i])\n }\n return b\n}\n</code></pre> min_cost_climbing_stairs_dp.js<pre><code>/* 階段を上る最小コスト:空間を最適化した動的計画法 */\nfunction minCostClimbingStairsDPComp(cost) {\n const n = cost.length - 1;\n if (n === 1 || n === 2) {\n return cost[n];\n }\n let a = cost[1],\n b = cost[2];\n for (let i = 3; i &lt;= n; i++) {\n const tmp = b;\n b = Math.min(a, tmp) + cost[i];\n a = tmp;\n }\n return b;\n}\n</code></pre> min_cost_climbing_stairs_dp.ts<pre><code>/* 階段を上る最小コスト:空間を最適化した動的計画法 */\nfunction minCostClimbingStairsDPComp(cost: Array&lt;number&gt;): number {\n const n = cost.length - 1;\n if (n === 1 || n === 2) {\n return cost[n];\n }\n let a = cost[1],\n b = cost[2];\n for (let i = 3; i &lt;= n; i++) {\n const tmp = b;\n b = Math.min(a, tmp) + cost[i];\n a = tmp;\n }\n return b;\n}\n</code></pre> min_cost_climbing_stairs_dp.dart<pre><code>/* 階段を上る最小コスト:空間を最適化した動的計画法 */\nint minCostClimbingStairsDPComp(List&lt;int&gt; cost) {\n int n = cost.length - 1;\n if (n == 1 || n == 2) return cost[n];\n int a = cost[1], b = cost[2];\n for (int i = 3; i &lt;= n; i++) {\n int tmp = b;\n b = min(a, tmp) + cost[i];\n a = tmp;\n }\n return b;\n}\n</code></pre> min_cost_climbing_stairs_dp.rs<pre><code>/* 階段を上る最小コスト:空間を最適化した動的計画法 */\nfn min_cost_climbing_stairs_dp_comp(cost: &amp;[i32]) -&gt; i32 {\n let n = cost.len() - 1;\n if n == 1 || n == 2 {\n return cost[n];\n };\n let (mut a, mut b) = (cost[1], cost[2]);\n for i in 3..=n {\n let tmp = b;\n b = cmp::min(a, tmp) + cost[i];\n a = tmp;\n }\n b\n}\n</code></pre> min_cost_climbing_stairs_dp.c<pre><code>/* 階段を上る最小コスト:空間を最適化した動的計画法 */\nint minCostClimbingStairsDPComp(int cost[], int costSize) {\n int n = costSize - 1;\n if (n == 1 || n == 2)\n return cost[n];\n int a = cost[1], b = cost[2];\n for (int i = 3; i &lt;= n; i++) {\n int tmp = b;\n b = myMin(a, tmp) + cost[i];\n a = tmp;\n }\n return b;\n}\n</code></pre> min_cost_climbing_stairs_dp.kt<pre><code>/* 階段を上る最小コスト:空間を最適化した動的計画法 */\nfun minCostClimbingStairsDPComp(cost: IntArray): Int {\n val n = cost.size - 1\n if (n == 1 || n == 2) return cost[n]\n var a = cost[1]\n var b = cost[2]\n for (i in 3..n) {\n val tmp = b\n b = min(a, tmp) + cost[i]\n a = tmp\n }\n return b\n}\n</code></pre> min_cost_climbing_stairs_dp.rb<pre><code>### 階段を上る最小コスト:動的計画法 ###\ndef min_cost_climbing_stairs_dp(cost)\n n = cost.length - 1\n return cost[n] if n == 1 || n == 2\n # 部分問題の解を格納する dp テーブルを初期化する\n dp = Array.new(n + 1, 0)\n # 初期状態:最小の部分問題の解を設定する\n dp[1], dp[2] = cost[1], cost[2]\n # 状態遷移:小さな部分問題から順に大きな部分問題を解く\n (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] }\n dp[n]\nend\n\n# 階段を上る最小コスト:空間を最適化した動的計画法\ndef min_cost_climbing_stairs_dp_comp(cost)\n n = cost.length - 1\n return cost[n] if n == 1 || n == 2\n a, b = cost[1], cost[2]\n (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] }\n b\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 14 章   動的計画法","14.2   動的計画法の問題の特徴"],"tags":[]},{"location":"chapter_dynamic_programming/dp_problem_features/#1422","level":2,"title":"14.2.2   後続の判断が過去の経路に依存しない性質","text":"<p>動的計画法が問題を効率よく解くうえで重要なのが、後続の判断が過去の経路に依存しない性質です。これは、ある状態が決まれば、その後の展開は現在の状態だけで決まり、そこまでに通ったすべての状態には依存しないことを意味します。</p> <p>階段の問題では、状態 \\(i\\) から状態 \\(i+1\\) または \\(i+2\\) に進めます。これは、それぞれ \\(1\\) 段または \\(2\\) 段上る選択です。どちらを選ぶときも、\\(i\\) より前の状態を考慮する必要はなく、過去の状態は \\(i\\) から先の展開に影響しません。</p> <p>しかし、階段の問題に制約を一つ加えると、事情が変わります。</p> <p>制約付きの階段上り</p> <p>\\(n\\) 段の階段があります。1 回で \\(1\\) 段または \\(2\\) 段上れますが、\\(1\\) 段ずつ上ることを 2 回連続では行えません。一番上まで上る方法は何通りありますか。</p> <p>図 14-8 のように、\\(3\\) 段目まで上る有効な方法は \\(2\\) 通りだけになります。\\(1\\) 段ずつ 3 回続けて上る方法は制約を満たさないため、除外されます。</p> <p></p> <p> 図 14-8   制約付きで 3 段目まで上る方法の数 </p> <p>この問題では、前回 \\(1\\) 段上ったなら、次は必ず \\(2\\) 段上る必要があります。つまり、次の選択は現在の状態(現在の段数)だけでは決まらず、一つ前の状態(前回いた段数)にも依存します。</p> <p>この状態の定義では、過去の経路に依存しない性質が成り立たず、状態遷移式 \\(dp[i] = dp[i-1] + dp[i-2]\\) も使えません。\\(dp[i-1]\\) は今回 \\(1\\) 段上る場合に対応しますが、その中には「前回も \\(1\\) 段上った」方法が多数含まれます。制約を満たすためには、\\(dp[i-1]\\) をそのまま \\(dp[i]\\) に加えられません。</p> <p>そこで状態の定義を拡張し、状態 \\([i, j]\\) を、現在 \\(i\\) 段目にいて、前回 \\(j\\) 段上った状態とします。ここで \\(j \\in \\{1, 2\\}\\) です。この定義なら、前回上ったのが \\(1\\) 段か \\(2\\) 段かを区別でき、現在の状態がどこから来たかを判断できます。</p> <ul> <li>前回 \\(1\\) 段上ったなら、その前は \\(2\\) 段しか選べません。したがって、\\(dp[i, 1]\\) には \\(dp[i-1, 2]\\) からだけ遷移できます。</li> <li>前回 \\(2\\) 段上ったなら、その前は \\(1\\) 段でも \\(2\\) 段でもかまいません。したがって、\\(dp[i, 2]\\) には \\(dp[i-2, 1]\\) または \\(dp[i-2, 2]\\) から遷移できます。</li> </ul> <p>図 14-9 のように、\\(dp[i, j]\\) を状態 \\([i, j]\\) に対応する方法の数とすると、状態遷移式は次のようになります。</p> \\[ \\begin{cases} dp[i, 1] = dp[i-1, 2] \\\\ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] \\end{cases} \\] <p></p> <p> 図 14-9   制約を考慮した漸化式 </p> <p>最後に \\(dp[n, 1] + dp[n, 2]\\) を返します。この和が、\\(n\\) 段目まで上る方法の総数です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_constraint_dp.py<pre><code>def climbing_stairs_constraint_dp(n: int) -&gt; int:\n \"\"\"制約付きの階段の上り方:動的計画法\"\"\"\n if n == 1 or n == 2:\n return 1\n # 部分問題の解を格納する dp テーブルを初期化する\n dp = [[0] * 3 for _ in range(n + 1)]\n # 初期状態:最小の部分問題の解を設定する\n dp[1][1], dp[1][2] = 1, 0\n dp[2][1], dp[2][2] = 0, 1\n # 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i in range(3, n + 1):\n dp[i][1] = dp[i - 1][2]\n dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n return dp[n][1] + dp[n][2]\n</code></pre> climbing_stairs_constraint_dp.cpp<pre><code>/* 制約付きの階段の上り方:動的計画法 */\nint climbingStairsConstraintDP(int n) {\n if (n == 1 || n == 2) {\n return 1;\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n vector&lt;vector&lt;int&gt;&gt; dp(n + 1, vector&lt;int&gt;(3, 0));\n // 初期状態:最小の部分問題の解を設定する\n dp[1][1] = 1;\n dp[1][2] = 0;\n dp[2][1] = 0;\n dp[2][2] = 1;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i][1] = dp[i - 1][2];\n dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n }\n return dp[n][1] + dp[n][2];\n}\n</code></pre> climbing_stairs_constraint_dp.java<pre><code>/* 制約付きの階段の上り方:動的計画法 */\nint climbingStairsConstraintDP(int n) {\n if (n == 1 || n == 2) {\n return 1;\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n int[][] dp = new int[n + 1][3];\n // 初期状態:最小の部分問題の解を設定する\n dp[1][1] = 1;\n dp[1][2] = 0;\n dp[2][1] = 0;\n dp[2][2] = 1;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i][1] = dp[i - 1][2];\n dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n }\n return dp[n][1] + dp[n][2];\n}\n</code></pre> climbing_stairs_constraint_dp.cs<pre><code>/* 制約付きの階段の上り方:動的計画法 */\nint ClimbingStairsConstraintDP(int n) {\n if (n == 1 || n == 2) {\n return 1;\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n int[,] dp = new int[n + 1, 3];\n // 初期状態:最小の部分問題の解を設定する\n dp[1, 1] = 1;\n dp[1, 2] = 0;\n dp[2, 1] = 0;\n dp[2, 2] = 1;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i, 1] = dp[i - 1, 2];\n dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2];\n }\n return dp[n, 1] + dp[n, 2];\n}\n</code></pre> climbing_stairs_constraint_dp.go<pre><code>/* 制約付きの階段の上り方:動的計画法 */\nfunc climbingStairsConstraintDP(n int) int {\n if n == 1 || n == 2 {\n return 1\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n dp := make([][3]int, n+1)\n // 初期状態:最小の部分問題の解を設定する\n dp[1][1] = 1\n dp[1][2] = 0\n dp[2][1] = 0\n dp[2][2] = 1\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i := 3; i &lt;= n; i++ {\n dp[i][1] = dp[i-1][2]\n dp[i][2] = dp[i-2][1] + dp[i-2][2]\n }\n return dp[n][1] + dp[n][2]\n}\n</code></pre> climbing_stairs_constraint_dp.swift<pre><code>/* 制約付きの階段の上り方:動的計画法 */\nfunc climbingStairsConstraintDP(n: Int) -&gt; Int {\n if n == 1 || n == 2 {\n return 1\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1)\n // 初期状態:最小の部分問題の解を設定する\n dp[1][1] = 1\n dp[1][2] = 0\n dp[2][1] = 0\n dp[2][2] = 1\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i in 3 ... n {\n dp[i][1] = dp[i - 1][2]\n dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n }\n return dp[n][1] + dp[n][2]\n}\n</code></pre> climbing_stairs_constraint_dp.js<pre><code>/* 制約付きの階段の上り方:動的計画法 */\nfunction climbingStairsConstraintDP(n) {\n if (n === 1 || n === 2) {\n return 1;\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n const dp = Array.from(new Array(n + 1), () =&gt; new Array(3));\n // 初期状態:最小の部分問題の解を設定する\n dp[1][1] = 1;\n dp[1][2] = 0;\n dp[2][1] = 0;\n dp[2][2] = 1;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (let i = 3; i &lt;= n; i++) {\n dp[i][1] = dp[i - 1][2];\n dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n }\n return dp[n][1] + dp[n][2];\n}\n</code></pre> climbing_stairs_constraint_dp.ts<pre><code>/* 制約付きの階段の上り方:動的計画法 */\nfunction climbingStairsConstraintDP(n: number): number {\n if (n === 1 || n === 2) {\n return 1;\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n const dp = Array.from({ length: n + 1 }, () =&gt; new Array(3));\n // 初期状態:最小の部分問題の解を設定する\n dp[1][1] = 1;\n dp[1][2] = 0;\n dp[2][1] = 0;\n dp[2][2] = 1;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (let i = 3; i &lt;= n; i++) {\n dp[i][1] = dp[i - 1][2];\n dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n }\n return dp[n][1] + dp[n][2];\n}\n</code></pre> climbing_stairs_constraint_dp.dart<pre><code>/* 制約付きの階段の上り方:動的計画法 */\nint climbingStairsConstraintDP(int n) {\n if (n == 1 || n == 2) {\n return 1;\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n List&lt;List&lt;int&gt;&gt; dp = List.generate(n + 1, (index) =&gt; List.filled(3, 0));\n // 初期状態:最小の部分問題の解を設定する\n dp[1][1] = 1;\n dp[1][2] = 0;\n dp[2][1] = 0;\n dp[2][2] = 1;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i][1] = dp[i - 1][2];\n dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n }\n return dp[n][1] + dp[n][2];\n}\n</code></pre> climbing_stairs_constraint_dp.rs<pre><code>/* 制約付きの階段の上り方:動的計画法 */\nfn climbing_stairs_constraint_dp(n: usize) -&gt; i32 {\n if n == 1 || n == 2 {\n return 1;\n };\n // 部分問題の解を格納する dp テーブルを初期化する\n let mut dp = vec![vec![-1; 3]; n + 1];\n // 初期状態:最小の部分問題の解を設定する\n dp[1][1] = 1;\n dp[1][2] = 0;\n dp[2][1] = 0;\n dp[2][2] = 1;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i in 3..=n {\n dp[i][1] = dp[i - 1][2];\n dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n }\n dp[n][1] + dp[n][2]\n}\n</code></pre> climbing_stairs_constraint_dp.c<pre><code>/* 制約付きの階段の上り方:動的計画法 */\nint climbingStairsConstraintDP(int n) {\n if (n == 1 || n == 2) {\n return 1;\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n int **dp = malloc((n + 1) * sizeof(int *));\n for (int i = 0; i &lt;= n; i++) {\n dp[i] = calloc(3, sizeof(int));\n }\n // 初期状態:最小の部分問題の解を設定する\n dp[1][1] = 1;\n dp[1][2] = 0;\n dp[2][1] = 0;\n dp[2][2] = 1;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i][1] = dp[i - 1][2];\n dp[i][2] = dp[i - 2][1] + dp[i - 2][2];\n }\n int res = dp[n][1] + dp[n][2];\n // メモリを解放する\n for (int i = 0; i &lt;= n; i++) {\n free(dp[i]);\n }\n free(dp);\n return res;\n}\n</code></pre> climbing_stairs_constraint_dp.kt<pre><code>/* 制約付きの階段の上り方:動的計画法 */\nfun climbingStairsConstraintDP(n: Int): Int {\n if (n == 1 || n == 2) {\n return 1\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n val dp = Array(n + 1) { IntArray(3) }\n // 初期状態:最小の部分問題の解を設定する\n dp[1][1] = 1\n dp[1][2] = 0\n dp[2][1] = 0\n dp[2][2] = 1\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (i in 3..n) {\n dp[i][1] = dp[i - 1][2]\n dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n }\n return dp[n][1] + dp[n][2]\n}\n</code></pre> climbing_stairs_constraint_dp.rb<pre><code>### 制約付きの階段の上り方:動的計画法 ###\ndef climbing_stairs_constraint_dp(n)\n return 1 if n == 1 || n == 2\n\n # 部分問題の解を格納する dp テーブルを初期化する\n dp = Array.new(n + 1) { Array.new(3, 0) }\n # 初期状態:最小の部分問題の解を設定する\n dp[1][1], dp[1][2] = 1, 0\n dp[2][1], dp[2][2] = 0, 1\n # 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i in 3...(n + 1)\n dp[i][1] = dp[i - 1][2]\n dp[i][2] = dp[i - 2][1] + dp[i - 2][2]\n end\n\n dp[n][1] + dp[n][2]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>この例では、一つ前の状態を追加で考慮するだけなので、状態を拡張すれば、再び過去の経路に依存しない形にできます。しかし、「過去の経路への依存」が非常に強い問題もあります。</p> <p>階段を上ると障害物ができる問題</p> <p>\\(n\\) 段の階段があり、1 回で \\(1\\) 段または \\(2\\) 段上れます。\\(i\\) 段目に到達すると、システムが自動的に \\(2i\\) 段目に障害物を置き、それ以降は \\(2i\\) 段目に上れなくなるものとします。たとえば、最初の 2 回で \\(2\\) 段目、\\(3\\) 段目に上ったら、その後は \\(4\\) 段目、\\(6\\) 段目には上れません。一番上まで上る方法は何通りありますか。</p> <p>この問題では、毎回の移動が上の段に障害物を置き、その後の移動に影響するため、次の移動は過去のすべての状態に依存します。この種の問題は、動的計画法では解くのが難しいことが多いです。</p> <p>実際、多くの複雑な組合せ最適化問題(巡回セールスマン問題など)は、過去の経路に依存しない性質を満たしません。そのような問題には、ヒューリスティック探索、遺伝的アルゴリズム、強化学習などの別の方法を選び、限られた時間で利用可能な局所最適解を得ることが一般的です。</p>","path":["第 14 章   動的計画法","14.2   動的計画法の問題の特徴"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/","level":1,"title":"14.3   動的計画法の解き方","text":"<p>前の 2 節では、動的計画法の問題の主な特徴を説明しました。ここからは、より実践的な二つの疑問を考えていきます。</p> <ol> <li>ある問題が動的計画法に適しているかどうかは、どのように判断するのでしょうか。</li> <li>動的計画法の問題は何から手を付け、どのような手順で解けばよいのでしょうか。</li> </ol>","path":["第 14 章   動的計画法","14.3   動的計画法の解き方"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1431","level":2,"title":"14.3.1   問題の見極め","text":"<p>一般に、部分問題の重複と最適部分構造があり、後続の判断が過去の経路に依存しない問題は、動的計画法に適しています。ただし、問題文からこれらの性質を直接見抜くのは難しいため、通常は条件を緩め、まずバックトラッキング(総当たり)で解けそうかを考えます。</p> <p>バックトラッキングに適した問題は、通常「決定木モデル」で表せます。木構造で記述でき、各ノードが判断を、各経路が一連の判断を表す問題です。</p> <p>言い換えると、明確な選択があり、その選択を順に行って解が得られるなら、決定木モデルに当てはまり、通常はバックトラッキングで解けます。</p> <p>これに加え、動的計画法の問題らしいと判断する「プラス材料」があります。</p> <ul> <li>最大・最小、最多・最少などの最適化を求めています。</li> <li>状態をリスト、多次元行列、木などで表せ、ある状態とその周囲の状態の間に漸化式があります。</li> </ul> <p>反対に、次のような「マイナス材料」もあります。</p> <ul> <li>最適解ではなく、可能な解をすべて求めることが目標です。</li> <li>順列や組合せの特徴が明らかで、具体的な複数の方法を返す必要があります。</li> </ul> <p>決定木モデルに当てはまり、「プラス材料」もはっきりしているなら、動的計画法の問題と仮定して取り組み、解く過程で確かめていけます。</p>","path":["第 14 章   動的計画法","14.3   動的計画法の解き方"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1432","level":2,"title":"14.3.2   問題を解く手順","text":"<p>動的計画法の手順は問題の性質や難しさによって異なりますが、通常は、選択の記述、状態の定義、\\(dp\\) テーブルの作成、状態遷移式の導出、境界条件の決定などの順で進めます。</p> <p>手順を具体的に示すため、典型的な「経路和の最小化」問題を使います。</p> <p>問題</p> <p>\\(n \\times m\\) の二次元グリッド <code>grid</code> が与えられます。各マスには、そのマスのコストを表す非負の整数があります。ロボットは左上のマスから出発し、毎回、下または右に 1 マスだけ進んで、右下のマスを目指します。左上から右下までの経路和の最小値を返してください。</p> <p>図 14-10 のグリッドでは、経路和の最小値は \\(13\\) です。</p> <p></p> <p> 図 14-10   経路和の最小化問題のデータ例 </p> <p>第 1 段階:各回の選択を考えて状態を定義し、\\(dp\\) テーブルを作る</p> <p>各回の選択は、現在のマスから下または右に 1 マス進むことです。現在の行と列のインデックスを \\([i, j]\\) とすると、移動後は \\([i+1, j]\\) または \\([i, j+1]\\) になります。したがって、状態は行と列の二つの変数を含む \\([i, j]\\) とします。</p> <p>状態 \\([i, j]\\) に対応する部分問題は、開始点 \\([0, 0]\\) から \\([i, j]\\) までの経路和の最小値を求めることで、その解を \\(dp[i, j]\\) とします。</p> <p>これで、図 14-11 の二次元の \\(dp\\) 行列が得られます。大きさは入力グリッド \\(grid\\) と同じです。</p> <p></p> <p> 図 14-11   状態の定義と dp テーブル </p> <p>注記</p> <p>動的計画法やバックトラッキングの過程は、一連の判断として表せます。状態はすべての判断変数で構成し、問題を解く過程の進み具合を表す変数をすべて含めます。次の状態を導くのに十分な情報を持たせる必要があります。</p> <p>各状態は一つの部分問題に対応し、\\(dp\\) テーブルにすべての部分問題の解を保存します。状態の各独立変数が、\\(dp\\) テーブルの一つの次元になります。本質的には、\\(dp\\) テーブルは状態から部分問題の解への対応表です。</p> <p>第 2 段階:最適部分構造を見つけ、状態遷移式を導く</p> <p>状態 \\([i, j]\\) には、上のマス \\([i-1, j]\\) または左のマス \\([i, j-1]\\) からしか到達できません。したがって、最適部分構造は、\\([i, j]\\) までの経路和の最小値が、\\([i, j-1]\\) までの最小値と \\([i-1, j]\\) までの最小値のうち、小さい方で決まることです。</p> <p>この分析から、図 14-12 の状態遷移式を導けます。</p> \\[ dp[i, j] = \\min(dp[i-1, j], dp[i, j-1]) + grid[i, j] \\] <p></p> <p> 図 14-12   最適部分構造と状態遷移式 </p> <p>注記</p> <p>定義した \\(dp\\) テーブルをもとに、元の問題と部分問題の関係を考えます。部分問題の最適解から元の問題の最適解を組み立てる方法、すなわち最適部分構造を見つけます。</p> <p>最適部分構造が見つかれば、それを使って状態遷移式を組み立てられます。</p> <p>第 3 段階:境界条件と状態遷移の順序を決める</p> <p>この問題では、先頭行の状態には左からしか到達できず、先頭列の状態には上からしか到達できません。そのため、先頭行 \\(i = 0\\) と先頭列 \\(j = 0\\) が境界条件になります。</p> <p>図 14-13 のように、各マスは左と上のマスから遷移するため、ループで行列を走査します。外側のループで行を、内側のループで列を走査します。</p> <p></p> <p> 図 14-13   境界条件と状態遷移の順序 </p> <p>注記</p> <p>境界条件は、動的計画法では \\(dp\\) テーブルの初期化に、探索では枝刈りに使います。</p> <p>状態遷移の順序で重要なのは、現在の問題の解を計算する時点で、依存するすべての小さな部分問題の解が正しく計算済みであることです。</p> <p>ここまでの分析から、動的計画法のコードを直接書けます。ただし、部分問題への分解は上から下への考え方なので、「全探索 \\(\\rightarrow\\) メモ化探索 \\(\\rightarrow\\) 動的計画法」の順に実装する方が、自然な思考の流れに沿っています。</p>","path":["第 14 章   動的計画法","14.3   動的計画法の解き方"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#1-1","level":3,"title":"1.   方法 1:全探索","text":"<p>状態 \\([i, j]\\) から探索を始め、より小さな状態 \\([i-1, j]\\) と \\([i, j-1]\\) へ分解していきます。再帰関数には、次の要素があります。</p> <ul> <li>再帰の引数:状態 \\([i, j]\\) です。</li> <li>戻り値:\\([0, 0]\\) から \\([i, j]\\) までの経路和の最小値 \\(dp[i, j]\\) です。</li> <li>終了条件:\\(i = 0\\) かつ \\(j = 0\\) なら、コスト \\(grid[0, 0]\\) を返します。</li> <li>枝刈り:\\(i &lt; 0\\) または \\(j &lt; 0\\) なら範囲外なので、到達不可能を表すコスト \\(+\\infty\\) を返します。</li> </ul> <p>実装は次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py<pre><code>def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -&gt; int:\n \"\"\"最小経路和:全探索\"\"\"\n # 左上のセルなら探索を終了する\n if i == 0 and j == 0:\n return grid[0][0]\n # 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if i &lt; 0 or j &lt; 0:\n return inf\n # 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n up = min_path_sum_dfs(grid, i - 1, j)\n left = min_path_sum_dfs(grid, i, j - 1)\n # 左上から (i, j) までの最小経路コストを返す\n return min(left, up) + grid[i][j]\n</code></pre> min_path_sum.cpp<pre><code>/* 最小経路和:全探索 */\nint minPathSumDFS(vector&lt;vector&lt;int&gt;&gt; &amp;grid, int i, int j) {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return INT_MAX;\n }\n // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n int up = minPathSumDFS(grid, i - 1, j);\n int left = minPathSumDFS(grid, i, j - 1);\n // 左上から (i, j) までの最小経路コストを返す\n return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;\n}\n</code></pre> min_path_sum.java<pre><code>/* 最小経路和:全探索 */\nint minPathSumDFS(int[][] grid, int i, int j) {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return Integer.MAX_VALUE;\n }\n // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n int up = minPathSumDFS(grid, i - 1, j);\n int left = minPathSumDFS(grid, i, j - 1);\n // 左上から (i, j) までの最小経路コストを返す\n return Math.min(left, up) + grid[i][j];\n}\n</code></pre> min_path_sum.cs<pre><code>/* 最小経路和:全探索 */\nint MinPathSumDFS(int[][] grid, int i, int j) {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return int.MaxValue;\n }\n // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n int up = MinPathSumDFS(grid, i - 1, j);\n int left = MinPathSumDFS(grid, i, j - 1);\n // 左上から (i, j) までの最小経路コストを返す\n return Math.Min(left, up) + grid[i][j];\n}\n</code></pre> min_path_sum.go<pre><code>/* 最小経路和:全探索 */\nfunc minPathSumDFS(grid [][]int, i, j int) int {\n // 左上のセルなら探索を終了する\n if i == 0 &amp;&amp; j == 0 {\n return grid[0][0]\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if i &lt; 0 || j &lt; 0 {\n return math.MaxInt\n }\n // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n up := minPathSumDFS(grid, i-1, j)\n left := minPathSumDFS(grid, i, j-1)\n // 左上から (i, j) までの最小経路コストを返す\n return int(math.Min(float64(left), float64(up))) + grid[i][j]\n}\n</code></pre> min_path_sum.swift<pre><code>/* 最小経路和:全探索 */\nfunc minPathSumDFS(grid: [[Int]], i: Int, j: Int) -&gt; Int {\n // 左上のセルなら探索を終了する\n if i == 0, j == 0 {\n return grid[0][0]\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if i &lt; 0 || j &lt; 0 {\n return .max\n }\n // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n let up = minPathSumDFS(grid: grid, i: i - 1, j: j)\n let left = minPathSumDFS(grid: grid, i: i, j: j - 1)\n // 左上から (i, j) までの最小経路コストを返す\n return min(left, up) + grid[i][j]\n}\n</code></pre> min_path_sum.js<pre><code>/* 最小経路和:全探索 */\nfunction minPathSumDFS(grid, i, j) {\n // 左上のセルなら探索を終了する\n if (i === 0 &amp;&amp; j === 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return Infinity;\n }\n // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n const up = minPathSumDFS(grid, i - 1, j);\n const left = minPathSumDFS(grid, i, j - 1);\n // 左上から (i, j) までの最小経路コストを返す\n return Math.min(left, up) + grid[i][j];\n}\n</code></pre> min_path_sum.ts<pre><code>/* 最小経路和:全探索 */\nfunction minPathSumDFS(\n grid: Array&lt;Array&lt;number&gt;&gt;,\n i: number,\n j: number\n): number {\n // 左上のセルなら探索を終了する\n if (i === 0 &amp;&amp; j == 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return Infinity;\n }\n // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n const up = minPathSumDFS(grid, i - 1, j);\n const left = minPathSumDFS(grid, i, j - 1);\n // 左上から (i, j) までの最小経路コストを返す\n return Math.min(left, up) + grid[i][j];\n}\n</code></pre> min_path_sum.dart<pre><code>/* 最小経路和:全探索 */\nint minPathSumDFS(List&lt;List&lt;int&gt;&gt; grid, int i, int j) {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n // Dart の int 型は固定範囲の整数であり、「無限大」を表す値は存在しない\n return BigInt.from(2).pow(31).toInt();\n }\n // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n int up = minPathSumDFS(grid, i - 1, j);\n int left = minPathSumDFS(grid, i, j - 1);\n // 左上から (i, j) までの最小経路コストを返す\n return min(left, up) + grid[i][j];\n}\n</code></pre> min_path_sum.rs<pre><code>/* 最小経路和:全探索 */\nfn min_path_sum_dfs(grid: &amp;Vec&lt;Vec&lt;i32&gt;&gt;, i: i32, j: i32) -&gt; i32 {\n // 左上のセルなら探索を終了する\n if i == 0 &amp;&amp; j == 0 {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if i &lt; 0 || j &lt; 0 {\n return i32::MAX;\n }\n // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n let up = min_path_sum_dfs(grid, i - 1, j);\n let left = min_path_sum_dfs(grid, i, j - 1);\n // 左上から (i, j) までの最小経路コストを返す\n std::cmp::min(left, up) + grid[i as usize][j as usize]\n}\n</code></pre> min_path_sum.c<pre><code>/* 最小経路和:全探索 */\nint minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return INT_MAX;\n }\n // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n int up = minPathSumDFS(grid, i - 1, j);\n int left = minPathSumDFS(grid, i, j - 1);\n // 左上から (i, j) までの最小経路コストを返す\n return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;\n}\n</code></pre> min_path_sum.kt<pre><code>/* 最小経路和:全探索 */\nfun minPathSumDFS(grid: Array&lt;IntArray&gt;, i: Int, j: Int): Int {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0]\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return Int.MAX_VALUE\n }\n // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n val up = minPathSumDFS(grid, i - 1, j)\n val left = minPathSumDFS(grid, i, j - 1)\n // 左上から (i, j) までの最小経路コストを返す\n return min(left, up) + grid[i][j]\n}\n</code></pre> min_path_sum.rb<pre><code>### 最小経路和:全探索 ###\ndef min_path_sum_dfs(grid, i, j)\n # 左上のセルなら探索を終了する\n return grid[i][j] if i == 0 &amp;&amp; j == 0\n # 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n return Float::INFINITY if i &lt; 0 || j &lt; 0\n # 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する\n up = min_path_sum_dfs(grid, i - 1, j)\n left = min_path_sum_dfs(grid, i, j - 1)\n # 左上から (i, j) までの最小経路コストを返す\n [left, up].min + grid[i][j]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-14 は、\\(dp[2, 1]\\) を根とする再帰木です。部分問題の重複が含まれており、グリッド <code>grid</code> が大きくなると、その数は急激に増えます。</p> <p>部分問題が重複する本質的な理由は、左上からあるマスに到達する経路が複数あることです。</p> <p></p> <p> 図 14-14   全探索の再帰木 </p> <p>各状態で下と右の二つの選択肢があり、左上から右下までは計 \\(m + n - 2\\) 歩なので、最悪時間計算量は \\(O(2^{m + n})\\) です。ここで \\(n\\) と \\(m\\) はそれぞれ行数と列数です。ただし、この計算ではグリッドの端付近を考慮していません。端に達すると選択肢は一つになるため、実際の経路数はこれより少なくなります。</p>","path":["第 14 章   動的計画法","14.3   動的計画法の解き方"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#2-2","level":3,"title":"2.   方法 2:メモ化探索","text":"<p>グリッド <code>grid</code> と同じ大きさの記録用リスト <code>mem</code> を導入し、各部分問題の解を保存して、重複する部分問題を枝刈りします。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py<pre><code>def min_path_sum_dfs_mem(\n grid: list[list[int]], mem: list[list[int]], i: int, j: int\n) -&gt; int:\n \"\"\"最小経路和:メモ化探索\"\"\"\n # 左上のセルなら探索を終了する\n if i == 0 and j == 0:\n return grid[0][0]\n # 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if i &lt; 0 or j &lt; 0:\n return inf\n # 記録済みなら、その値をそのまま返す\n if mem[i][j] != -1:\n return mem[i][j]\n # 左隣と上隣のセルまでの最小経路コスト\n up = min_path_sum_dfs_mem(grid, mem, i - 1, j)\n left = min_path_sum_dfs_mem(grid, mem, i, j - 1)\n # 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = min(left, up) + grid[i][j]\n return mem[i][j]\n</code></pre> min_path_sum.cpp<pre><code>/* 最小経路和:メモ化探索 */\nint minPathSumDFSMem(vector&lt;vector&lt;int&gt;&gt; &amp;grid, vector&lt;vector&lt;int&gt;&gt; &amp;mem, int i, int j) {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return INT_MAX;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][j] != -1) {\n return mem[i][j];\n }\n // 左隣と上隣のセルまでの最小経路コスト\n int up = minPathSumDFSMem(grid, mem, i - 1, j);\n int left = minPathSumDFSMem(grid, mem, i, j - 1);\n // 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;\n return mem[i][j];\n}\n</code></pre> min_path_sum.java<pre><code>/* 最小経路和:メモ化探索 */\nint minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return Integer.MAX_VALUE;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][j] != -1) {\n return mem[i][j];\n }\n // 左隣と上隣のセルまでの最小経路コスト\n int up = minPathSumDFSMem(grid, mem, i - 1, j);\n int left = minPathSumDFSMem(grid, mem, i, j - 1);\n // 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = Math.min(left, up) + grid[i][j];\n return mem[i][j];\n}\n</code></pre> min_path_sum.cs<pre><code>/* 最小経路和:メモ化探索 */\nint MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return int.MaxValue;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][j] != -1) {\n return mem[i][j];\n }\n // 左隣と上隣のセルまでの最小経路コスト\n int up = MinPathSumDFSMem(grid, mem, i - 1, j);\n int left = MinPathSumDFSMem(grid, mem, i, j - 1);\n // 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = Math.Min(left, up) + grid[i][j];\n return mem[i][j];\n}\n</code></pre> min_path_sum.go<pre><code>/* 最小経路和:メモ化探索 */\nfunc minPathSumDFSMem(grid, mem [][]int, i, j int) int {\n // 左上のセルなら探索を終了する\n if i == 0 &amp;&amp; j == 0 {\n return grid[0][0]\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if i &lt; 0 || j &lt; 0 {\n return math.MaxInt\n }\n // 記録済みなら、その値をそのまま返す\n if mem[i][j] != -1 {\n return mem[i][j]\n }\n // 左隣と上隣のセルまでの最小経路コスト\n up := minPathSumDFSMem(grid, mem, i-1, j)\n left := minPathSumDFSMem(grid, mem, i, j-1)\n // 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j]\n return mem[i][j]\n}\n</code></pre> min_path_sum.swift<pre><code>/* 最小経路和:メモ化探索 */\nfunc minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -&gt; Int {\n // 左上のセルなら探索を終了する\n if i == 0, j == 0 {\n return grid[0][0]\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if i &lt; 0 || j &lt; 0 {\n return .max\n }\n // 記録済みなら、その値をそのまま返す\n if mem[i][j] != -1 {\n return mem[i][j]\n }\n // 左隣と上隣のセルまでの最小経路コスト\n let up = minPathSumDFSMem(grid: grid, mem: &amp;mem, i: i - 1, j: j)\n let left = minPathSumDFSMem(grid: grid, mem: &amp;mem, i: i, j: j - 1)\n // 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = min(left, up) + grid[i][j]\n return mem[i][j]\n}\n</code></pre> min_path_sum.js<pre><code>/* 最小経路和:メモ化探索 */\nfunction minPathSumDFSMem(grid, mem, i, j) {\n // 左上のセルなら探索を終了する\n if (i === 0 &amp;&amp; j === 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return Infinity;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][j] !== -1) {\n return mem[i][j];\n }\n // 左隣と上隣のセルまでの最小経路コスト\n const up = minPathSumDFSMem(grid, mem, i - 1, j);\n const left = minPathSumDFSMem(grid, mem, i, j - 1);\n // 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = Math.min(left, up) + grid[i][j];\n return mem[i][j];\n}\n</code></pre> min_path_sum.ts<pre><code>/* 最小経路和:メモ化探索 */\nfunction minPathSumDFSMem(\n grid: Array&lt;Array&lt;number&gt;&gt;,\n mem: Array&lt;Array&lt;number&gt;&gt;,\n i: number,\n j: number\n): number {\n // 左上のセルなら探索を終了する\n if (i === 0 &amp;&amp; j === 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return Infinity;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][j] != -1) {\n return mem[i][j];\n }\n // 左隣と上隣のセルまでの最小経路コスト\n const up = minPathSumDFSMem(grid, mem, i - 1, j);\n const left = minPathSumDFSMem(grid, mem, i, j - 1);\n // 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = Math.min(left, up) + grid[i][j];\n return mem[i][j];\n}\n</code></pre> min_path_sum.dart<pre><code>/* 最小経路和:メモ化探索 */\nint minPathSumDFSMem(List&lt;List&lt;int&gt;&gt; grid, List&lt;List&lt;int&gt;&gt; mem, int i, int j) {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n // Dart の int 型は固定範囲の整数であり、「無限大」を表す値は存在しない\n return BigInt.from(2).pow(31).toInt();\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][j] != -1) {\n return mem[i][j];\n }\n // 左隣と上隣のセルまでの最小経路コスト\n int up = minPathSumDFSMem(grid, mem, i - 1, j);\n int left = minPathSumDFSMem(grid, mem, i, j - 1);\n // 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = min(left, up) + grid[i][j];\n return mem[i][j];\n}\n</code></pre> min_path_sum.rs<pre><code>/* 最小経路和:メモ化探索 */\nfn min_path_sum_dfs_mem(grid: &amp;Vec&lt;Vec&lt;i32&gt;&gt;, mem: &amp;mut Vec&lt;Vec&lt;i32&gt;&gt;, i: i32, j: i32) -&gt; i32 {\n // 左上のセルなら探索を終了する\n if i == 0 &amp;&amp; j == 0 {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if i &lt; 0 || j &lt; 0 {\n return i32::MAX;\n }\n // 記録済みなら、その値をそのまま返す\n if mem[i as usize][j as usize] != -1 {\n return mem[i as usize][j as usize];\n }\n // 左隣と上隣のセルまでの最小経路コスト\n let up = min_path_sum_dfs_mem(grid, mem, i - 1, j);\n let left = min_path_sum_dfs_mem(grid, mem, i, j - 1);\n // 左上から (i, j) までの最小経路コストを記録して返す\n mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize];\n mem[i as usize][j as usize]\n}\n</code></pre> min_path_sum.c<pre><code>/* 最小経路和:メモ化探索 */\nint minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0];\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return INT_MAX;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][j] != -1) {\n return mem[i][j];\n }\n // 左隣と上隣のセルまでの最小経路コスト\n int up = minPathSumDFSMem(grid, mem, i - 1, j);\n int left = minPathSumDFSMem(grid, mem, i, j - 1);\n // 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;\n return mem[i][j];\n}\n</code></pre> min_path_sum.kt<pre><code>/* 最小経路和:メモ化探索 */\nfun minPathSumDFSMem(\n grid: Array&lt;IntArray&gt;,\n mem: Array&lt;IntArray&gt;,\n i: Int,\n j: Int\n): Int {\n // 左上のセルなら探索を終了する\n if (i == 0 &amp;&amp; j == 0) {\n return grid[0][0]\n }\n // 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n if (i &lt; 0 || j &lt; 0) {\n return Int.MAX_VALUE\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][j] != -1) {\n return mem[i][j]\n }\n // 左隣と上隣のセルまでの最小経路コスト\n val up = minPathSumDFSMem(grid, mem, i - 1, j)\n val left = minPathSumDFSMem(grid, mem, i, j - 1)\n // 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = min(left, up) + grid[i][j]\n return mem[i][j]\n}\n</code></pre> min_path_sum.rb<pre><code>### 最小経路和:メモ化探索 ###\ndef min_path_sum_dfs_mem(grid, mem, i, j)\n # 左上のセルなら探索を終了する\n return grid[0][0] if i == 0 &amp;&amp; j == 0\n # 行または列のインデックスが範囲外の場合、コスト +∞ を返す\n return Float::INFINITY if i &lt; 0 || j &lt; 0\n # 記録済みなら、その値をそのまま返す\n return mem[i][j] if mem[i][j] != -1\n # 左隣と上隣のセルまでの最小経路コスト\n up = min_path_sum_dfs_mem(grid, mem, i - 1, j)\n left = min_path_sum_dfs_mem(grid, mem, i, j - 1)\n # 左上から (i, j) までの最小経路コストを記録して返す\n mem[i][j] = [left, up].min + grid[i][j]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-15 のように、メモ化すると各部分問題の解を 1 回だけ計算すれば済みます。そのため、時間計算量は状態の総数、すなわちグリッドの大きさ \\(O(nm)\\) になります。</p> <p></p> <p> 図 14-15   メモ化探索の再帰木 </p>","path":["第 14 章   動的計画法","14.3   動的計画法の解き方"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#3-3","level":3,"title":"3.   方法 3:動的計画法","text":"<p>反復による動的計画法の実装を次に示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py<pre><code>def min_path_sum_dp(grid: list[list[int]]) -&gt; int:\n \"\"\"最小経路和:動的計画法\"\"\"\n n, m = len(grid), len(grid[0])\n # dp テーブルを初期化する\n dp = [[0] * m for _ in range(n)]\n dp[0][0] = grid[0][0]\n # 状態遷移:先頭行\n for j in range(1, m):\n dp[0][j] = dp[0][j - 1] + grid[0][j]\n # 状態遷移:先頭列\n for i in range(1, n):\n dp[i][0] = dp[i - 1][0] + grid[i][0]\n # 状態遷移:残りの行と列\n for i in range(1, n):\n for j in range(1, m):\n dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n return dp[n - 1][m - 1]\n</code></pre> min_path_sum.cpp<pre><code>/* 最小経路和:動的計画法 */\nint minPathSumDP(vector&lt;vector&lt;int&gt;&gt; &amp;grid) {\n int n = grid.size(), m = grid[0].size();\n // dp テーブルを初期化する\n vector&lt;vector&lt;int&gt;&gt; dp(n, vector&lt;int&gt;(m));\n dp[0][0] = grid[0][0];\n // 状態遷移:先頭行\n for (int j = 1; j &lt; m; j++) {\n dp[0][j] = dp[0][j - 1] + grid[0][j];\n }\n // 状態遷移:先頭列\n for (int i = 1; i &lt; n; i++) {\n dp[i][0] = dp[i - 1][0] + grid[i][0];\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt; n; i++) {\n for (int j = 1; j &lt; m; j++) {\n dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n }\n }\n return dp[n - 1][m - 1];\n}\n</code></pre> min_path_sum.java<pre><code>/* 最小経路和:動的計画法 */\nint minPathSumDP(int[][] grid) {\n int n = grid.length, m = grid[0].length;\n // dp テーブルを初期化する\n int[][] dp = new int[n][m];\n dp[0][0] = grid[0][0];\n // 状態遷移:先頭行\n for (int j = 1; j &lt; m; j++) {\n dp[0][j] = dp[0][j - 1] + grid[0][j];\n }\n // 状態遷移:先頭列\n for (int i = 1; i &lt; n; i++) {\n dp[i][0] = dp[i - 1][0] + grid[i][0];\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt; n; i++) {\n for (int j = 1; j &lt; m; j++) {\n dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n }\n }\n return dp[n - 1][m - 1];\n}\n</code></pre> min_path_sum.cs<pre><code>/* 最小経路和:動的計画法 */\nint MinPathSumDP(int[][] grid) {\n int n = grid.Length, m = grid[0].Length;\n // dp テーブルを初期化する\n int[,] dp = new int[n, m];\n dp[0, 0] = grid[0][0];\n // 状態遷移:先頭行\n for (int j = 1; j &lt; m; j++) {\n dp[0, j] = dp[0, j - 1] + grid[0][j];\n }\n // 状態遷移:先頭列\n for (int i = 1; i &lt; n; i++) {\n dp[i, 0] = dp[i - 1, 0] + grid[i][0];\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt; n; i++) {\n for (int j = 1; j &lt; m; j++) {\n dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j];\n }\n }\n return dp[n - 1, m - 1];\n}\n</code></pre> min_path_sum.go<pre><code>/* 最小経路和:動的計画法 */\nfunc minPathSumDP(grid [][]int) int {\n n, m := len(grid), len(grid[0])\n // dp テーブルを初期化する\n dp := make([][]int, n)\n for i := 0; i &lt; n; i++ {\n dp[i] = make([]int, m)\n }\n dp[0][0] = grid[0][0]\n // 状態遷移:先頭行\n for j := 1; j &lt; m; j++ {\n dp[0][j] = dp[0][j-1] + grid[0][j]\n }\n // 状態遷移:先頭列\n for i := 1; i &lt; n; i++ {\n dp[i][0] = dp[i-1][0] + grid[i][0]\n }\n // 状態遷移:残りの行と列\n for i := 1; i &lt; n; i++ {\n for j := 1; j &lt; m; j++ {\n dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j]\n }\n }\n return dp[n-1][m-1]\n}\n</code></pre> min_path_sum.swift<pre><code>/* 最小経路和:動的計画法 */\nfunc minPathSumDP(grid: [[Int]]) -&gt; Int {\n let n = grid.count\n let m = grid[0].count\n // dp テーブルを初期化する\n var dp = Array(repeating: Array(repeating: 0, count: m), count: n)\n dp[0][0] = grid[0][0]\n // 状態遷移:先頭行\n for j in 1 ..&lt; m {\n dp[0][j] = dp[0][j - 1] + grid[0][j]\n }\n // 状態遷移:先頭列\n for i in 1 ..&lt; n {\n dp[i][0] = dp[i - 1][0] + grid[i][0]\n }\n // 状態遷移:残りの行と列\n for i in 1 ..&lt; n {\n for j in 1 ..&lt; m {\n dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n }\n }\n return dp[n - 1][m - 1]\n}\n</code></pre> min_path_sum.js<pre><code>/* 最小経路和:動的計画法 */\nfunction minPathSumDP(grid) {\n const n = grid.length,\n m = grid[0].length;\n // dp テーブルを初期化する\n const dp = Array.from({ length: n }, () =&gt;\n Array.from({ length: m }, () =&gt; 0)\n );\n dp[0][0] = grid[0][0];\n // 状態遷移:先頭行\n for (let j = 1; j &lt; m; j++) {\n dp[0][j] = dp[0][j - 1] + grid[0][j];\n }\n // 状態遷移:先頭列\n for (let i = 1; i &lt; n; i++) {\n dp[i][0] = dp[i - 1][0] + grid[i][0];\n }\n // 状態遷移:残りの行と列\n for (let i = 1; i &lt; n; i++) {\n for (let j = 1; j &lt; m; j++) {\n dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n }\n }\n return dp[n - 1][m - 1];\n}\n</code></pre> min_path_sum.ts<pre><code>/* 最小経路和:動的計画法 */\nfunction minPathSumDP(grid: Array&lt;Array&lt;number&gt;&gt;): number {\n const n = grid.length,\n m = grid[0].length;\n // dp テーブルを初期化する\n const dp = Array.from({ length: n }, () =&gt;\n Array.from({ length: m }, () =&gt; 0)\n );\n dp[0][0] = grid[0][0];\n // 状態遷移:先頭行\n for (let j = 1; j &lt; m; j++) {\n dp[0][j] = dp[0][j - 1] + grid[0][j];\n }\n // 状態遷移:先頭列\n for (let i = 1; i &lt; n; i++) {\n dp[i][0] = dp[i - 1][0] + grid[i][0];\n }\n // 状態遷移:残りの行と列\n for (let i = 1; i &lt; n; i++) {\n for (let j: number = 1; j &lt; m; j++) {\n dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n }\n }\n return dp[n - 1][m - 1];\n}\n</code></pre> min_path_sum.dart<pre><code>/* 最小経路和:動的計画法 */\nint minPathSumDP(List&lt;List&lt;int&gt;&gt; grid) {\n int n = grid.length, m = grid[0].length;\n // dp テーブルを初期化する\n List&lt;List&lt;int&gt;&gt; dp = List.generate(n, (i) =&gt; List.filled(m, 0));\n dp[0][0] = grid[0][0];\n // 状態遷移:先頭行\n for (int j = 1; j &lt; m; j++) {\n dp[0][j] = dp[0][j - 1] + grid[0][j];\n }\n // 状態遷移:先頭列\n for (int i = 1; i &lt; n; i++) {\n dp[i][0] = dp[i - 1][0] + grid[i][0];\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt; n; i++) {\n for (int j = 1; j &lt; m; j++) {\n dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n }\n }\n return dp[n - 1][m - 1];\n}\n</code></pre> min_path_sum.rs<pre><code>/* 最小経路和:動的計画法 */\nfn min_path_sum_dp(grid: &amp;Vec&lt;Vec&lt;i32&gt;&gt;) -&gt; i32 {\n let (n, m) = (grid.len(), grid[0].len());\n // dp テーブルを初期化する\n let mut dp = vec![vec![0; m]; n];\n dp[0][0] = grid[0][0];\n // 状態遷移:先頭行\n for j in 1..m {\n dp[0][j] = dp[0][j - 1] + grid[0][j];\n }\n // 状態遷移:先頭列\n for i in 1..n {\n dp[i][0] = dp[i - 1][0] + grid[i][0];\n }\n // 状態遷移:残りの行と列\n for i in 1..n {\n for j in 1..m {\n dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n }\n }\n dp[n - 1][m - 1]\n}\n</code></pre> min_path_sum.c<pre><code>/* 最小経路和:動的計画法 */\nint minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {\n // dp テーブルを初期化する\n int **dp = malloc(n * sizeof(int *));\n for (int i = 0; i &lt; n; i++) {\n dp[i] = calloc(m, sizeof(int));\n }\n dp[0][0] = grid[0][0];\n // 状態遷移:先頭行\n for (int j = 1; j &lt; m; j++) {\n dp[0][j] = dp[0][j - 1] + grid[0][j];\n }\n // 状態遷移:先頭列\n for (int i = 1; i &lt; n; i++) {\n dp[i][0] = dp[i - 1][0] + grid[i][0];\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt; n; i++) {\n for (int j = 1; j &lt; m; j++) {\n dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];\n }\n }\n int res = dp[n - 1][m - 1];\n // メモリを解放する\n for (int i = 0; i &lt; n; i++) {\n free(dp[i]);\n }\n return res;\n}\n</code></pre> min_path_sum.kt<pre><code>/* 最小経路和:動的計画法 */\nfun minPathSumDP(grid: Array&lt;IntArray&gt;): Int {\n val n = grid.size\n val m = grid[0].size\n // dp テーブルを初期化する\n val dp = Array(n) { IntArray(m) }\n dp[0][0] = grid[0][0]\n // 状態遷移:先頭行\n for (j in 1..&lt;m) {\n dp[0][j] = dp[0][j - 1] + grid[0][j]\n }\n // 状態遷移:先頭列\n for (i in 1..&lt;n) {\n dp[i][0] = dp[i - 1][0] + grid[i][0]\n }\n // 状態遷移:残りの行と列\n for (i in 1..&lt;n) {\n for (j in 1..&lt;m) {\n dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]\n }\n }\n return dp[n - 1][m - 1]\n}\n</code></pre> min_path_sum.rb<pre><code>### 最小経路和:動的計画法 ###\ndef min_path_sum_dp(grid)\n n, m = grid.length, grid.first.length\n # dp テーブルを初期化する\n dp = Array.new(n) { Array.new(m, 0) }\n dp[0][0] = grid[0][0]\n # 状態遷移:先頭行\n (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] }\n # 状態遷移:先頭列\n (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] }\n # 状態遷移:残りの行と列\n for i in 1...n\n for j in 1...m\n dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j]\n end\n end\n dp[n -1][m -1]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-16 に、経路和の最小化の状態遷移を示します。グリッド全体を走査するため、時間計算量は \\(O(nm)\\) です。</p> <p>配列 <code>dp</code> の大きさは \\(n \\times m\\) なので、空間計算量は \\(O(nm)\\) です。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt;&lt;11&gt;&lt;12&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 14-16   経路和の最小化を動的計画法で解く流れ </p>","path":["第 14 章   動的計画法","14.3   動的計画法の解き方"],"tags":[]},{"location":"chapter_dynamic_programming/dp_solution_pipeline/#4","level":3,"title":"4.   空間の最適化","text":"<p>各マスが依存するのは左と上のマスだけなので、1 行分の配列だけで \\(dp\\) テーブルを実装できます。</p> <p>配列 <code>dp</code> は 1 行分の状態しか表せないため、先頭列をあらかじめ初期化することはできません。各行を走査するときに、その先頭の状態を更新することに注意してください。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby min_path_sum.py<pre><code>def min_path_sum_dp_comp(grid: list[list[int]]) -&gt; int:\n \"\"\"最小経路和:空間を最適化した動的計画法\"\"\"\n n, m = len(grid), len(grid[0])\n # dp テーブルを初期化する\n dp = [0] * m\n # 状態遷移:先頭行\n dp[0] = grid[0][0]\n for j in range(1, m):\n dp[j] = dp[j - 1] + grid[0][j]\n # 状態遷移:残りの行\n for i in range(1, n):\n # 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0]\n # 状態遷移:残りの列\n for j in range(1, m):\n dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n return dp[m - 1]\n</code></pre> min_path_sum.cpp<pre><code>/* 最小経路和:空間を最適化した動的計画法 */\nint minPathSumDPComp(vector&lt;vector&lt;int&gt;&gt; &amp;grid) {\n int n = grid.size(), m = grid[0].size();\n // dp テーブルを初期化する\n vector&lt;int&gt; dp(m);\n // 状態遷移:先頭行\n dp[0] = grid[0][0];\n for (int j = 1; j &lt; m; j++) {\n dp[j] = dp[j - 1] + grid[0][j];\n }\n // 状態遷移:残りの行\n for (int i = 1; i &lt; n; i++) {\n // 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0];\n // 状態遷移:残りの列\n for (int j = 1; j &lt; m; j++) {\n dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];\n }\n }\n return dp[m - 1];\n}\n</code></pre> min_path_sum.java<pre><code>/* 最小経路和:空間を最適化した動的計画法 */\nint minPathSumDPComp(int[][] grid) {\n int n = grid.length, m = grid[0].length;\n // dp テーブルを初期化する\n int[] dp = new int[m];\n // 状態遷移:先頭行\n dp[0] = grid[0][0];\n for (int j = 1; j &lt; m; j++) {\n dp[j] = dp[j - 1] + grid[0][j];\n }\n // 状態遷移:残りの行\n for (int i = 1; i &lt; n; i++) {\n // 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0];\n // 状態遷移:残りの列\n for (int j = 1; j &lt; m; j++) {\n dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n }\n }\n return dp[m - 1];\n}\n</code></pre> min_path_sum.cs<pre><code>/* 最小経路和:空間を最適化した動的計画法 */\nint MinPathSumDPComp(int[][] grid) {\n int n = grid.Length, m = grid[0].Length;\n // dp テーブルを初期化する\n int[] dp = new int[m];\n dp[0] = grid[0][0];\n // 状態遷移:先頭行\n for (int j = 1; j &lt; m; j++) {\n dp[j] = dp[j - 1] + grid[0][j];\n }\n // 状態遷移:残りの行\n for (int i = 1; i &lt; n; i++) {\n // 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0];\n // 状態遷移:残りの列\n for (int j = 1; j &lt; m; j++) {\n dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j];\n }\n }\n return dp[m - 1];\n}\n</code></pre> min_path_sum.go<pre><code>/* 最小経路和:空間を最適化した動的計画法 */\nfunc minPathSumDPComp(grid [][]int) int {\n n, m := len(grid), len(grid[0])\n // dp テーブルを初期化する\n dp := make([]int, m)\n // 状態遷移:先頭行\n dp[0] = grid[0][0]\n for j := 1; j &lt; m; j++ {\n dp[j] = dp[j-1] + grid[0][j]\n }\n // 状態遷移:残りの行と列\n for i := 1; i &lt; n; i++ {\n // 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0]\n // 状態遷移:残りの列\n for j := 1; j &lt; m; j++ {\n dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j]\n }\n }\n return dp[m-1]\n}\n</code></pre> min_path_sum.swift<pre><code>/* 最小経路和:空間を最適化した動的計画法 */\nfunc minPathSumDPComp(grid: [[Int]]) -&gt; Int {\n let n = grid.count\n let m = grid[0].count\n // dp テーブルを初期化する\n var dp = Array(repeating: 0, count: m)\n // 状態遷移:先頭行\n dp[0] = grid[0][0]\n for j in 1 ..&lt; m {\n dp[j] = dp[j - 1] + grid[0][j]\n }\n // 状態遷移:残りの行\n for i in 1 ..&lt; n {\n // 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0]\n // 状態遷移:残りの列\n for j in 1 ..&lt; m {\n dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n }\n }\n return dp[m - 1]\n}\n</code></pre> min_path_sum.js<pre><code>/* 最小経路和:空間を最適化した動的計画法 */\nfunction minPathSumDPComp(grid) {\n const n = grid.length,\n m = grid[0].length;\n // dp テーブルを初期化する\n const dp = new Array(m);\n // 状態遷移:先頭行\n dp[0] = grid[0][0];\n for (let j = 1; j &lt; m; j++) {\n dp[j] = dp[j - 1] + grid[0][j];\n }\n // 状態遷移:残りの行\n for (let i = 1; i &lt; n; i++) {\n // 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0];\n // 状態遷移:残りの列\n for (let j = 1; j &lt; m; j++) {\n dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n }\n }\n return dp[m - 1];\n}\n</code></pre> min_path_sum.ts<pre><code>/* 最小経路和:空間を最適化した動的計画法 */\nfunction minPathSumDPComp(grid: Array&lt;Array&lt;number&gt;&gt;): number {\n const n = grid.length,\n m = grid[0].length;\n // dp テーブルを初期化する\n const dp = new Array(m);\n // 状態遷移:先頭行\n dp[0] = grid[0][0];\n for (let j = 1; j &lt; m; j++) {\n dp[j] = dp[j - 1] + grid[0][j];\n }\n // 状態遷移:残りの行\n for (let i = 1; i &lt; n; i++) {\n // 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0];\n // 状態遷移:残りの列\n for (let j = 1; j &lt; m; j++) {\n dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j];\n }\n }\n return dp[m - 1];\n}\n</code></pre> min_path_sum.dart<pre><code>/* 最小経路和:空間を最適化した動的計画法 */\nint minPathSumDPComp(List&lt;List&lt;int&gt;&gt; grid) {\n int n = grid.length, m = grid[0].length;\n // dp テーブルを初期化する\n List&lt;int&gt; dp = List.filled(m, 0);\n dp[0] = grid[0][0];\n for (int j = 1; j &lt; m; j++) {\n dp[j] = dp[j - 1] + grid[0][j];\n }\n // 状態遷移:残りの行\n for (int i = 1; i &lt; n; i++) {\n // 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0];\n // 状態遷移:残りの列\n for (int j = 1; j &lt; m; j++) {\n dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];\n }\n }\n return dp[m - 1];\n}\n</code></pre> min_path_sum.rs<pre><code>/* 最小経路和:空間を最適化した動的計画法 */\nfn min_path_sum_dp_comp(grid: &amp;Vec&lt;Vec&lt;i32&gt;&gt;) -&gt; i32 {\n let (n, m) = (grid.len(), grid[0].len());\n // dp テーブルを初期化する\n let mut dp = vec![0; m];\n // 状態遷移:先頭行\n dp[0] = grid[0][0];\n for j in 1..m {\n dp[j] = dp[j - 1] + grid[0][j];\n }\n // 状態遷移:残りの行\n for i in 1..n {\n // 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0];\n // 状態遷移:残りの列\n for j in 1..m {\n dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j];\n }\n }\n dp[m - 1]\n}\n</code></pre> min_path_sum.c<pre><code>/* 最小経路和:空間を最適化した動的計画法 */\nint minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {\n // dp テーブルを初期化する\n int *dp = calloc(m, sizeof(int));\n // 状態遷移:先頭行\n dp[0] = grid[0][0];\n for (int j = 1; j &lt; m; j++) {\n dp[j] = dp[j - 1] + grid[0][j];\n }\n // 状態遷移:残りの行\n for (int i = 1; i &lt; n; i++) {\n // 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0];\n // 状態遷移:残りの列\n for (int j = 1; j &lt; m; j++) {\n dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j];\n }\n }\n int res = dp[m - 1];\n // メモリを解放する\n free(dp);\n return res;\n}\n</code></pre> min_path_sum.kt<pre><code>/* 最小経路和:空間を最適化した動的計画法 */\nfun minPathSumDPComp(grid: Array&lt;IntArray&gt;): Int {\n val n = grid.size\n val m = grid[0].size\n // dp テーブルを初期化する\n val dp = IntArray(m)\n // 状態遷移:先頭行\n dp[0] = grid[0][0]\n for (j in 1..&lt;m) {\n dp[j] = dp[j - 1] + grid[0][j]\n }\n // 状態遷移:残りの行\n for (i in 1..&lt;n) {\n // 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0]\n // 状態遷移:残りの列\n for (j in 1..&lt;m) {\n dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]\n }\n }\n return dp[m - 1]\n}\n</code></pre> min_path_sum.rb<pre><code>### 最小経路和:空間を最適化した動的計画法 ###\ndef min_path_sum_dp_comp(grid)\n n, m = grid.length, grid.first.length\n # dp テーブルを初期化する\n dp = Array.new(m, 0)\n # 状態遷移:先頭行\n dp[0] = grid[0][0]\n (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] }\n # 状態遷移:残りの行\n for i in 1...n\n # 状態遷移:先頭列\n dp[0] = dp[0] + grid[i][0]\n # 状態遷移:残りの列\n (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] }\n end\n dp[m - 1]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 14 章   動的計画法","14.3   動的計画法の解き方"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/","level":1,"title":"14.6   編集距離問題","text":"<p>編集距離は Levenshtein 距離とも呼ばれ、一方の文字列をもう一方に変換するために必要な最小の変更回数を表します。情報検索や自然言語処理で、二つの列の類似度を測るためによく使われます。</p> <p>問題</p> <p>二つの文字列 \\(s\\) と \\(t\\) が与えられます。\\(s\\) を \\(t\\) に変換するための最小の編集回数を返してください。</p> <p>文字列には、文字を一つ挿入する、文字を一つ削除する、文字を任意の文字に置き換える、という 3 種類の編集操作を行えます。</p> <p>図 14-27 のように、<code>kitten</code> を <code>sitting</code> に変換するには、置換 2 回と挿入 1 回の計 3 回が必要です。<code>hello</code> を <code>algo</code> に変換するには、置換 2 回と削除 1 回の計 3 回が必要です。</p> <p></p> <p> 図 14-27   編集距離のデータ例 </p> <p>編集距離問題は、決定木モデルで自然に説明できます。文字列が木のノードに対応し、1 回の判断(編集操作)が辺に対応します。</p> <p>図 14-28 のように、操作を制限しなければ各ノードから多くの辺が伸び、各辺が一つの操作に対応します。つまり、<code>hello</code> から <code>algo</code> への変換には、多くの経路が考えられます。</p> <p>決定木の観点では、この問題の目標はノード <code>hello</code> とノード <code>algo</code> の間の最短経路を求めることです。</p> <p></p> <p> 図 14-28   決定木モデルによる編集距離問題の表現 </p>","path":["第 14 章   動的計画法","14.6   編集距離問題"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#1","level":3,"title":"1.   動的計画法の考え方","text":"<p>第 1 段階:各回の選択を考えて状態を定義し、\\(dp\\) テーブルを作る</p> <p>各回の選択は、文字列 \\(s\\) に編集操作を 1 回行うことです。</p> <p>部分問題を作るには、編集するたびに問題を小さくしていきたいところです。文字列 \\(s\\) と \\(t\\) の長さをそれぞれ \\(n\\) と \\(m\\) とし、まず末尾の文字 \\(s[n-1]\\) と \\(t[m-1]\\) を考えます。</p> <ul> <li>\\(s[n-1]\\) と \\(t[m-1]\\) が同じなら、それらを飛ばして \\(s[n-2]\\) と \\(t[m-2]\\) を考えられます。</li> <li>\\(s[n-1]\\) と \\(t[m-1]\\) が異なるなら、\\(s\\) に編集(挿入、削除、置換)を 1 回行って末尾の文字を一致させ、それらを飛ばして、より小さな問題を考えます。</li> </ul> <p>つまり、文字列 \\(s\\) に対する各回の判断(編集操作)によって、\\(s\\) と \\(t\\) のまだ対応させていない文字が変わります。そこで、現在考えている \\(s\\) の \\(i\\) 番目と \\(t\\) の \\(j\\) 番目の文字を状態とし、\\([i, j]\\) と表します。</p> <p>状態 \\([i, j]\\) の部分問題は、\\(s\\) の先頭 \\(i\\) 文字を \\(t\\) の先頭 \\(j\\) 文字に変換するための最小編集回数です。</p> <p>これで、\\((n+1) \\times (m+1)\\) の二次元の \\(dp\\) テーブルが得られます。</p> <p>第 2 段階:最適部分構造を見つけ、状態遷移式を導く</p> <p>部分問題 \\(dp[i, j]\\) に対応する二つの文字列の末尾は、\\(s[i-1]\\) と \\(t[j-1]\\) です。編集操作によって、図 14-29 の 3 通りに分けられます。</p> <ol> <li>\\(s[i-1]\\) の後ろに \\(t[j-1]\\) を挿入すると、残る部分問題は \\(dp[i, j-1]\\) です。</li> <li>\\(s[i-1]\\) を削除すると、残る部分問題は \\(dp[i-1, j]\\) です。</li> <li>\\(s[i-1]\\) を \\(t[j-1]\\) に置換すると、残る部分問題は \\(dp[i-1, j-1]\\) です。</li> </ol> <p></p> <p> 図 14-29   編集距離の状態遷移 </p> <p>ここから最適部分構造が得られます。\\(dp[i, j]\\) の最小編集回数は、\\(dp[i, j-1]\\)、\\(dp[i-1, j]\\)、\\(dp[i-1, j-1]\\) の三つの最小編集回数のうち、最も小さいものに、今回の編集 \\(1\\) 回を加えた値です。状態遷移式は次のようになります。</p> \\[ dp[i, j] = \\min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 \\] <p>\\(s[i-1]\\) と \\(t[j-1]\\) が同じなら、その文字を編集する必要はありません。この場合の状態遷移式は次のとおりです。</p> \\[ dp[i, j] = dp[i-1, j-1] \\] <p>第 3 段階:境界条件と状態遷移の順序を決める</p> <p>両方の文字列が空なら編集回数は \\(0\\)、すなわち \\(dp[0, 0] = 0\\) です。\\(s\\) が空で \\(t\\) が空でないなら、最小編集回数は \\(t\\) の長さなので、先頭行は \\(dp[0, j] = j\\) です。\\(s\\) が空でなく \\(t\\) が空なら、最小編集回数は \\(s\\) の長さなので、先頭列は \\(dp[i, 0] = i\\) です。</p> <p>状態遷移式から、解 \\(dp[i, j]\\) は左、上、左上の解に依存します。したがって、二重ループで \\(dp\\) テーブル全体を順方向に走査すれば十分です。</p>","path":["第 14 章   動的計画法","14.6   編集距離問題"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#2","level":3,"title":"2.   コードの実装","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby edit_distance.py<pre><code>def edit_distance_dp(s: str, t: str) -&gt; int:\n \"\"\"編集距離:動的計画法\"\"\"\n n, m = len(s), len(t)\n dp = [[0] * (m + 1) for _ in range(n + 1)]\n # 状態遷移:先頭行と先頭列\n for i in range(1, n + 1):\n dp[i][0] = i\n for j in range(1, m + 1):\n dp[0][j] = j\n # 状態遷移:残りの行と列\n for i in range(1, n + 1):\n for j in range(1, m + 1):\n if s[i - 1] == t[j - 1]:\n # 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i - 1][j - 1]\n else:\n # 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1\n return dp[n][m]\n</code></pre> edit_distance.cpp<pre><code>/* 編集距離:動的計画法 */\nint editDistanceDP(string s, string t) {\n int n = s.length(), m = t.length();\n vector&lt;vector&lt;int&gt;&gt; dp(n + 1, vector&lt;int&gt;(m + 1, 0));\n // 状態遷移:先頭行と先頭列\n for (int i = 1; i &lt;= n; i++) {\n dp[i][0] = i;\n }\n for (int j = 1; j &lt;= m; j++) {\n dp[0][j] = j;\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt;= n; i++) {\n for (int j = 1; j &lt;= m; j++) {\n if (s[i - 1] == t[j - 1]) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i - 1][j - 1];\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n }\n }\n }\n return dp[n][m];\n}\n</code></pre> edit_distance.java<pre><code>/* 編集距離:動的計画法 */\nint editDistanceDP(String s, String t) {\n int n = s.length(), m = t.length();\n int[][] dp = new int[n + 1][m + 1];\n // 状態遷移:先頭行と先頭列\n for (int i = 1; i &lt;= n; i++) {\n dp[i][0] = i;\n }\n for (int j = 1; j &lt;= m; j++) {\n dp[0][j] = j;\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt;= n; i++) {\n for (int j = 1; j &lt;= m; j++) {\n if (s.charAt(i - 1) == t.charAt(j - 1)) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i - 1][j - 1];\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n }\n }\n }\n return dp[n][m];\n}\n</code></pre> edit_distance.cs<pre><code>/* 編集距離:動的計画法 */\nint EditDistanceDP(string s, string t) {\n int n = s.Length, m = t.Length;\n int[,] dp = new int[n + 1, m + 1];\n // 状態遷移:先頭行と先頭列\n for (int i = 1; i &lt;= n; i++) {\n dp[i, 0] = i;\n }\n for (int j = 1; j &lt;= m; j++) {\n dp[0, j] = j;\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt;= n; i++) {\n for (int j = 1; j &lt;= m; j++) {\n if (s[i - 1] == t[j - 1]) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i, j] = dp[i - 1, j - 1];\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1;\n }\n }\n }\n return dp[n, m];\n}\n</code></pre> edit_distance.go<pre><code>/* 編集距離:動的計画法 */\nfunc editDistanceDP(s string, t string) int {\n n := len(s)\n m := len(t)\n dp := make([][]int, n+1)\n for i := 0; i &lt;= n; i++ {\n dp[i] = make([]int, m+1)\n }\n // 状態遷移:先頭行と先頭列\n for i := 1; i &lt;= n; i++ {\n dp[i][0] = i\n }\n for j := 1; j &lt;= m; j++ {\n dp[0][j] = j\n }\n // 状態遷移:残りの行と列\n for i := 1; i &lt;= n; i++ {\n for j := 1; j &lt;= m; j++ {\n if s[i-1] == t[j-1] {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i-1][j-1]\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1\n }\n }\n }\n return dp[n][m]\n}\n</code></pre> edit_distance.swift<pre><code>/* 編集距離:動的計画法 */\nfunc editDistanceDP(s: String, t: String) -&gt; Int {\n let n = s.utf8CString.count\n let m = t.utf8CString.count\n var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1)\n // 状態遷移:先頭行と先頭列\n for i in 1 ... n {\n dp[i][0] = i\n }\n for j in 1 ... m {\n dp[0][j] = j\n }\n // 状態遷移:残りの行と列\n for i in 1 ... n {\n for j in 1 ... m {\n if s.utf8CString[i - 1] == t.utf8CString[j - 1] {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i - 1][j - 1]\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1\n }\n }\n }\n return dp[n][m]\n}\n</code></pre> edit_distance.js<pre><code>/* 編集距離:動的計画法 */\nfunction editDistanceDP(s, t) {\n const n = s.length,\n m = t.length;\n const dp = Array.from({ length: n + 1 }, () =&gt; new Array(m + 1).fill(0));\n // 状態遷移:先頭行と先頭列\n for (let i = 1; i &lt;= n; i++) {\n dp[i][0] = i;\n }\n for (let j = 1; j &lt;= m; j++) {\n dp[0][j] = j;\n }\n // 状態遷移:残りの行と列\n for (let i = 1; i &lt;= n; i++) {\n for (let j = 1; j &lt;= m; j++) {\n if (s.charAt(i - 1) === t.charAt(j - 1)) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i - 1][j - 1];\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] =\n Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;\n }\n }\n }\n return dp[n][m];\n}\n</code></pre> edit_distance.ts<pre><code>/* 編集距離:動的計画法 */\nfunction editDistanceDP(s: string, t: string): number {\n const n = s.length,\n m = t.length;\n const dp = Array.from({ length: n + 1 }, () =&gt;\n Array.from({ length: m + 1 }, () =&gt; 0)\n );\n // 状態遷移:先頭行と先頭列\n for (let i = 1; i &lt;= n; i++) {\n dp[i][0] = i;\n }\n for (let j = 1; j &lt;= m; j++) {\n dp[0][j] = j;\n }\n // 状態遷移:残りの行と列\n for (let i = 1; i &lt;= n; i++) {\n for (let j = 1; j &lt;= m; j++) {\n if (s.charAt(i - 1) === t.charAt(j - 1)) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i - 1][j - 1];\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] =\n Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1;\n }\n }\n }\n return dp[n][m];\n}\n</code></pre> edit_distance.dart<pre><code>/* 編集距離:動的計画法 */\nint editDistanceDP(String s, String t) {\n int n = s.length, m = t.length;\n List&lt;List&lt;int&gt;&gt; dp = List.generate(n + 1, (_) =&gt; List.filled(m + 1, 0));\n // 状態遷移:先頭行と先頭列\n for (int i = 1; i &lt;= n; i++) {\n dp[i][0] = i;\n }\n for (int j = 1; j &lt;= m; j++) {\n dp[0][j] = j;\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt;= n; i++) {\n for (int j = 1; j &lt;= m; j++) {\n if (s[i - 1] == t[j - 1]) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i - 1][j - 1];\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n }\n }\n }\n return dp[n][m];\n}\n</code></pre> edit_distance.rs<pre><code>/* 編集距離:動的計画法 */\nfn edit_distance_dp(s: &amp;str, t: &amp;str) -&gt; i32 {\n let (n, m) = (s.len(), t.len());\n let mut dp = vec![vec![0; m + 1]; n + 1];\n // 状態遷移:先頭行と先頭列\n for i in 1..=n {\n dp[i][0] = i as i32;\n }\n for j in 1..m {\n dp[0][j] = j as i32;\n }\n // 状態遷移:残りの行と列\n for i in 1..=n {\n for j in 1..=m {\n if s.chars().nth(i - 1) == t.chars().nth(j - 1) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i - 1][j - 1];\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] =\n std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n }\n }\n }\n dp[n][m]\n}\n</code></pre> edit_distance.c<pre><code>/* 編集距離:動的計画法 */\nint editDistanceDP(char *s, char *t, int n, int m) {\n int **dp = malloc((n + 1) * sizeof(int *));\n for (int i = 0; i &lt;= n; i++) {\n dp[i] = calloc(m + 1, sizeof(int));\n }\n // 状態遷移:先頭行と先頭列\n for (int i = 1; i &lt;= n; i++) {\n dp[i][0] = i;\n }\n for (int j = 1; j &lt;= m; j++) {\n dp[0][j] = j;\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt;= n; i++) {\n for (int j = 1; j &lt;= m; j++) {\n if (s[i - 1] == t[j - 1]) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i - 1][j - 1];\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;\n }\n }\n }\n int res = dp[n][m];\n // メモリを解放する\n for (int i = 0; i &lt;= n; i++) {\n free(dp[i]);\n }\n return res;\n}\n</code></pre> edit_distance.kt<pre><code>/* 編集距離:動的計画法 */\nfun editDistanceDP(s: String, t: String): Int {\n val n = s.length\n val m = t.length\n val dp = Array(n + 1) { IntArray(m + 1) }\n // 状態遷移:先頭行と先頭列\n for (i in 1..n) {\n dp[i][0] = i\n }\n for (j in 1..m) {\n dp[0][j] = j\n }\n // 状態遷移:残りの行と列\n for (i in 1..n) {\n for (j in 1..m) {\n if (s[i - 1] == t[j - 1]) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i - 1][j - 1]\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1\n }\n }\n }\n return dp[n][m]\n}\n</code></pre> edit_distance.rb<pre><code>### 編集距離:動的計画法 ###\ndef edit_distance_dp(s, t)\n n, m = s.length, t.length\n dp = Array.new(n + 1) { Array.new(m + 1, 0) }\n # 状態遷移:先頭行と先頭列\n (1...(n + 1)).each { |i| dp[i][0] = i }\n (1...(m + 1)).each { |j| dp[0][j] = j }\n # 状態遷移:残りの行と列\n for i in 1...(n + 1)\n for j in 1...(m +1)\n if s[i - 1] == t[j - 1]\n # 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[i][j] = dp[i - 1][j - 1]\n else\n # 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1\n end\n end\n end\n dp[n][m]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-30 のように、編集距離問題の状態遷移はナップサック問題によく似ており、どちらも二次元の表を埋める過程と捉えられます。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt;&lt;11&gt;&lt;12&gt;&lt;13&gt;&lt;14&gt;&lt;15&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 14-30   編集距離を動的計画法で求める流れ </p>","path":["第 14 章   動的計画法","14.6   編集距離問題"],"tags":[]},{"location":"chapter_dynamic_programming/edit_distance_problem/#3","level":3,"title":"3.   空間の最適化","text":"<p>\\(dp[i,j]\\) は、上の \\(dp[i-1, j]\\)、左の \\(dp[i, j-1]\\)、左上の \\(dp[i-1, j-1]\\) から遷移します。順方向に走査すると左上の \\(dp[i-1, j-1]\\) が失われ、逆方向では \\(dp[i, j-1]\\) を先に計算できません。そのため、どちらの順序もそのままでは使えません。</p> <p>そこで、変数 <code>leftup</code> に左上の解 \\(dp[i-1, j-1]\\) を一時保存すれば、左と上の解だけを考えればよくなります。これは個数制限なしナップサック問題と同じ状況なので、順方向に走査できます。コードを次に示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby edit_distance.py<pre><code>def edit_distance_dp_comp(s: str, t: str) -&gt; int:\n \"\"\"編集距離:空間を最適化した動的計画法\"\"\"\n n, m = len(s), len(t)\n dp = [0] * (m + 1)\n # 状態遷移:先頭行\n for j in range(1, m + 1):\n dp[j] = j\n # 状態遷移:残りの行\n for i in range(1, n + 1):\n # 状態遷移:先頭列\n leftup = dp[0] # dp[i-1, j-1] を一時保存する\n dp[0] += 1\n # 状態遷移:残りの列\n for j in range(1, m + 1):\n temp = dp[j]\n if s[i - 1] == t[j - 1]:\n # 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup\n else:\n # 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = min(dp[j - 1], dp[j], leftup) + 1\n leftup = temp # 次の反復の dp[i-1, j-1] に更新する\n return dp[m]\n</code></pre> edit_distance.cpp<pre><code>/* 編集距離:空間を最適化した動的計画法 */\nint editDistanceDPComp(string s, string t) {\n int n = s.length(), m = t.length();\n vector&lt;int&gt; dp(m + 1, 0);\n // 状態遷移:先頭行\n for (int j = 1; j &lt;= m; j++) {\n dp[j] = j;\n }\n // 状態遷移:残りの行\n for (int i = 1; i &lt;= n; i++) {\n // 状態遷移:先頭列\n int leftup = dp[0]; // dp[i-1, j-1] を一時保存する\n dp[0] = i;\n // 状態遷移:残りの列\n for (int j = 1; j &lt;= m; j++) {\n int temp = dp[j];\n if (s[i - 1] == t[j - 1]) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup;\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;\n }\n leftup = temp; // 次の反復の dp[i-1, j-1] に更新する\n }\n }\n return dp[m];\n}\n</code></pre> edit_distance.java<pre><code>/* 編集距離:空間を最適化した動的計画法 */\nint editDistanceDPComp(String s, String t) {\n int n = s.length(), m = t.length();\n int[] dp = new int[m + 1];\n // 状態遷移:先頭行\n for (int j = 1; j &lt;= m; j++) {\n dp[j] = j;\n }\n // 状態遷移:残りの行\n for (int i = 1; i &lt;= n; i++) {\n // 状態遷移:先頭列\n int leftup = dp[0]; // dp[i-1, j-1] を一時保存する\n dp[0] = i;\n // 状態遷移:残りの列\n for (int j = 1; j &lt;= m; j++) {\n int temp = dp[j];\n if (s.charAt(i - 1) == t.charAt(j - 1)) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup;\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1;\n }\n leftup = temp; // 次の反復の dp[i-1, j-1] に更新する\n }\n }\n return dp[m];\n}\n</code></pre> edit_distance.cs<pre><code>/* 編集距離:空間を最適化した動的計画法 */\nint EditDistanceDPComp(string s, string t) {\n int n = s.Length, m = t.Length;\n int[] dp = new int[m + 1];\n // 状態遷移:先頭行\n for (int j = 1; j &lt;= m; j++) {\n dp[j] = j;\n }\n // 状態遷移:残りの行\n for (int i = 1; i &lt;= n; i++) {\n // 状態遷移:先頭列\n int leftup = dp[0]; // dp[i-1, j-1] を一時保存する\n dp[0] = i;\n // 状態遷移:残りの列\n for (int j = 1; j &lt;= m; j++) {\n int temp = dp[j];\n if (s[i - 1] == t[j - 1]) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup;\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1;\n }\n leftup = temp; // 次の反復の dp[i-1, j-1] に更新する\n }\n }\n return dp[m];\n}\n</code></pre> edit_distance.go<pre><code>/* 編集距離:空間を最適化した動的計画法 */\nfunc editDistanceDPComp(s string, t string) int {\n n := len(s)\n m := len(t)\n dp := make([]int, m+1)\n // 状態遷移:先頭行\n for j := 1; j &lt;= m; j++ {\n dp[j] = j\n }\n // 状態遷移:残りの行\n for i := 1; i &lt;= n; i++ {\n // 状態遷移:先頭列\n leftUp := dp[0] // dp[i-1, j-1] を一時保存する\n dp[0] = i\n // 状態遷移:残りの列\n for j := 1; j &lt;= m; j++ {\n temp := dp[j]\n if s[i-1] == t[j-1] {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftUp\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1\n }\n leftUp = temp // 次の反復の dp[i-1, j-1] に更新する\n }\n }\n return dp[m]\n}\n</code></pre> edit_distance.swift<pre><code>/* 編集距離:空間を最適化した動的計画法 */\nfunc editDistanceDPComp(s: String, t: String) -&gt; Int {\n let n = s.utf8CString.count\n let m = t.utf8CString.count\n var dp = Array(repeating: 0, count: m + 1)\n // 状態遷移:先頭行\n for j in 1 ... m {\n dp[j] = j\n }\n // 状態遷移:残りの行\n for i in 1 ... n {\n // 状態遷移:先頭列\n var leftup = dp[0] // dp[i-1, j-1] を一時保存する\n dp[0] = i\n // 状態遷移:残りの列\n for j in 1 ... m {\n let temp = dp[j]\n if s.utf8CString[i - 1] == t.utf8CString[j - 1] {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1\n }\n leftup = temp // 次の反復の dp[i-1, j-1] に更新する\n }\n }\n return dp[m]\n}\n</code></pre> edit_distance.js<pre><code>/* 編集距離:空間を最適化した動的計画法 */\nfunction editDistanceDPComp(s, t) {\n const n = s.length,\n m = t.length;\n const dp = new Array(m + 1).fill(0);\n // 状態遷移:先頭行\n for (let j = 1; j &lt;= m; j++) {\n dp[j] = j;\n }\n // 状態遷移:残りの行\n for (let i = 1; i &lt;= n; i++) {\n // 状態遷移:先頭列\n let leftup = dp[0]; // dp[i-1, j-1] を一時保存する\n dp[0] = i;\n // 状態遷移:残りの列\n for (let j = 1; j &lt;= m; j++) {\n const temp = dp[j];\n if (s.charAt(i - 1) === t.charAt(j - 1)) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup;\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;\n }\n leftup = temp; // 次の反復の dp[i-1, j-1] に更新する\n }\n }\n return dp[m];\n}\n</code></pre> edit_distance.ts<pre><code>/* 編集距離:空間を最適化した動的計画法 */\nfunction editDistanceDPComp(s: string, t: string): number {\n const n = s.length,\n m = t.length;\n const dp = new Array(m + 1).fill(0);\n // 状態遷移:先頭行\n for (let j = 1; j &lt;= m; j++) {\n dp[j] = j;\n }\n // 状態遷移:残りの行\n for (let i = 1; i &lt;= n; i++) {\n // 状態遷移:先頭列\n let leftup = dp[0]; // dp[i-1, j-1] を一時保存する\n dp[0] = i;\n // 状態遷移:残りの列\n for (let j = 1; j &lt;= m; j++) {\n const temp = dp[j];\n if (s.charAt(i - 1) === t.charAt(j - 1)) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup;\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1;\n }\n leftup = temp; // 次の反復の dp[i-1, j-1] に更新する\n }\n }\n return dp[m];\n}\n</code></pre> edit_distance.dart<pre><code>/* 編集距離:空間を最適化した動的計画法 */\nint editDistanceDPComp(String s, String t) {\n int n = s.length, m = t.length;\n List&lt;int&gt; dp = List.filled(m + 1, 0);\n // 状態遷移:先頭行\n for (int j = 1; j &lt;= m; j++) {\n dp[j] = j;\n }\n // 状態遷移:残りの行\n for (int i = 1; i &lt;= n; i++) {\n // 状態遷移:先頭列\n int leftup = dp[0]; // dp[i-1, j-1] を一時保存する\n dp[0] = i;\n // 状態遷移:残りの列\n for (int j = 1; j &lt;= m; j++) {\n int temp = dp[j];\n if (s[i - 1] == t[j - 1]) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup;\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;\n }\n leftup = temp; // 次の反復の dp[i-1, j-1] に更新する\n }\n }\n return dp[m];\n}\n</code></pre> edit_distance.rs<pre><code>/* 編集距離:空間を最適化した動的計画法 */\nfn edit_distance_dp_comp(s: &amp;str, t: &amp;str) -&gt; i32 {\n let (n, m) = (s.len(), t.len());\n let mut dp = vec![0; m + 1];\n // 状態遷移:先頭行\n for j in 1..m {\n dp[j] = j as i32;\n }\n // 状態遷移:残りの行\n for i in 1..=n {\n // 状態遷移:先頭列\n let mut leftup = dp[0]; // dp[i-1, j-1] を一時保存する\n dp[0] = i as i32;\n // 状態遷移:残りの列\n for j in 1..=m {\n let temp = dp[j];\n if s.chars().nth(i - 1) == t.chars().nth(j - 1) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup;\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1;\n }\n leftup = temp; // 次の反復の dp[i-1, j-1] に更新する\n }\n }\n dp[m]\n}\n</code></pre> edit_distance.c<pre><code>/* 編集距離:空間を最適化した動的計画法 */\nint editDistanceDPComp(char *s, char *t, int n, int m) {\n int *dp = calloc(m + 1, sizeof(int));\n // 状態遷移:先頭行\n for (int j = 1; j &lt;= m; j++) {\n dp[j] = j;\n }\n // 状態遷移:残りの行\n for (int i = 1; i &lt;= n; i++) {\n // 状態遷移:先頭列\n int leftup = dp[0]; // dp[i-1, j-1] を一時保存する\n dp[0] = i;\n // 状態遷移:残りの列\n for (int j = 1; j &lt;= m; j++) {\n int temp = dp[j];\n if (s[i - 1] == t[j - 1]) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup;\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1;\n }\n leftup = temp; // 次の反復の dp[i-1, j-1] に更新する\n }\n }\n int res = dp[m];\n // メモリを解放する\n free(dp);\n return res;\n}\n</code></pre> edit_distance.kt<pre><code>/* 編集距離:空間を最適化した動的計画法 */\nfun editDistanceDPComp(s: String, t: String): Int {\n val n = s.length\n val m = t.length\n val dp = IntArray(m + 1)\n // 状態遷移:先頭行\n for (j in 1..m) {\n dp[j] = j\n }\n // 状態遷移:残りの行\n for (i in 1..n) {\n // 状態遷移:先頭列\n var leftup = dp[0] // dp[i-1, j-1] を一時保存する\n dp[0] = i\n // 状態遷移:残りの列\n for (j in 1..m) {\n val temp = dp[j]\n if (s[i - 1] == t[j - 1]) {\n // 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup\n } else {\n // 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1\n }\n leftup = temp // 次の反復の dp[i-1, j-1] に更新する\n }\n }\n return dp[m]\n}\n</code></pre> edit_distance.rb<pre><code>### 編集距離:空間を最適化した動的計画法 ###\ndef edit_distance_dp_comp(s, t)\n n, m = s.length, t.length\n dp = Array.new(m + 1, 0)\n # 状態遷移:先頭行\n (1...(m + 1)).each { |j| dp[j] = j }\n # 状態遷移:残りの行\n for i in 1...(n + 1)\n # 状態遷移:先頭列\n leftup = dp.first # dp[i-1, j-1] を一時保存する\n dp[0] += 1\n # 状態遷移:残りの列\n for j in 1...(m + 1)\n temp = dp[j]\n if s[i - 1] == t[j - 1]\n # 2 つの文字が等しい場合、その 2 文字をスキップする\n dp[j] = leftup\n else\n # 最小編集回数 = 挿入、削除、置換の 3 操作での最小編集回数 + 1\n dp[j] = [dp[j - 1], dp[j], leftup].min + 1\n end\n leftup = temp # 次の反復の dp[i-1, j-1] に更新する\n end\n end\n dp[m]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 14 章   動的計画法","14.6   編集距離問題"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/","level":1,"title":"14.8   練習問題","text":"","path":["第 14 章   動的計画法","14.8   練習問題"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1481","level":2,"title":"14.8.1   知識の確認","text":"","path":["第 14 章   動的計画法","14.8   練習問題"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1","level":3,"title":"1.   動的計画法に適した場面","text":"<p>ある生徒が「漸化式を書けるなら、必ず動的計画法を使うべきだ」と言いました。 次の各作業には、動的計画法、バックトラッキング、または <code>dp</code> 表を作らずループや数式を使う方法のどれがより適しているかを判断し、主な理由を一つ書いてください。</p> <ol> <li>額面 <code>[1, 3, 4]</code> の硬貨で金額 6 を作るための最少枚数を求めます。各硬貨は何枚でも使えます。</li> <li><code>[1, 2, 3]</code> の全順列を出力します。</li> <li> <p>\\(1 + 2 + \\dots + n\\) を計算します。</p> <p>動的計画法に適していると判断した作業については、<code>dp[i]</code> が何を表すかも説明してください。</p> </li> </ol> 解答例 <ol> <li> <p>動的計画法に適しています。<code>dp[i]</code> を、金額 <code>i</code> を作るために必要な硬貨の最少枚数とします。 <code>i</code> 以下の額面の各硬貨 <code>c</code> について <code>dp[i-c] + 1</code> を答えの候補とし、 その最小値を取ります。異なる選択でも同じ金額に繰り返し行き着き、大きな金額の最適解も小さな金額の最適解から構成できます。 金額 6 の答えは、<code>3 + 3</code> の 2 枚です。</p> </li> <li> <p>バックトラッキングを使います。求められるのは 6 個すべての順列を一つずつ生成することであり、バックトラッキングなら、一つを選んで探索を進め、 選択を取り消して別の分岐を試す作業を体系的に行えます。どの方法でも、実際に順列を出力するときには列挙を省略できません。</p> </li> <li> <p>ループまたは等差数列の公式で十分です。<code>S(i) = S(i-1) + i</code> と書けますが、<code>S(i)</code> は一つの小さな <code>S(i-1)</code> だけに依存し、 各部分和は一回計算すれば済みます。重複する部分問題がないため、<code>dp</code> 表は不要です。「漸化式を書ける」ことと「動的計画法が必要」なことは同じではありません。</p> </li> </ol>","path":["第 14 章   動的計画法","14.8   練習問題"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#2","level":3,"title":"2.   ナップサックの表の一マスを計算する","text":"<p>0-1 ナップサック問題で、品物の重さは <code>wgt = [1, 2, 3]</code>、価値は <code>val = [5, 11, 15]</code>、容量は 4 です。 <code>dp[i][c]</code> は、最初の \\(i\\) 個の品物だけを考え、容量の上限を \\(c\\) としたときに得られる最大の価値を表します。 ナップサックをちょうど満杯にする必要はありません。</p> <p>状態 <code>dp[3][4]</code> だけを計算します。<code>dp[2][4] = 16</code>、<code>dp[2][1] = 5</code> は分かっているものとします。</p> <ol> <li>3 番目の品物を選ばない場合、価値の候補はいくつですか。</li> <li>3 番目の品物を選ぶ場合、残りの容量と価値の候補はいくつですか。</li> <li><code>dp[3][4]</code> はいくつになりますか。どの品物を選ぶことに対応しますか。</li> </ol> 解答例 <ol> <li> <p>3 番目の品物を選ばない場合は、最初の二つの品物の結果を使い、候補は <code>dp[2][4] = 16</code> です。</p> </li> <li> <p>3 番目の品物の重さは 3 なので、入れた後の残りの容量は \\(4-3=1\\) です。価値の候補は <code>dp[2][1] + 15 = 5 + 15 = 20</code> です。</p> </li> <li> <p>16 と 20 を比較して、<code>dp[3][4] = 20</code> となります。これは 1 番目と 3 番目の品物を選ぶことに対応し、 重さの合計は \\(1+3=4\\)、価値の合計は \\(5+15=20\\) です。</p> <p>この状態の計算は、0-1 ナップサック問題での一回の「選ぶか選ばないか」の比較を表しています。</p> </li> </ol>","path":["第 14 章   動的計画法","14.8   練習問題"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#3","level":3,"title":"3.   ナップサックの容量を更新する順序","text":"<p>0-1 ナップサック問題に、重さ 2、価値 5 の品物が一つだけあります。ナップサックの容量は 4 です。 各品物は一回だけ選べ、初期の一次元配列は <code>dp = [0, 0, 0, 0, 0]</code> です。</p> <p>ある生徒がこの品物を処理するとき、容量 2 から 4 の順に更新しました。</p> <ul> <li><code>dp[2]</code> を更新して 5 を得ました。</li> <li><code>dp[3]</code> を更新して、ここでも 5 を得ました。</li> <li><code>dp[4]</code> の更新では、今求めた <code>dp[2]</code> を使ったため、<code>dp[4] = 10</code> となりました。</li> </ul> <ol> <li><code>dp[4] = 10</code> は正しい結果ですか。理由も説明してください。</li> <li>「各品物は一回だけ選べる」という条件から、<code>dp[4]</code> はいくつになるべきですか。</li> <li>各品物を処理するとき、容量は大きい順と小さい順のどちらで更新すべきですか。その順序によって、どの問題を避けられますか。</li> </ol> 解答例 <ol> <li> <p>正しくありません。価値 10 は、価値 5 の同じ品物を二回入れたことに相当し、 「各品物は一回だけ選べる」という条件に違反します。</p> </li> <li> <p>入れられるのはこの一つの品物だけなので、正しい <code>dp[4]</code> は 5 です。</p> </li> <li> <p>容量は大きい順、つまり 4、3、2 の順に更新します。 そうすれば、<code>dp[c]</code> を計算するときに読み取る <code>dp[c-2]</code> は、まだ現在の品物を処理する前の結果なので、 同じ反復内で現在の品物を重複して使わずに済みます。</p> </li> </ol>","path":["第 14 章   動的計画法","14.8   練習問題"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1482","level":2,"title":"14.8.2   プログラミング演習","text":"","path":["第 14 章   動的計画法","14.8   練習問題"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#1_1","level":3,"title":"1.   階段の上り方の数","text":"<p><code>n</code> 段の階段があります。一回に 1 段か 2 段だけ上れ、ちょうど第 <code>n</code> 段に到達する必要があります。 異なる上り方が何通りあるかを計算してください。<code>n &gt;= 1</code> とし、各歩で 1 段進むか 2 段進むかによってのみ上り方を区別します。 一次元の動的計画法の配列を使い、二つの状態だけを残す空間最適化は、ここでは行わないでください。</p> 解くためのヒント <ol> <li>第 i 段に到達する最後の一歩は、1 段または 2 段しか進めません</li> <li>したがって、dp[i] = dp[i-1] + dp[i-2] が成り立ちます</li> <li>まず n が 1 と 2 の場合を処理し、第 3 段から表を埋めます</li> </ol> <p>LeetCode</p>","path":["第 14 章   動的計画法","14.8   練習問題"],"tags":[]},{"location":"chapter_dynamic_programming/exercises/#2-0-1","level":3,"title":"2.   0-1 ナップサック問題","text":"<p>同じ長さの配列 <code>wgt</code> と <code>val</code> が与えられます。品物 <code>i</code> の重さは正の整数 <code>wgt[i]</code>、価値は非負整数 <code>val[i]</code> です。 ナップサックの容量 <code>cap</code> は非負整数です。各品物は一回だけ選べます。重さの合計が <code>cap</code> を超えない範囲で、 ナップサックに入れられる価値の合計の最大値を求めてください。一次元の動的計画法で実装してください。</p> 解くためのヒント <ol> <li>長さ cap + 1 の配列 dp を初期化します。dp[c] は、容量の上限が c の場合の最大の価値を表します</li> <li>品物 i を処理するときは、「選ばない」場合の dp[c] と、「選ぶ」場合の dp[c-wgt[i]] + val[i] を比較します</li> <li>同じ反復内で現在の品物を重複して選ばないよう、容量は必ず大きい順に更新します</li> </ol>","path":["第 14 章   動的計画法","14.8   練習問題"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/","level":1,"title":"14.1   動的計画法への第一歩","text":"<p>動的計画法(dynamic programming)は、問題を一連の小さな部分問題に分解し、その解を保存することで重複した計算を避け、時間効率を大幅に高める重要なアルゴリズムの枠組みです。</p> <p>この節では、古典的な問題を取り上げ、まず総当たりのバックトラッキングによる解法を示します。そこに含まれる部分問題の重複を確認し、より効率的な動的計画法へと段階的に発展させます。</p> <p>階段を上る</p> <p>\\(n\\) 段の階段があります。1 回で \\(1\\) 段または \\(2\\) 段上れるとき、一番上まで上る方法は何通りありますか。</p> <p>図 14-1 のように、\\(3\\) 段の階段を上る方法は \\(3\\) 通りあります。</p> <p></p> <p> 図 14-1   3 段目まで上る方法の数 </p> <p>求めたいのは方法の数なので、バックトラッキングですべての可能性を列挙することが考えられます。具体的には、階段を上ることを選択の繰り返しと捉えます。地面から出発し、毎回 \\(1\\) 段か \\(2\\) 段かを選び、一番上に着くたびに方法の数を \\(1\\) 増やします。一番上を超えた場合は枝刈りします。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_backtrack.py<pre><code>def backtrack(choices: list[int], state: int, n: int, res: list[int]) -&gt; int:\n \"\"\"バックトラッキング\"\"\"\n # n 段目に到達したら、方法の数を 1 増やす\n if state == n:\n res[0] += 1\n # すべての選択肢を走査する\n for choice in choices:\n # 枝刈り:n 段目を越えてはならない\n if state + choice &gt; n:\n continue\n # 試行:選択を行い、状態を更新する\n backtrack(choices, state + choice, n, res)\n # 復元\n\ndef climbing_stairs_backtrack(n: int) -&gt; int:\n \"\"\"階段の上り方:バックトラッキング\"\"\"\n choices = [1, 2] # 1 段または 2 段上ることを選択できる\n state = 0 # 0 段目から上り始める\n res = [0] # res[0] に方法の数を記録する\n backtrack(choices, state, n, res)\n return res[0]\n</code></pre> climbing_stairs_backtrack.cpp<pre><code>/* バックトラッキング */\nvoid backtrack(vector&lt;int&gt; &amp;choices, int state, int n, vector&lt;int&gt; &amp;res) {\n // n 段目に到達したら、方法の数を 1 増やす\n if (state == n)\n res[0]++;\n // すべての選択肢を走査する\n for (auto &amp;choice : choices) {\n // 枝刈り:n 段目を越えてはならない\n if (state + choice &gt; n)\n continue;\n // 試行:選択を行い、状態を更新する\n backtrack(choices, state + choice, n, res);\n // 復元\n }\n}\n\n/* 階段の上り方:バックトラッキング */\nint climbingStairsBacktrack(int n) {\n vector&lt;int&gt; choices = {1, 2}; // 1 段または 2 段上ることを選択できる\n int state = 0; // 0 段目から上り始める\n vector&lt;int&gt; res = {0}; // res[0] に方法の数を記録する\n backtrack(choices, state, n, res);\n return res[0];\n}\n</code></pre> climbing_stairs_backtrack.java<pre><code>/* バックトラッキング */\nvoid backtrack(List&lt;Integer&gt; choices, int state, int n, List&lt;Integer&gt; res) {\n // n 段目に到達したら、方法の数を 1 増やす\n if (state == n)\n res.set(0, res.get(0) + 1);\n // すべての選択肢を走査する\n for (Integer choice : choices) {\n // 枝刈り:n 段目を越えてはならない\n if (state + choice &gt; n)\n continue;\n // 試行:選択を行い、状態を更新する\n backtrack(choices, state + choice, n, res);\n // 復元\n }\n}\n\n/* 階段の上り方:バックトラッキング */\nint climbingStairsBacktrack(int n) {\n List&lt;Integer&gt; choices = Arrays.asList(1, 2); // 1 段または 2 段上ることを選択できる\n int state = 0; // 0 段目から上り始める\n List&lt;Integer&gt; res = new ArrayList&lt;&gt;();\n res.add(0); // res[0] に方法の数を記録する\n backtrack(choices, state, n, res);\n return res.get(0);\n}\n</code></pre> climbing_stairs_backtrack.cs<pre><code>/* バックトラッキング */\nvoid Backtrack(List&lt;int&gt; choices, int state, int n, List&lt;int&gt; res) {\n // n 段目に到達したら、方法の数を 1 増やす\n if (state == n)\n res[0]++;\n // すべての選択肢を走査する\n foreach (int choice in choices) {\n // 枝刈り:n 段目を越えてはならない\n if (state + choice &gt; n)\n continue;\n // 試行:選択を行い、状態を更新する\n Backtrack(choices, state + choice, n, res);\n // 復元\n }\n}\n\n/* 階段の上り方:バックトラッキング */\nint ClimbingStairsBacktrack(int n) {\n List&lt;int&gt; choices = [1, 2]; // 1 段または 2 段上ることを選択できる\n int state = 0; // 0 段目から上り始める\n List&lt;int&gt; res = [0]; // res[0] に方法の数を記録する\n Backtrack(choices, state, n, res);\n return res[0];\n}\n</code></pre> climbing_stairs_backtrack.go<pre><code>/* バックトラッキング */\nfunc backtrack(choices []int, state, n int, res []int) {\n // n 段目に到達したら、方法の数を 1 増やす\n if state == n {\n res[0] = res[0] + 1\n }\n // すべての選択肢を走査する\n for _, choice := range choices {\n // 枝刈り:n 段目を越えてはならない\n if state+choice &gt; n {\n continue\n }\n // 試行:選択を行い、状態を更新する\n backtrack(choices, state+choice, n, res)\n // 復元\n }\n}\n\n/* 階段の上り方:バックトラッキング */\nfunc climbingStairsBacktrack(n int) int {\n // 1 段または 2 段上ることを選択できる\n choices := []int{1, 2}\n // 0 段目から上り始める\n state := 0\n res := make([]int, 1)\n // res[0] に方法の数を記録する\n res[0] = 0\n backtrack(choices, state, n, res)\n return res[0]\n}\n</code></pre> climbing_stairs_backtrack.swift<pre><code>/* バックトラッキング */\nfunc backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) {\n // n 段目に到達したら、方法の数を 1 増やす\n if state == n {\n res[0] += 1\n }\n // すべての選択肢を走査する\n for choice in choices {\n // 枝刈り:n 段目を越えてはならない\n if state + choice &gt; n {\n continue\n }\n // 試行:選択を行い、状態を更新する\n backtrack(choices: choices, state: state + choice, n: n, res: &amp;res)\n // 復元\n }\n}\n\n/* 階段の上り方:バックトラッキング */\nfunc climbingStairsBacktrack(n: Int) -&gt; Int {\n let choices = [1, 2] // 1 段または 2 段上ることを選択できる\n let state = 0 // 0 段目から上り始める\n var res: [Int] = []\n res.append(0) // res[0] に方法の数を記録する\n backtrack(choices: choices, state: state, n: n, res: &amp;res)\n return res[0]\n}\n</code></pre> climbing_stairs_backtrack.js<pre><code>/* バックトラッキング */\nfunction backtrack(choices, state, n, res) {\n // n 段目に到達したら、方法の数を 1 増やす\n if (state === n) res.set(0, res.get(0) + 1);\n // すべての選択肢を走査する\n for (const choice of choices) {\n // 枝刈り:n 段目を越えてはならない\n if (state + choice &gt; n) continue;\n // 試行:選択を行い、状態を更新する\n backtrack(choices, state + choice, n, res);\n // 復元\n }\n}\n\n/* 階段の上り方:バックトラッキング */\nfunction climbingStairsBacktrack(n) {\n const choices = [1, 2]; // 1 段または 2 段上ることを選択できる\n const state = 0; // 0 段目から上り始める\n const res = new Map();\n res.set(0, 0); // res[0] に方法の数を記録する\n backtrack(choices, state, n, res);\n return res.get(0);\n}\n</code></pre> climbing_stairs_backtrack.ts<pre><code>/* バックトラッキング */\nfunction backtrack(\n choices: number[],\n state: number,\n n: number,\n res: Map&lt;0, any&gt;\n): void {\n // n 段目に到達したら、方法の数を 1 増やす\n if (state === n) res.set(0, res.get(0) + 1);\n // すべての選択肢を走査する\n for (const choice of choices) {\n // 枝刈り:n 段目を越えてはならない\n if (state + choice &gt; n) continue;\n // 試行:選択を行い、状態を更新する\n backtrack(choices, state + choice, n, res);\n // 復元\n }\n}\n\n/* 階段の上り方:バックトラッキング */\nfunction climbingStairsBacktrack(n: number): number {\n const choices = [1, 2]; // 1 段または 2 段上ることを選択できる\n const state = 0; // 0 段目から上り始める\n const res = new Map();\n res.set(0, 0); // res[0] に方法の数を記録する\n backtrack(choices, state, n, res);\n return res.get(0);\n}\n</code></pre> climbing_stairs_backtrack.dart<pre><code>/* バックトラッキング */\nvoid backtrack(List&lt;int&gt; choices, int state, int n, List&lt;int&gt; res) {\n // n 段目に到達したら、方法の数を 1 増やす\n if (state == n) {\n res[0]++;\n }\n // すべての選択肢を走査する\n for (int choice in choices) {\n // 枝刈り:n 段目を越えてはならない\n if (state + choice &gt; n) continue;\n // 試行:選択を行い、状態を更新する\n backtrack(choices, state + choice, n, res);\n // 復元\n }\n}\n\n/* 階段の上り方:バックトラッキング */\nint climbingStairsBacktrack(int n) {\n List&lt;int&gt; choices = [1, 2]; // 1 段または 2 段上ることを選択できる\n int state = 0; // 0 段目から上り始める\n List&lt;int&gt; res = [];\n res.add(0); // res[0] に方法の数を記録する\n backtrack(choices, state, n, res);\n return res[0];\n}\n</code></pre> climbing_stairs_backtrack.rs<pre><code>/* バックトラッキング */\nfn backtrack(choices: &amp;[i32], state: i32, n: i32, res: &amp;mut [i32]) {\n // n 段目に到達したら、方法の数を 1 増やす\n if state == n {\n res[0] = res[0] + 1;\n }\n // すべての選択肢を走査する\n for &amp;choice in choices {\n // 枝刈り:n 段目を越えてはならない\n if state + choice &gt; n {\n continue;\n }\n // 試行:選択を行い、状態を更新する\n backtrack(choices, state + choice, n, res);\n // 復元\n }\n}\n\n/* 階段の上り方:バックトラッキング */\nfn climbing_stairs_backtrack(n: usize) -&gt; i32 {\n let choices = vec![1, 2]; // 1 段または 2 段上ることを選択できる\n let state = 0; // 0 段目から上り始める\n let mut res = Vec::new();\n res.push(0); // res[0] に方法の数を記録する\n backtrack(&amp;choices, state, n as i32, &amp;mut res);\n res[0]\n}\n</code></pre> climbing_stairs_backtrack.c<pre><code>/* バックトラッキング */\nvoid backtrack(int *choices, int state, int n, int *res, int len) {\n // n 段目に到達したら、方法の数を 1 増やす\n if (state == n)\n res[0]++;\n // すべての選択肢を走査する\n for (int i = 0; i &lt; len; i++) {\n int choice = choices[i];\n // 枝刈り:n 段目を越えてはならない\n if (state + choice &gt; n)\n continue;\n // 試行:選択を行い、状態を更新する\n backtrack(choices, state + choice, n, res, len);\n // 復元\n }\n}\n\n/* 階段の上り方:バックトラッキング */\nint climbingStairsBacktrack(int n) {\n int choices[2] = {1, 2}; // 1 段または 2 段上ることを選択できる\n int state = 0; // 0 段目から上り始める\n int *res = (int *)malloc(sizeof(int));\n *res = 0; // res[0] に方法の数を記録する\n int len = sizeof(choices) / sizeof(int);\n backtrack(choices, state, n, res, len);\n int result = *res;\n free(res);\n return result;\n}\n</code></pre> climbing_stairs_backtrack.kt<pre><code>/* バックトラッキング */\nfun backtrack(\n choices: MutableList&lt;Int&gt;,\n state: Int,\n n: Int,\n res: MutableList&lt;Int&gt;\n) {\n // n 段目に到達したら、方法の数を 1 増やす\n if (state == n)\n res[0] = res[0] + 1\n // すべての選択肢を走査する\n for (choice in choices) {\n // 枝刈り:n 段目を越えてはならない\n if (state + choice &gt; n) continue\n // 試行:選択を行い、状態を更新する\n backtrack(choices, state + choice, n, res)\n // 復元\n }\n}\n\n/* 階段の上り方:バックトラッキング */\nfun climbingStairsBacktrack(n: Int): Int {\n val choices = mutableListOf(1, 2) // 1 段または 2 段上ることを選択できる\n val state = 0 // 0 段目から上り始める\n val res = mutableListOf&lt;Int&gt;()\n res.add(0) // res[0] に方法の数を記録する\n backtrack(choices, state, n, res)\n return res[0]\n}\n</code></pre> climbing_stairs_backtrack.rb<pre><code>### バックトラッキング ###\ndef backtrack(choices, state, n, res)\n # n 段目に到達したら、方法の数を 1 増やす\n res[0] += 1 if state == n\n # すべての選択肢を走査する\n for choice in choices\n # 枝刈り:n 段目を越えてはならない\n next if state + choice &gt; n\n\n # 試行:選択を行い、状態を更新する\n backtrack(choices, state + choice, n, res)\n end\n # 復元\nend\n\n### 階段の上り方:バックトラッキング ###\ndef climbing_stairs_backtrack(n)\n choices = [1, 2] # 1 段または 2 段上ることを選択できる\n state = 0 # 0 段目から上り始める\n res = [0] # res[0] に方法の数を記録する\n backtrack(choices, state, n, res)\n res.first\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 14 章   動的計画法","14.1   動的計画法への第一歩"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1411-1","level":2,"title":"14.1.1   方法 1:全探索","text":"<p>バックトラッキングでは通常、問題を明示的に分解するのではなく、一連の意思決定として捉えます。試行と枝刈りを通じて、可能な解をすべて探索します。</p> <p>この問題を、分解するという観点から考えてみましょう。\\(i\\) 段目まで上る方法が \\(dp[i]\\) 通りあるとすると、\\(dp[i]\\) が元の問題であり、部分問題には次のものがあります。</p> \\[ dp[i-1], dp[i-2], \\dots, dp[2], dp[1] \\] <p>毎回 \\(1\\) 段か \\(2\\) 段しか上れないので、\\(i\\) 段目にいるなら、その直前にいたのは \\(i - 1\\) 段目か \\(i - 2\\) 段目です。つまり、\\(i\\) 段目には \\(i -1\\) 段目または \\(i - 2\\) 段目からしか来られません。</p> <p>ここから、重要な結論が得られます。\\(i - 1\\) 段目まで上る方法の数と、\\(i - 2\\) 段目まで上る方法の数の和が、\\(i\\) 段目まで上る方法の数になります。式で表すと、次のとおりです。</p> \\[ dp[i] = dp[i-1] + dp[i-2] \\] <p>つまり、階段を上る問題では部分問題の間に漸化式があり、部分問題の解から元の問題の解を組み立てられます。図 14-2 に、この関係を示します。</p> <p></p> <p> 図 14-2   方法の数の漸化式 </p> <p>この漸化式から、全探索による解法が得られます。\\(dp[n]\\) から始め、大きな問題を二つの小さな問題の和へ再帰的に分解し、最小の部分問題 \\(dp[1]\\) と \\(dp[2]\\) に到達したら戻ります。その解は既知で、\\(dp[1] = 1\\)、\\(dp[2] = 2\\) です。つまり、\\(1\\) 段目と \\(2\\) 段目まで上る方法は、それぞれ \\(1\\) 通りと \\(2\\) 通りです。</p> <p>次のコードは、通常のバックトラッキングのコードと同じく深さ優先探索ですが、より簡潔です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dfs.py<pre><code>def dfs(i: int) -&gt; int:\n \"\"\"探索\"\"\"\n # dp[1] と dp[2] は既知なので、その値を返す\n if i == 1 or i == 2:\n return i\n # dp[i] = dp[i-1] + dp[i-2]\n count = dfs(i - 1) + dfs(i - 2)\n return count\n\ndef climbing_stairs_dfs(n: int) -&gt; int:\n \"\"\"階段の上り方:探索\"\"\"\n return dfs(n)\n</code></pre> climbing_stairs_dfs.cpp<pre><code>/* 探索 */\nint dfs(int i) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2)\n return i;\n // dp[i] = dp[i-1] + dp[i-2]\n int count = dfs(i - 1) + dfs(i - 2);\n return count;\n}\n\n/* 階段の上り方:探索 */\nint climbingStairsDFS(int n) {\n return dfs(n);\n}\n</code></pre> climbing_stairs_dfs.java<pre><code>/* 探索 */\nint dfs(int i) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2)\n return i;\n // dp[i] = dp[i-1] + dp[i-2]\n int count = dfs(i - 1) + dfs(i - 2);\n return count;\n}\n\n/* 階段の上り方:探索 */\nint climbingStairsDFS(int n) {\n return dfs(n);\n}\n</code></pre> climbing_stairs_dfs.cs<pre><code>/* 探索 */\nint DFS(int i) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2)\n return i;\n // dp[i] = dp[i-1] + dp[i-2]\n int count = DFS(i - 1) + DFS(i - 2);\n return count;\n}\n\n/* 階段の上り方:探索 */\nint ClimbingStairsDFS(int n) {\n return DFS(n);\n}\n</code></pre> climbing_stairs_dfs.go<pre><code>/* 探索 */\nfunc dfs(i int) int {\n // dp[1] と dp[2] は既知なので、その値を返す\n if i == 1 || i == 2 {\n return i\n }\n // dp[i] = dp[i-1] + dp[i-2]\n count := dfs(i-1) + dfs(i-2)\n return count\n}\n\n/* 階段の上り方:探索 */\nfunc climbingStairsDFS(n int) int {\n return dfs(n)\n}\n</code></pre> climbing_stairs_dfs.swift<pre><code>/* 探索 */\nfunc dfs(i: Int) -&gt; Int {\n // dp[1] と dp[2] は既知なので、その値を返す\n if i == 1 || i == 2 {\n return i\n }\n // dp[i] = dp[i-1] + dp[i-2]\n let count = dfs(i: i - 1) + dfs(i: i - 2)\n return count\n}\n\n/* 階段の上り方:探索 */\nfunc climbingStairsDFS(n: Int) -&gt; Int {\n dfs(i: n)\n}\n</code></pre> climbing_stairs_dfs.js<pre><code>/* 探索 */\nfunction dfs(i) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i === 1 || i === 2) return i;\n // dp[i] = dp[i-1] + dp[i-2]\n const count = dfs(i - 1) + dfs(i - 2);\n return count;\n}\n\n/* 階段の上り方:探索 */\nfunction climbingStairsDFS(n) {\n return dfs(n);\n}\n</code></pre> climbing_stairs_dfs.ts<pre><code>/* 探索 */\nfunction dfs(i: number): number {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i === 1 || i === 2) return i;\n // dp[i] = dp[i-1] + dp[i-2]\n const count = dfs(i - 1) + dfs(i - 2);\n return count;\n}\n\n/* 階段の上り方:探索 */\nfunction climbingStairsDFS(n: number): number {\n return dfs(n);\n}\n</code></pre> climbing_stairs_dfs.dart<pre><code>/* 探索 */\nint dfs(int i) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2) return i;\n // dp[i] = dp[i-1] + dp[i-2]\n int count = dfs(i - 1) + dfs(i - 2);\n return count;\n}\n\n/* 階段の上り方:探索 */\nint climbingStairsDFS(int n) {\n return dfs(n);\n}\n</code></pre> climbing_stairs_dfs.rs<pre><code>/* 探索 */\nfn dfs(i: usize) -&gt; i32 {\n // dp[1] と dp[2] は既知なので、その値を返す\n if i == 1 || i == 2 {\n return i as i32;\n }\n // dp[i] = dp[i-1] + dp[i-2]\n let count = dfs(i - 1) + dfs(i - 2);\n count\n}\n\n/* 階段の上り方:探索 */\nfn climbing_stairs_dfs(n: usize) -&gt; i32 {\n dfs(n)\n}\n</code></pre> climbing_stairs_dfs.c<pre><code>/* 探索 */\nint dfs(int i) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2)\n return i;\n // dp[i] = dp[i-1] + dp[i-2]\n int count = dfs(i - 1) + dfs(i - 2);\n return count;\n}\n\n/* 階段の上り方:探索 */\nint climbingStairsDFS(int n) {\n return dfs(n);\n}\n</code></pre> climbing_stairs_dfs.kt<pre><code>/* 探索 */\nfun dfs(i: Int): Int {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2) return i\n // dp[i] = dp[i-1] + dp[i-2]\n val count = dfs(i - 1) + dfs(i - 2)\n return count\n}\n\n/* 階段の上り方:探索 */\nfun climbingStairsDFS(n: Int): Int {\n return dfs(n)\n}\n</code></pre> climbing_stairs_dfs.rb<pre><code>### 探索 ###\ndef dfs(i)\n # dp[1] と dp[2] は既知なので、その値を返す\n return i if i == 1 || i == 2\n # dp[i] = dp[i-1] + dp[i-2]\n dfs(i - 1) + dfs(i - 2)\nend\n\n### 階段の上り方:探索 ###\ndef climbing_stairs_dfs(n)\n dfs(n)\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-3 に、全探索でできる再帰木を示します。問題 \\(dp[n]\\) の再帰木の深さは \\(n\\) で、時間計算量は \\(O(2^n)\\) です。指数のオーダーは爆発的に増えるため、大きな \\(n\\) を入力すると、長時間待つことになります。</p> <p></p> <p> 図 14-3   階段を上る問題の再帰木 </p> <p>図 14-3 から、指数のオーダーの時間計算量は「部分問題の重複」によって生じていることがわかります。たとえば \\(dp[9]\\) は \\(dp[8]\\) と \\(dp[7]\\) に分解され、\\(dp[8]\\) は \\(dp[7]\\) と \\(dp[6]\\) に分解されます。どちらにも部分問題 \\(dp[7]\\) が含まれます。</p> <p>さらに、その部分問題にも小さな部分問題の重複が含まれ、同じことが次々に繰り返されます。計算資源の大部分が、こうした重複した部分問題に費やされています。</p>","path":["第 14 章   動的計画法","14.1   動的計画法への第一歩"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1412-2","level":2,"title":"14.1.2   方法 2:メモ化探索","text":"<p>効率を高めるには、重複する部分問題をそれぞれ 1 回だけ計算したいところです。そこで配列 <code>mem</code> に各部分問題の解を記録し、探索中に重複する部分問題を枝刈りします。</p> <ol> <li>\\(dp[i]\\) を初めて計算したら、後で使えるよう <code>mem[i]</code> に記録します。</li> <li>再び \\(dp[i]\\) が必要になったら、<code>mem[i]</code> から直接取得し、同じ部分問題の再計算を避けます。</li> </ol> <p>コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dfs_mem.py<pre><code>def dfs(i: int, mem: list[int]) -&gt; int:\n \"\"\"メモ化探索\"\"\"\n # dp[1] と dp[2] は既知なので、その値を返す\n if i == 1 or i == 2:\n return i\n # dp[i] が記録済みなら、その値をそのまま返す\n if mem[i] != -1:\n return mem[i]\n # dp[i] = dp[i-1] + dp[i-2]\n count = dfs(i - 1, mem) + dfs(i - 2, mem)\n # dp[i] を記録する\n mem[i] = count\n return count\n\ndef climbing_stairs_dfs_mem(n: int) -&gt; int:\n \"\"\"階段の上り方:メモ化探索\"\"\"\n # mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n mem = [-1] * (n + 1)\n return dfs(n, mem)\n</code></pre> climbing_stairs_dfs_mem.cpp<pre><code>/* メモ化探索 */\nint dfs(int i, vector&lt;int&gt; &amp;mem) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2)\n return i;\n // dp[i] が記録済みなら、その値をそのまま返す\n if (mem[i] != -1)\n return mem[i];\n // dp[i] = dp[i-1] + dp[i-2]\n int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n // dp[i] を記録する\n mem[i] = count;\n return count;\n}\n\n/* 階段の上り方:メモ化探索 */\nint climbingStairsDFSMem(int n) {\n // mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n vector&lt;int&gt; mem(n + 1, -1);\n return dfs(n, mem);\n}\n</code></pre> climbing_stairs_dfs_mem.java<pre><code>/* メモ化探索 */\nint dfs(int i, int[] mem) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2)\n return i;\n // dp[i] が記録済みなら、その値をそのまま返す\n if (mem[i] != -1)\n return mem[i];\n // dp[i] = dp[i-1] + dp[i-2]\n int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n // dp[i] を記録する\n mem[i] = count;\n return count;\n}\n\n/* 階段の上り方:メモ化探索 */\nint climbingStairsDFSMem(int n) {\n // mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n int[] mem = new int[n + 1];\n Arrays.fill(mem, -1);\n return dfs(n, mem);\n}\n</code></pre> climbing_stairs_dfs_mem.cs<pre><code>/* メモ化探索 */\nint DFS(int i, int[] mem) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2)\n return i;\n // dp[i] が記録済みなら、その値をそのまま返す\n if (mem[i] != -1)\n return mem[i];\n // dp[i] = dp[i-1] + dp[i-2]\n int count = DFS(i - 1, mem) + DFS(i - 2, mem);\n // dp[i] を記録する\n mem[i] = count;\n return count;\n}\n\n/* 階段の上り方:メモ化探索 */\nint ClimbingStairsDFSMem(int n) {\n // mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n int[] mem = new int[n + 1];\n Array.Fill(mem, -1);\n return DFS(n, mem);\n}\n</code></pre> climbing_stairs_dfs_mem.go<pre><code>/* メモ化探索 */\nfunc dfsMem(i int, mem []int) int {\n // dp[1] と dp[2] は既知なので、その値を返す\n if i == 1 || i == 2 {\n return i\n }\n // dp[i] が記録済みなら、その値をそのまま返す\n if mem[i] != -1 {\n return mem[i]\n }\n // dp[i] = dp[i-1] + dp[i-2]\n count := dfsMem(i-1, mem) + dfsMem(i-2, mem)\n // dp[i] を記録する\n mem[i] = count\n return count\n}\n\n/* 階段の上り方:メモ化探索 */\nfunc climbingStairsDFSMem(n int) int {\n // mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n mem := make([]int, n+1)\n for i := range mem {\n mem[i] = -1\n }\n return dfsMem(n, mem)\n}\n</code></pre> climbing_stairs_dfs_mem.swift<pre><code>/* メモ化探索 */\nfunc dfs(i: Int, mem: inout [Int]) -&gt; Int {\n // dp[1] と dp[2] は既知なので、その値を返す\n if i == 1 || i == 2 {\n return i\n }\n // dp[i] が記録済みなら、その値をそのまま返す\n if mem[i] != -1 {\n return mem[i]\n }\n // dp[i] = dp[i-1] + dp[i-2]\n let count = dfs(i: i - 1, mem: &amp;mem) + dfs(i: i - 2, mem: &amp;mem)\n // dp[i] を記録する\n mem[i] = count\n return count\n}\n\n/* 階段の上り方:メモ化探索 */\nfunc climbingStairsDFSMem(n: Int) -&gt; Int {\n // mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n var mem = Array(repeating: -1, count: n + 1)\n return dfs(i: n, mem: &amp;mem)\n}\n</code></pre> climbing_stairs_dfs_mem.js<pre><code>/* メモ化探索 */\nfunction dfs(i, mem) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i === 1 || i === 2) return i;\n // dp[i] が記録済みなら、その値をそのまま返す\n if (mem[i] != -1) return mem[i];\n // dp[i] = dp[i-1] + dp[i-2]\n const count = dfs(i - 1, mem) + dfs(i - 2, mem);\n // dp[i] を記録する\n mem[i] = count;\n return count;\n}\n\n/* 階段の上り方:メモ化探索 */\nfunction climbingStairsDFSMem(n) {\n // mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n const mem = new Array(n + 1).fill(-1);\n return dfs(n, mem);\n}\n</code></pre> climbing_stairs_dfs_mem.ts<pre><code>/* メモ化探索 */\nfunction dfs(i: number, mem: number[]): number {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i === 1 || i === 2) return i;\n // dp[i] が記録済みなら、その値をそのまま返す\n if (mem[i] != -1) return mem[i];\n // dp[i] = dp[i-1] + dp[i-2]\n const count = dfs(i - 1, mem) + dfs(i - 2, mem);\n // dp[i] を記録する\n mem[i] = count;\n return count;\n}\n\n/* 階段の上り方:メモ化探索 */\nfunction climbingStairsDFSMem(n: number): number {\n // mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n const mem = new Array(n + 1).fill(-1);\n return dfs(n, mem);\n}\n</code></pre> climbing_stairs_dfs_mem.dart<pre><code>/* メモ化探索 */\nint dfs(int i, List&lt;int&gt; mem) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2) return i;\n // dp[i] が記録済みなら、その値をそのまま返す\n if (mem[i] != -1) return mem[i];\n // dp[i] = dp[i-1] + dp[i-2]\n int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n // dp[i] を記録する\n mem[i] = count;\n return count;\n}\n\n/* 階段の上り方:メモ化探索 */\nint climbingStairsDFSMem(int n) {\n // mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n List&lt;int&gt; mem = List.filled(n + 1, -1);\n return dfs(n, mem);\n}\n</code></pre> climbing_stairs_dfs_mem.rs<pre><code>/* メモ化探索 */\nfn dfs(i: usize, mem: &amp;mut [i32]) -&gt; i32 {\n // dp[1] と dp[2] は既知なので、その値を返す\n if i == 1 || i == 2 {\n return i as i32;\n }\n // dp[i] が記録済みなら、その値をそのまま返す\n if mem[i] != -1 {\n return mem[i];\n }\n // dp[i] = dp[i-1] + dp[i-2]\n let count = dfs(i - 1, mem) + dfs(i - 2, mem);\n // dp[i] を記録する\n mem[i] = count;\n count\n}\n\n/* 階段の上り方:メモ化探索 */\nfn climbing_stairs_dfs_mem(n: usize) -&gt; i32 {\n // mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n let mut mem = vec![-1; n + 1];\n dfs(n, &amp;mut mem)\n}\n</code></pre> climbing_stairs_dfs_mem.c<pre><code>/* メモ化探索 */\nint dfs(int i, int *mem) {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2)\n return i;\n // dp[i] が記録済みなら、その値をそのまま返す\n if (mem[i] != -1)\n return mem[i];\n // dp[i] = dp[i-1] + dp[i-2]\n int count = dfs(i - 1, mem) + dfs(i - 2, mem);\n // dp[i] を記録する\n mem[i] = count;\n return count;\n}\n\n/* 階段の上り方:メモ化探索 */\nint climbingStairsDFSMem(int n) {\n // mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n int *mem = (int *)malloc((n + 1) * sizeof(int));\n for (int i = 0; i &lt;= n; i++) {\n mem[i] = -1;\n }\n int result = dfs(n, mem);\n free(mem);\n return result;\n}\n</code></pre> climbing_stairs_dfs_mem.kt<pre><code>/* メモ化探索 */\nfun dfs(i: Int, mem: IntArray): Int {\n // dp[1] と dp[2] は既知なので、その値を返す\n if (i == 1 || i == 2) return i\n // dp[i] が記録済みなら、その値をそのまま返す\n if (mem[i] != -1) return mem[i]\n // dp[i] = dp[i-1] + dp[i-2]\n val count = dfs(i - 1, mem) + dfs(i - 2, mem)\n // dp[i] を記録する\n mem[i] = count\n return count\n}\n\n/* 階段の上り方:メモ化探索 */\nfun climbingStairsDFSMem(n: Int): Int {\n // mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n val mem = IntArray(n + 1)\n mem.fill(-1)\n return dfs(n, mem)\n}\n</code></pre> climbing_stairs_dfs_mem.rb<pre><code>### メモ化探索 ###\ndef dfs(i, mem)\n # dp[1] と dp[2] は既知なので、その値を返す\n return i if i == 1 || i == 2\n # dp[i] が記録済みなら、その値をそのまま返す\n return mem[i] if mem[i] != -1\n\n # dp[i] = dp[i-1] + dp[i-2]\n count = dfs(i - 1, mem) + dfs(i - 2, mem)\n # dp[i] を記録する\n mem[i] = count\nend\n\n### 階段の上り方:メモ化探索 ###\ndef climbing_stairs_dfs_mem(n)\n # mem[i] は i 段目まで上る方法の総数を記録し、-1 は未記録を表す\n mem = Array.new(n + 1, -1)\n dfs(n, mem)\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-4 のように、メモ化すると、重複する部分問題はすべて 1 回の計算で済み、時間計算量は \\(O(n)\\) に改善されます。これは大きな進歩です。</p> <p></p> <p> 図 14-4   メモ化探索の再帰木 </p>","path":["第 14 章   動的計画法","14.1   動的計画法への第一歩"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1413-3","level":2,"title":"14.1.3   方法 3:動的計画法","text":"<p>メモ化探索は「上から下へ」進む方法です。元の問題(根ノード)から始め、大きな部分問題を小さな部分問題へ再帰的に分解し、解が既知の最小の部分問題(葉ノード)まで進みます。その後、戻りながら各段階の解を集め、元の問題の解を組み立てます。</p> <p>対して、動的計画法は「下から上へ」進む方法です。最小の部分問題の解から始め、反復によってより大きな部分問題の解を組み立て、元の問題の解まで到達します。</p> <p>動的計画法には引き返す過程がないため、再帰は不要で、ループによる反復だけで実装できます。次のコードでは、部分問題の解を保存する配列 <code>dp</code> を初期化します。これは、メモ化探索の配列 <code>mem</code> と同じ記録の役割を果たします。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dp.py<pre><code>def climbing_stairs_dp(n: int) -&gt; int:\n \"\"\"階段の上り方:動的計画法\"\"\"\n if n == 1 or n == 2:\n return n\n # 部分問題の解を格納する dp テーブルを初期化する\n dp = [0] * (n + 1)\n # 初期状態:最小の部分問題の解を設定する\n dp[1], dp[2] = 1, 2\n # 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i in range(3, n + 1):\n dp[i] = dp[i - 1] + dp[i - 2]\n return dp[n]\n</code></pre> climbing_stairs_dp.cpp<pre><code>/* 階段の上り方:動的計画法 */\nint climbingStairsDP(int n) {\n if (n == 1 || n == 2)\n return n;\n // 部分問題の解を格納する dp テーブルを初期化する\n vector&lt;int&gt; dp(n + 1);\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = 1;\n dp[2] = 2;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i] = dp[i - 1] + dp[i - 2];\n }\n return dp[n];\n}\n</code></pre> climbing_stairs_dp.java<pre><code>/* 階段の上り方:動的計画法 */\nint climbingStairsDP(int n) {\n if (n == 1 || n == 2)\n return n;\n // 部分問題の解を格納する dp テーブルを初期化する\n int[] dp = new int[n + 1];\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = 1;\n dp[2] = 2;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i] = dp[i - 1] + dp[i - 2];\n }\n return dp[n];\n}\n</code></pre> climbing_stairs_dp.cs<pre><code>/* 階段の上り方:動的計画法 */\nint ClimbingStairsDP(int n) {\n if (n == 1 || n == 2)\n return n;\n // 部分問題の解を格納する dp テーブルを初期化する\n int[] dp = new int[n + 1];\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = 1;\n dp[2] = 2;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i] = dp[i - 1] + dp[i - 2];\n }\n return dp[n];\n}\n</code></pre> climbing_stairs_dp.go<pre><code>/* 階段の上り方:動的計画法 */\nfunc climbingStairsDP(n int) int {\n if n == 1 || n == 2 {\n return n\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n dp := make([]int, n+1)\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = 1\n dp[2] = 2\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i := 3; i &lt;= n; i++ {\n dp[i] = dp[i-1] + dp[i-2]\n }\n return dp[n]\n}\n</code></pre> climbing_stairs_dp.swift<pre><code>/* 階段の上り方:動的計画法 */\nfunc climbingStairsDP(n: Int) -&gt; Int {\n if n == 1 || n == 2 {\n return n\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n var dp = Array(repeating: 0, count: n + 1)\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = 1\n dp[2] = 2\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i in 3 ... n {\n dp[i] = dp[i - 1] + dp[i - 2]\n }\n return dp[n]\n}\n</code></pre> climbing_stairs_dp.js<pre><code>/* 階段の上り方:動的計画法 */\nfunction climbingStairsDP(n) {\n if (n === 1 || n === 2) return n;\n // 部分問題の解を格納する dp テーブルを初期化する\n const dp = new Array(n + 1).fill(-1);\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = 1;\n dp[2] = 2;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (let i = 3; i &lt;= n; i++) {\n dp[i] = dp[i - 1] + dp[i - 2];\n }\n return dp[n];\n}\n</code></pre> climbing_stairs_dp.ts<pre><code>/* 階段の上り方:動的計画法 */\nfunction climbingStairsDP(n: number): number {\n if (n === 1 || n === 2) return n;\n // 部分問題の解を格納する dp テーブルを初期化する\n const dp = new Array(n + 1).fill(-1);\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = 1;\n dp[2] = 2;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (let i = 3; i &lt;= n; i++) {\n dp[i] = dp[i - 1] + dp[i - 2];\n }\n return dp[n];\n}\n</code></pre> climbing_stairs_dp.dart<pre><code>/* 階段の上り方:動的計画法 */\nint climbingStairsDP(int n) {\n if (n == 1 || n == 2) return n;\n // 部分問題の解を格納する dp テーブルを初期化する\n List&lt;int&gt; dp = List.filled(n + 1, 0);\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = 1;\n dp[2] = 2;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i] = dp[i - 1] + dp[i - 2];\n }\n return dp[n];\n}\n</code></pre> climbing_stairs_dp.rs<pre><code>/* 階段の上り方:動的計画法 */\nfn climbing_stairs_dp(n: usize) -&gt; i32 {\n // dp[1] と dp[2] は既知なので、その値を返す\n if n == 1 || n == 2 {\n return n as i32;\n }\n // 部分問題の解を格納する dp テーブルを初期化する\n let mut dp = vec![-1; n + 1];\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = 1;\n dp[2] = 2;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i in 3..=n {\n dp[i] = dp[i - 1] + dp[i - 2];\n }\n dp[n]\n}\n</code></pre> climbing_stairs_dp.c<pre><code>/* 階段の上り方:動的計画法 */\nint climbingStairsDP(int n) {\n if (n == 1 || n == 2)\n return n;\n // 部分問題の解を格納する dp テーブルを初期化する\n int *dp = (int *)malloc((n + 1) * sizeof(int));\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = 1;\n dp[2] = 2;\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (int i = 3; i &lt;= n; i++) {\n dp[i] = dp[i - 1] + dp[i - 2];\n }\n int result = dp[n];\n free(dp);\n return result;\n}\n</code></pre> climbing_stairs_dp.kt<pre><code>/* 階段の上り方:動的計画法 */\nfun climbingStairsDP(n: Int): Int {\n if (n == 1 || n == 2) return n\n // 部分問題の解を格納する dp テーブルを初期化する\n val dp = IntArray(n + 1)\n // 初期状態:最小の部分問題の解を設定する\n dp[1] = 1\n dp[2] = 2\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for (i in 3..n) {\n dp[i] = dp[i - 1] + dp[i - 2]\n }\n return dp[n]\n}\n</code></pre> climbing_stairs_dp.rb<pre><code>### 階段の上り方:動的計画法 ###\ndef climbing_stairs_dp(n)\n return n if n == 1 || n == 2\n\n # 部分問題の解を格納する dp テーブルを初期化する\n dp = Array.new(n + 1, 0)\n # 初期状態:最小の部分問題の解を設定する\n dp[1], dp[2] = 1, 2\n # 状態遷移:小さな部分問題から順に大きな部分問題を解く\n (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] }\n\n dp[n]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-5 に、上のコードの実行過程を示します。</p> <p></p> <p> 図 14-5   階段を上る問題を動的計画法で解く流れ </p> <p>バックトラッキングと同様に、動的計画法でも「状態」を使って問題を解く途中の段階を表します。各状態は、一つの部分問題と、それに対応する局所的な最適解に対応します。たとえば階段を上る問題では、現在いる段数 \\(i\\) を状態とします。</p> <p>ここまでの内容から、動的計画法の主な用語をまとめられます。</p> <ul> <li>配列 <code>dp</code> を dp テーブルと呼び、\\(dp[i]\\) は状態 \\(i\\) に対応する部分問題の解を表します。</li> <li>最小の部分問題に対応する状態(\\(1\\) 段目と \\(2\\) 段目)を初期状態と呼びます。</li> <li>漸化式 \\(dp[i] = dp[i-1] + dp[i-2]\\) を状態遷移式と呼びます。</li> </ul>","path":["第 14 章   動的計画法","14.1   動的計画法への第一歩"],"tags":[]},{"location":"chapter_dynamic_programming/intro_to_dynamic_programming/#1414","level":2,"title":"14.1.4   空間の最適化","text":"<p>注意深く見ると、\\(dp[i]\\) が依存するのは \\(dp[i-1]\\) と \\(dp[i-2]\\) だけなので、配列 <code>dp</code> にすべての部分問題の解を保存する必要はありません。二つの変数を順に更新していけば十分です。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby climbing_stairs_dp.py<pre><code>def climbing_stairs_dp_comp(n: int) -&gt; int:\n \"\"\"階段の上り方:空間を最適化した動的計画法\"\"\"\n if n == 1 or n == 2:\n return n\n a, b = 1, 2\n for _ in range(3, n + 1):\n a, b = b, a + b\n return b\n</code></pre> climbing_stairs_dp.cpp<pre><code>/* 階段の上り方:空間を最適化した動的計画法 */\nint climbingStairsDPComp(int n) {\n if (n == 1 || n == 2)\n return n;\n int a = 1, b = 2;\n for (int i = 3; i &lt;= n; i++) {\n int tmp = b;\n b = a + b;\n a = tmp;\n }\n return b;\n}\n</code></pre> climbing_stairs_dp.java<pre><code>/* 階段の上り方:空間を最適化した動的計画法 */\nint climbingStairsDPComp(int n) {\n if (n == 1 || n == 2)\n return n;\n int a = 1, b = 2;\n for (int i = 3; i &lt;= n; i++) {\n int tmp = b;\n b = a + b;\n a = tmp;\n }\n return b;\n}\n</code></pre> climbing_stairs_dp.cs<pre><code>/* 階段の上り方:空間を最適化した動的計画法 */\nint ClimbingStairsDPComp(int n) {\n if (n == 1 || n == 2)\n return n;\n int a = 1, b = 2;\n for (int i = 3; i &lt;= n; i++) {\n int tmp = b;\n b = a + b;\n a = tmp;\n }\n return b;\n}\n</code></pre> climbing_stairs_dp.go<pre><code>/* 階段の上り方:空間を最適化した動的計画法 */\nfunc climbingStairsDPComp(n int) int {\n if n == 1 || n == 2 {\n return n\n }\n a, b := 1, 2\n // 状態遷移:小さな部分問題から順に大きな部分問題を解く\n for i := 3; i &lt;= n; i++ {\n a, b = b, a+b\n }\n return b\n}\n</code></pre> climbing_stairs_dp.swift<pre><code>/* 階段の上り方:空間を最適化した動的計画法 */\nfunc climbingStairsDPComp(n: Int) -&gt; Int {\n if n == 1 || n == 2 {\n return n\n }\n var a = 1\n var b = 2\n for _ in 3 ... n {\n (a, b) = (b, a + b)\n }\n return b\n}\n</code></pre> climbing_stairs_dp.js<pre><code>/* 階段の上り方:空間を最適化した動的計画法 */\nfunction climbingStairsDPComp(n) {\n if (n === 1 || n === 2) return n;\n let a = 1,\n b = 2;\n for (let i = 3; i &lt;= n; i++) {\n const tmp = b;\n b = a + b;\n a = tmp;\n }\n return b;\n}\n</code></pre> climbing_stairs_dp.ts<pre><code>/* 階段の上り方:空間を最適化した動的計画法 */\nfunction climbingStairsDPComp(n: number): number {\n if (n === 1 || n === 2) return n;\n let a = 1,\n b = 2;\n for (let i = 3; i &lt;= n; i++) {\n const tmp = b;\n b = a + b;\n a = tmp;\n }\n return b;\n}\n</code></pre> climbing_stairs_dp.dart<pre><code>/* 階段の上り方:空間を最適化した動的計画法 */\nint climbingStairsDPComp(int n) {\n if (n == 1 || n == 2) return n;\n int a = 1, b = 2;\n for (int i = 3; i &lt;= n; i++) {\n int tmp = b;\n b = a + b;\n a = tmp;\n }\n return b;\n}\n</code></pre> climbing_stairs_dp.rs<pre><code>/* 階段の上り方:空間を最適化した動的計画法 */\nfn climbing_stairs_dp_comp(n: usize) -&gt; i32 {\n if n == 1 || n == 2 {\n return n as i32;\n }\n let (mut a, mut b) = (1, 2);\n for _ in 3..=n {\n let tmp = b;\n b = a + b;\n a = tmp;\n }\n b\n}\n</code></pre> climbing_stairs_dp.c<pre><code>/* 階段の上り方:空間を最適化した動的計画法 */\nint climbingStairsDPComp(int n) {\n if (n == 1 || n == 2)\n return n;\n int a = 1, b = 2;\n for (int i = 3; i &lt;= n; i++) {\n int tmp = b;\n b = a + b;\n a = tmp;\n }\n return b;\n}\n</code></pre> climbing_stairs_dp.kt<pre><code>/* 階段の上り方:空間を最適化した動的計画法 */\nfun climbingStairsDPComp(n: Int): Int {\n if (n == 1 || n == 2) return n\n var a = 1\n var b = 2\n for (i in 3..n) {\n val temp = b\n b += a\n a = temp\n }\n return b\n}\n</code></pre> climbing_stairs_dp.rb<pre><code>### 階段の上り方:空間を最適化した動的計画法 ###\ndef climbing_stairs_dp_comp(n)\n return n if n == 1 || n == 2\n\n a, b = 1, 2\n (3...(n + 1)).each { a, b = b, a + b }\n\n b\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>上のコードでは配列 <code>dp</code> が不要になったため、空間計算量は \\(O(n)\\) から \\(O(1)\\) に下がります。</p> <p>動的計画法では、現在の状態が直前の限られた数の状態だけに依存することがよくあります。その場合、必要な状態だけを保持し、「次元を減らす」ことでメモリを節約できます。この空間最適化の技法を「ローリング変数」または「ローリング配列」と呼びます。</p>","path":["第 14 章   動的計画法","14.1   動的計画法への第一歩"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/","level":1,"title":"14.4   0-1 ナップサック問題","text":"<p>ナップサック問題は動的計画法の入門に適しており、最もよく登場する問題の形式です。0-1 ナップサック問題、個数制限なしナップサック問題、個数制限付きナップサック問題など、多くの変種があります。</p> <p>この節では、まず最も一般的な 0-1 ナップサック問題を解きます。</p> <p>問題</p> <p>\\(n\\) 個の品物があり、\\(i\\) 番目の重さは \\(wgt[i-1]\\)、価値は \\(val[i-1]\\) です。容量 \\(cap\\) のナップサックがあり、各品物は 1 回だけ選べます。容量の制限内で入れられる品物の最大価値を求めてください。</p> <p>図 14-17 のように、品物の番号 \\(i\\) は \\(1\\) から始まり、配列のインデックスは \\(0\\) から始まるので、品物 \\(i\\) の重さと価値は \\(wgt[i-1]\\) と \\(val[i-1]\\) に対応します。</p> <p></p> <p> 図 14-17   0-1 ナップサック問題のデータ例 </p> <p>0-1 ナップサック問題は、\\(n\\) 回の選択からなる過程と捉えられます。各品物について、入れないか入れるかの二つを選ぶため、決定木モデルに当てはまります。</p> <p>目標が「容量の制限内で入れられる品物の最大価値」を求めることなので、動的計画法の問題である可能性が高いと考えられます。</p> <p>第 1 段階:各回の選択を考えて状態を定義し、\\(dp\\) テーブルを作る</p> <p>各品物を入れなければ容量は変わらず、入れれば容量が減ります。そこで状態を、現在の品物の番号 \\(i\\) とナップサックの容量 \\(c\\) からなる \\([i, c]\\) と定義します。</p> <p>状態 \\([i, c]\\) に対応する部分問題は、最初の \\(i\\) 個の品物を使い、容量 \\(c\\) のナップサックで得られる最大価値で、その解を \\(dp[i, c]\\) とします。</p> <p>求めるのは \\(dp[n, cap]\\) なので、\\((n+1) \\times (cap+1)\\) の二次元の \\(dp\\) テーブルが必要です。</p> <p>第 2 段階:最適部分構造を見つけ、状態遷移式を導く</p> <p>品物 \\(i\\) をどうするか決めると、残るのは最初の \\(i-1\\) 個に関する部分問題です。次の二つの場合に分かれます。</p> <ul> <li>品物 \\(i\\) を入れない:容量は変わらず、状態は \\([i-1, c]\\) になります。</li> <li>品物 \\(i\\) を入れる:容量は \\(wgt[i-1]\\) 減り、価値は \\(val[i-1]\\) 増え、状態は \\([i-1, c-wgt[i-1]]\\) になります。</li> </ul> <p>ここから最適部分構造がわかります。最大価値 \\(dp[i, c]\\) は、品物 \\(i\\) を入れない場合と品物 \\(i\\) を入れる場合のうち、大きい方の価値です。したがって、状態遷移式は次のようになります。</p> \\[ dp[i, c] = \\max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) \\] <p>現在の品物の重さ \\(wgt[i - 1]\\) が残り容量 \\(c\\) を超える場合は、入れない方しか選べないことに注意してください。</p> <p>第 3 段階:境界条件と状態遷移の順序を決める</p> <p>品物がない場合、または容量が \\(0\\) の場合、最大価値は \\(0\\) です。つまり、先頭列 \\(dp[i, 0]\\) と先頭行 \\(dp[0, c]\\) はすべて \\(0\\) です。</p> <p>現在の状態 \\([i, c]\\) は、上の状態 \\([i-1, c]\\) と左上の状態 \\([i-1, c-wgt[i-1]]\\) から遷移するため、二重ループで \\(dp\\) テーブル全体を順方向に走査すれば十分です。</p> <p>以上の分析に基づき、全探索、メモ化探索、動的計画法を順に実装します。</p>","path":["第 14 章   動的計画法","14.4   0-1 ナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#1-1","level":3,"title":"1.   方法 1:全探索","text":"<p>探索のコードには、次の要素があります。</p> <ul> <li>再帰の引数:状態 \\([i, c]\\) です。</li> <li>戻り値:部分問題の解 \\(dp[i, c]\\) です。</li> <li>終了条件:品物の番号が範囲外の \\(i = 0\\) になった場合、または残り容量が \\(0\\) になった場合は、再帰を終了して価値 \\(0\\) を返します。</li> <li>枝刈り:現在の品物の重さが残り容量を超える場合は、入れない方だけを選びます。</li> </ul> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py<pre><code>def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -&gt; int:\n \"\"\"0-1 ナップサック問題:全探索\"\"\"\n # すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if i == 0 or c == 0:\n return 0\n # ナップサックの容量を超える場合、入れない選択しかできない\n if wgt[i - 1] &gt; c:\n return knapsack_dfs(wgt, val, i - 1, c)\n # 品物 i を入れない場合と入れる場合の最大価値を計算する\n no = knapsack_dfs(wgt, val, i - 1, c)\n yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]\n # 2 通りの選択のうち、価値が大きいほうを返す\n return max(no, yes)\n</code></pre> knapsack.cpp<pre><code>/* 0-1 ナップサック問題:全探索 */\nint knapsackDFS(vector&lt;int&gt; &amp;wgt, vector&lt;int&gt; &amp;val, int i, int c) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0;\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFS(wgt, val, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n int no = knapsackDFS(wgt, val, i - 1, c);\n int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを返す\n return max(no, yes);\n}\n</code></pre> knapsack.java<pre><code>/* 0-1 ナップサック問題:全探索 */\nint knapsackDFS(int[] wgt, int[] val, int i, int c) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0;\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFS(wgt, val, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n int no = knapsackDFS(wgt, val, i - 1, c);\n int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを返す\n return Math.max(no, yes);\n}\n</code></pre> knapsack.cs<pre><code>/* 0-1 ナップサック問題:全探索 */\nint KnapsackDFS(int[] weight, int[] val, int i, int c) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0;\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (weight[i - 1] &gt; c) {\n return KnapsackDFS(weight, val, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n int no = KnapsackDFS(weight, val, i - 1, c);\n int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを返す\n return Math.Max(no, yes);\n}\n</code></pre> knapsack.go<pre><code>/* 0-1 ナップサック問題:全探索 */\nfunc knapsackDFS(wgt, val []int, i, c int) int {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if i == 0 || c == 0 {\n return 0\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if wgt[i-1] &gt; c {\n return knapsackDFS(wgt, val, i-1, c)\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n no := knapsackDFS(wgt, val, i-1, c)\n yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1]\n // 2 通りの選択のうち、価値が大きいほうを返す\n return int(math.Max(float64(no), float64(yes)))\n}\n</code></pre> knapsack.swift<pre><code>/* 0-1 ナップサック問題:全探索 */\nfunc knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -&gt; Int {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if i == 0 || c == 0 {\n return 0\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if wgt[i - 1] &gt; c {\n return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c)\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c)\n let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1]\n // 2 通りの選択のうち、価値が大きいほうを返す\n return max(no, yes)\n}\n</code></pre> knapsack.js<pre><code>/* 0-1 ナップサック問題:全探索 */\nfunction knapsackDFS(wgt, val, i, c) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i === 0 || c === 0) {\n return 0;\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFS(wgt, val, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n const no = knapsackDFS(wgt, val, i - 1, c);\n const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを返す\n return Math.max(no, yes);\n}\n</code></pre> knapsack.ts<pre><code>/* 0-1 ナップサック問題:全探索 */\nfunction knapsackDFS(\n wgt: Array&lt;number&gt;,\n val: Array&lt;number&gt;,\n i: number,\n c: number\n): number {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i === 0 || c === 0) {\n return 0;\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFS(wgt, val, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n const no = knapsackDFS(wgt, val, i - 1, c);\n const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを返す\n return Math.max(no, yes);\n}\n</code></pre> knapsack.dart<pre><code>/* 0-1 ナップサック問題:全探索 */\nint knapsackDFS(List&lt;int&gt; wgt, List&lt;int&gt; val, int i, int c) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0;\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFS(wgt, val, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n int no = knapsackDFS(wgt, val, i - 1, c);\n int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを返す\n return max(no, yes);\n}\n</code></pre> knapsack.rs<pre><code>/* 0-1 ナップサック問題:全探索 */\nfn knapsack_dfs(wgt: &amp;[i32], val: &amp;[i32], i: usize, c: usize) -&gt; i32 {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if i == 0 || c == 0 {\n return 0;\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if wgt[i - 1] &gt; c as i32 {\n return knapsack_dfs(wgt, val, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n let no = knapsack_dfs(wgt, val, i - 1, c);\n let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを返す\n std::cmp::max(no, yes)\n}\n</code></pre> knapsack.c<pre><code>/* 0-1 ナップサック問題:全探索 */\nint knapsackDFS(int wgt[], int val[], int i, int c) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0;\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFS(wgt, val, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n int no = knapsackDFS(wgt, val, i - 1, c);\n int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを返す\n return myMax(no, yes);\n}\n</code></pre> knapsack.kt<pre><code>/* 0-1 ナップサック問題:全探索 */\nfun knapsackDFS(\n wgt: IntArray,\n _val: IntArray,\n i: Int,\n c: Int\n): Int {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFS(wgt, _val, i - 1, c)\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n val no = knapsackDFS(wgt, _val, i - 1, c)\n val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1]\n // 2 通りの選択のうち、価値が大きいほうを返す\n return max(no, yes)\n}\n</code></pre> knapsack.rb<pre><code>### 0-1 ナップサック問題:全探索 ###\ndef knapsack_dfs(wgt, val, i, c)\n # すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n return 0 if i == 0 || c == 0\n # ナップサックの容量を超える場合、入れない選択しかできない\n return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] &gt; c\n # 品物 i を入れない場合と入れる場合の最大価値を計算する\n no = knapsack_dfs(wgt, val, i - 1, c)\n yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]\n # 2 通りの選択のうち、価値が大きいほうを返す\n [no, yes].max\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-18 のように、各品物から「選ばない」「選ぶ」の二つの探索分岐が生じるため、時間計算量は \\(O(2^n)\\) です。</p> <p>再帰木には、\\(dp[1, 10]\\) などの重複した部分問題があります。品物が多く、容量が大きく、特に同じ重さの品物が多いと、重複する部分問題の数は大幅に増えます。</p> <p></p> <p> 図 14-18   0-1 ナップサック問題の全探索の再帰木 </p>","path":["第 14 章   動的計画法","14.4   0-1 ナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#2-2","level":3,"title":"2.   方法 2:メモ化探索","text":"<p>重複した部分問題を 1 回だけ計算するため、記録用リスト <code>mem</code> に解を保存します。<code>mem[i][c]</code> は \\(dp[i, c]\\) に対応します。</p> <p>メモ化すると、時間計算量は部分問題の数で決まり、\\(O(n \\times cap)\\) になります。実装は次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py<pre><code>def knapsack_dfs_mem(\n wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int\n) -&gt; int:\n \"\"\"0-1 ナップサック問題:メモ化探索\"\"\"\n # すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if i == 0 or c == 0:\n return 0\n # 記録済みなら、その値をそのまま返す\n if mem[i][c] != -1:\n return mem[i][c]\n # ナップサックの容量を超える場合、入れない選択しかできない\n if wgt[i - 1] &gt; c:\n return knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n # 品物 i を入れない場合と入れる場合の最大価値を計算する\n no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]\n # 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = max(no, yes)\n return mem[i][c]\n</code></pre> knapsack.cpp<pre><code>/* 0-1 ナップサック問題:メモ化探索 */\nint knapsackDFSMem(vector&lt;int&gt; &amp;wgt, vector&lt;int&gt; &amp;val, vector&lt;vector&lt;int&gt;&gt; &amp;mem, int i, int c) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][c] != -1) {\n return mem[i][c];\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFSMem(wgt, val, mem, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = max(no, yes);\n return mem[i][c];\n}\n</code></pre> knapsack.java<pre><code>/* 0-1 ナップサック問題:メモ化探索 */\nint knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][c] != -1) {\n return mem[i][c];\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFSMem(wgt, val, mem, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = Math.max(no, yes);\n return mem[i][c];\n}\n</code></pre> knapsack.cs<pre><code>/* 0-1 ナップサック問題:メモ化探索 */\nint KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][c] != -1) {\n return mem[i][c];\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (weight[i - 1] &gt; c) {\n return KnapsackDFSMem(weight, val, mem, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n int no = KnapsackDFSMem(weight, val, mem, i - 1, c);\n int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = Math.Max(no, yes);\n return mem[i][c];\n}\n</code></pre> knapsack.go<pre><code>/* 0-1 ナップサック問題:メモ化探索 */\nfunc knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if i == 0 || c == 0 {\n return 0\n }\n // 記録済みなら、その値をそのまま返す\n if mem[i][c] != -1 {\n return mem[i][c]\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if wgt[i-1] &gt; c {\n return knapsackDFSMem(wgt, val, mem, i-1, c)\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n no := knapsackDFSMem(wgt, val, mem, i-1, c)\n yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1]\n // 2 通りの選択のうち、価値が大きいほうを返す\n mem[i][c] = int(math.Max(float64(no), float64(yes)))\n return mem[i][c]\n}\n</code></pre> knapsack.swift<pre><code>/* 0-1 ナップサック問題:メモ化探索 */\nfunc knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -&gt; Int {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if i == 0 || c == 0 {\n return 0\n }\n // 記録済みなら、その値をそのまま返す\n if mem[i][c] != -1 {\n return mem[i][c]\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if wgt[i - 1] &gt; c {\n return knapsackDFSMem(wgt: wgt, val: val, mem: &amp;mem, i: i - 1, c: c)\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n let no = knapsackDFSMem(wgt: wgt, val: val, mem: &amp;mem, i: i - 1, c: c)\n let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &amp;mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1]\n // 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = max(no, yes)\n return mem[i][c]\n}\n</code></pre> knapsack.js<pre><code>/* 0-1 ナップサック問題:メモ化探索 */\nfunction knapsackDFSMem(wgt, val, mem, i, c) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i === 0 || c === 0) {\n return 0;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][c] !== -1) {\n return mem[i][c];\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFSMem(wgt, val, mem, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n const no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n const yes =\n knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = Math.max(no, yes);\n return mem[i][c];\n}\n</code></pre> knapsack.ts<pre><code>/* 0-1 ナップサック問題:メモ化探索 */\nfunction knapsackDFSMem(\n wgt: Array&lt;number&gt;,\n val: Array&lt;number&gt;,\n mem: Array&lt;Array&lt;number&gt;&gt;,\n i: number,\n c: number\n): number {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i === 0 || c === 0) {\n return 0;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][c] !== -1) {\n return mem[i][c];\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFSMem(wgt, val, mem, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n const no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n const yes =\n knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = Math.max(no, yes);\n return mem[i][c];\n}\n</code></pre> knapsack.dart<pre><code>/* 0-1 ナップサック問題:メモ化探索 */\nint knapsackDFSMem(\n List&lt;int&gt; wgt,\n List&lt;int&gt; val,\n List&lt;List&lt;int&gt;&gt; mem,\n int i,\n int c,\n) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][c] != -1) {\n return mem[i][c];\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFSMem(wgt, val, mem, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n int no = knapsackDFSMem(wgt, val, mem, i - 1, c);\n int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = max(no, yes);\n return mem[i][c];\n}\n</code></pre> knapsack.rs<pre><code>/* 0-1 ナップサック問題:メモ化探索 */\nfn knapsack_dfs_mem(wgt: &amp;[i32], val: &amp;[i32], mem: &amp;mut Vec&lt;Vec&lt;i32&gt;&gt;, i: usize, c: usize) -&gt; i32 {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if i == 0 || c == 0 {\n return 0;\n }\n // 記録済みなら、その値をそのまま返す\n if mem[i][c] != -1 {\n return mem[i][c];\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if wgt[i - 1] &gt; c as i32 {\n return knapsack_dfs_mem(wgt, val, mem, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c);\n let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = std::cmp::max(no, yes);\n mem[i][c]\n}\n</code></pre> knapsack.c<pre><code>/* 0-1 ナップサック問題:メモ化探索 */\nint knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0;\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][c] != -1) {\n return mem[i][c];\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);\n int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1];\n // 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = myMax(no, yes);\n return mem[i][c];\n}\n</code></pre> knapsack.kt<pre><code>/* 0-1 ナップサック問題:メモ化探索 */\nfun knapsackDFSMem(\n wgt: IntArray,\n _val: IntArray,\n mem: Array&lt;IntArray&gt;,\n i: Int,\n c: Int\n): Int {\n // すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n if (i == 0 || c == 0) {\n return 0\n }\n // 記録済みなら、その値をそのまま返す\n if (mem[i][c] != -1) {\n return mem[i][c]\n }\n // ナップサックの容量を超える場合、入れない選択しかできない\n if (wgt[i - 1] &gt; c) {\n return knapsackDFSMem(wgt, _val, mem, i - 1, c)\n }\n // 品物 i を入れない場合と入れる場合の最大価値を計算する\n val no = knapsackDFSMem(wgt, _val, mem, i - 1, c)\n val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1]\n // 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = max(no, yes)\n return mem[i][c]\n}\n</code></pre> knapsack.rb<pre><code>### 0-1 ナップサック問題:メモ化探索 ###\ndef knapsack_dfs_mem(wgt, val, mem, i, c)\n # すべての品物を選び終えたか、ナップサックの残り容量がなければ、価値 0 を返す\n return 0 if i == 0 || c == 0\n # 記録済みなら、その値をそのまま返す\n return mem[i][c] if mem[i][c] != -1\n # ナップサックの容量を超える場合、入れない選択しかできない\n return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] &gt; c\n # 品物 i を入れない場合と入れる場合の最大価値を計算する\n no = knapsack_dfs_mem(wgt, val, mem, i - 1, c)\n yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]\n # 2 通りの選択のうち、価値が大きいほうを記録して返す\n mem[i][c] = [no, yes].max\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-19 に、メモ化探索で枝刈りされる探索分岐を示します。</p> <p></p> <p> 図 14-19   0-1 ナップサック問題のメモ化探索の再帰木 </p>","path":["第 14 章   動的計画法","14.4   0-1 ナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#3-3","level":3,"title":"3.   方法 3:動的計画法","text":"<p>動的計画法は、本質的には状態遷移によって \\(dp\\) テーブルを埋める過程です。コードを次に示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py<pre><code>def knapsack_dp(wgt: list[int], val: list[int], cap: int) -&gt; int:\n \"\"\"0-1 ナップサック問題:動的計画法\"\"\"\n n = len(wgt)\n # dp テーブルを初期化する\n dp = [[0] * (cap + 1) for _ in range(n + 1)]\n # 状態遷移\n for i in range(1, n + 1):\n for c in range(1, cap + 1):\n if wgt[i - 1] &gt; c:\n # ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c]\n else:\n # 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])\n return dp[n][cap]\n</code></pre> knapsack.cpp<pre><code>/* 0-1 ナップサック問題:動的計画法 */\nint knapsackDP(vector&lt;int&gt; &amp;wgt, vector&lt;int&gt; &amp;val, int cap) {\n int n = wgt.size();\n // dp テーブルを初期化する\n vector&lt;vector&lt;int&gt;&gt; dp(n + 1, vector&lt;int&gt;(cap + 1, 0));\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[n][cap];\n}\n</code></pre> knapsack.java<pre><code>/* 0-1 ナップサック問題:動的計画法 */\nint knapsackDP(int[] wgt, int[] val, int cap) {\n int n = wgt.length;\n // dp テーブルを初期化する\n int[][] dp = new int[n + 1][cap + 1];\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[n][cap];\n}\n</code></pre> knapsack.cs<pre><code>/* 0-1 ナップサック問題:動的計画法 */\nint KnapsackDP(int[] weight, int[] val, int cap) {\n int n = weight.Length;\n // dp テーブルを初期化する\n int[,] dp = new int[n + 1, cap + 1];\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (weight[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i, c] = dp[i - 1, c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]);\n }\n }\n }\n return dp[n, cap];\n}\n</code></pre> knapsack.go<pre><code>/* 0-1 ナップサック問題:動的計画法 */\nfunc knapsackDP(wgt, val []int, cap int) int {\n n := len(wgt)\n // dp テーブルを初期化する\n dp := make([][]int, n+1)\n for i := 0; i &lt;= n; i++ {\n dp[i] = make([]int, cap+1)\n }\n // 状態遷移\n for i := 1; i &lt;= n; i++ {\n for c := 1; c &lt;= cap; c++ {\n if wgt[i-1] &gt; c {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i-1][c]\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1])))\n }\n }\n }\n return dp[n][cap]\n}\n</code></pre> knapsack.swift<pre><code>/* 0-1 ナップサック問題:動的計画法 */\nfunc knapsackDP(wgt: [Int], val: [Int], cap: Int) -&gt; Int {\n let n = wgt.count\n // dp テーブルを初期化する\n var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1)\n // 状態遷移\n for i in 1 ... n {\n for c in 1 ... cap {\n if wgt[i - 1] &gt; c {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c]\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1])\n }\n }\n }\n return dp[n][cap]\n}\n</code></pre> knapsack.js<pre><code>/* 0-1 ナップサック問題:動的計画法 */\nfunction knapsackDP(wgt, val, cap) {\n const n = wgt.length;\n // dp テーブルを初期化する\n const dp = Array(n + 1)\n .fill(0)\n .map(() =&gt; Array(cap + 1).fill(0));\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = Math.max(\n dp[i - 1][c],\n dp[i - 1][c - wgt[i - 1]] + val[i - 1]\n );\n }\n }\n }\n return dp[n][cap];\n}\n</code></pre> knapsack.ts<pre><code>/* 0-1 ナップサック問題:動的計画法 */\nfunction knapsackDP(\n wgt: Array&lt;number&gt;,\n val: Array&lt;number&gt;,\n cap: number\n): number {\n const n = wgt.length;\n // dp テーブルを初期化する\n const dp = Array.from({ length: n + 1 }, () =&gt;\n Array.from({ length: cap + 1 }, () =&gt; 0)\n );\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = Math.max(\n dp[i - 1][c],\n dp[i - 1][c - wgt[i - 1]] + val[i - 1]\n );\n }\n }\n }\n return dp[n][cap];\n}\n</code></pre> knapsack.dart<pre><code>/* 0-1 ナップサック問題:動的計画法 */\nint knapsackDP(List&lt;int&gt; wgt, List&lt;int&gt; val, int cap) {\n int n = wgt.length;\n // dp テーブルを初期化する\n List&lt;List&lt;int&gt;&gt; dp = List.generate(n + 1, (index) =&gt; List.filled(cap + 1, 0));\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[n][cap];\n}\n</code></pre> knapsack.rs<pre><code>/* 0-1 ナップサック問題:動的計画法 */\nfn knapsack_dp(wgt: &amp;[i32], val: &amp;[i32], cap: usize) -&gt; i32 {\n let n = wgt.len();\n // dp テーブルを初期化する\n let mut dp = vec![vec![0; cap + 1]; n + 1];\n // 状態遷移\n for i in 1..=n {\n for c in 1..=cap {\n if wgt[i - 1] &gt; c as i32 {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = std::cmp::max(\n dp[i - 1][c],\n dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1],\n );\n }\n }\n }\n dp[n][cap]\n}\n</code></pre> knapsack.c<pre><code>/* 0-1 ナップサック問題:動的計画法 */\nint knapsackDP(int wgt[], int val[], int cap, int wgtSize) {\n int n = wgtSize;\n // dp テーブルを初期化する\n int **dp = malloc((n + 1) * sizeof(int *));\n for (int i = 0; i &lt;= n; i++) {\n dp[i] = calloc(cap + 1, sizeof(int));\n }\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n int res = dp[n][cap];\n // メモリを解放する\n for (int i = 0; i &lt;= n; i++) {\n free(dp[i]);\n }\n return res;\n}\n</code></pre> knapsack.kt<pre><code>/* 0-1 ナップサック問題:動的計画法 */\nfun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {\n val n = wgt.size\n // dp テーブルを初期化する\n val dp = Array(n + 1) { IntArray(cap + 1) }\n // 状態遷移\n for (i in 1..n) {\n for (c in 1..cap) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c]\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1])\n }\n }\n }\n return dp[n][cap]\n}\n</code></pre> knapsack.rb<pre><code>### 0-1 ナップサック問題:動的計画法 ###\ndef knapsack_dp(wgt, val, cap)\n n = wgt.length\n # dp テーブルを初期化する\n dp = Array.new(n + 1) { Array.new(cap + 1, 0) }\n # 状態遷移\n for i in 1...(n + 1)\n for c in 1...(cap + 1)\n if wgt[i - 1] &gt; c\n # ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c]\n else\n # 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max\n end\n end\n end\n dp[n][cap]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-20 のように、時間計算量と空間計算量は、どちらも配列 <code>dp</code> の大きさで決まり、\\(O(n \\times cap)\\) です。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt;&lt;11&gt;&lt;12&gt;&lt;13&gt;&lt;14&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 14-20   0-1 ナップサック問題を動的計画法で解く流れ </p>","path":["第 14 章   動的計画法","14.4   0-1 ナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/knapsack_problem/#4","level":3,"title":"4.   空間の最適化","text":"<p>各状態は一つ前の行の状態だけに依存するため、二つの配列を交互に使えば、空間計算量を \\(O(n \\times cap)\\) から \\(O(cap)\\) に下げられます。</p> <p>さらに、配列を一つだけにできるでしょうか。各状態は真上か左上のマスから遷移します。配列が一つなら、\\(i\\) 行目の走査を始める時点では、まだ \\(i-1\\) 行目の状態が入っています。</p> <ul> <li>順方向に走査すると、\\(dp[i, j]\\) を計算する時点で、左上の \\(dp[i-1, 1]\\) ~ \\(dp[i-1, j-1]\\) がすでに上書きされている可能性があり、正しく状態遷移できません。</li> <li>逆方向に走査すれば、必要な値は上書きされず、正しく状態遷移できます。</li> </ul> <p>図 14-21 に、配列一つで \\(i = 1\\) 行目から \\(i = 2\\) 行目へ移る過程を示します。順方向と逆方向の違いを考えてみてください。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 14-21   空間最適化後の 0-1 ナップサック問題の動的計画法 </p> <p>実装では、配列 <code>dp</code> の第 1 次元 \\(i\\) を取り除き、内側のループを逆順にするだけです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby knapsack.py<pre><code>def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -&gt; int:\n \"\"\"0-1 ナップサック問題:空間を最適化した動的計画法\"\"\"\n n = len(wgt)\n # dp テーブルを初期化する\n dp = [0] * (cap + 1)\n # 状態遷移\n for i in range(1, n + 1):\n # 逆順に走査する\n for c in range(cap, 0, -1):\n if wgt[i - 1] &gt; c:\n # ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c]\n else:\n # 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n return dp[cap]\n</code></pre> knapsack.cpp<pre><code>/* 0-1 ナップサック問題:空間を最適化した動的計画法 */\nint knapsackDPComp(vector&lt;int&gt; &amp;wgt, vector&lt;int&gt; &amp;val, int cap) {\n int n = wgt.size();\n // dp テーブルを初期化する\n vector&lt;int&gt; dp(cap + 1, 0);\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n // 逆順に走査する\n for (int c = cap; c &gt;= 1; c--) {\n if (wgt[i - 1] &lt;= c) {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> knapsack.java<pre><code>/* 0-1 ナップサック問題:空間を最適化した動的計画法 */\nint knapsackDPComp(int[] wgt, int[] val, int cap) {\n int n = wgt.length;\n // dp テーブルを初期化する\n int[] dp = new int[cap + 1];\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n // 逆順に走査する\n for (int c = cap; c &gt;= 1; c--) {\n if (wgt[i - 1] &lt;= c) {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> knapsack.cs<pre><code>/* 0-1 ナップサック問題:空間を最適化した動的計画法 */\nint KnapsackDPComp(int[] weight, int[] val, int cap) {\n int n = weight.Length;\n // dp テーブルを初期化する\n int[] dp = new int[cap + 1];\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n // 逆順に走査する\n for (int c = cap; c &gt; 0; c--) {\n if (weight[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> knapsack.go<pre><code>/* 0-1 ナップサック問題:空間を最適化した動的計画法 */\nfunc knapsackDPComp(wgt, val []int, cap int) int {\n n := len(wgt)\n // dp テーブルを初期化する\n dp := make([]int, cap+1)\n // 状態遷移\n for i := 1; i &lt;= n; i++ {\n // 逆順に走査する\n for c := cap; c &gt;= 1; c-- {\n if wgt[i-1] &lt;= c {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1])))\n }\n }\n }\n return dp[cap]\n}\n</code></pre> knapsack.swift<pre><code>/* 0-1 ナップサック問題:空間を最適化した動的計画法 */\nfunc knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -&gt; Int {\n let n = wgt.count\n // dp テーブルを初期化する\n var dp = Array(repeating: 0, count: cap + 1)\n // 状態遷移\n for i in 1 ... n {\n // 逆順に走査する\n for c in (1 ... cap).reversed() {\n if wgt[i - 1] &lt;= c {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n }\n }\n }\n return dp[cap]\n}\n</code></pre> knapsack.js<pre><code>/* 0-1 ナップサック問題:空間を最適化した動的計画法 */\nfunction knapsackDPComp(wgt, val, cap) {\n const n = wgt.length;\n // dp テーブルを初期化する\n const dp = Array(cap + 1).fill(0);\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n // 逆順に走査する\n for (let c = cap; c &gt;= 1; c--) {\n if (wgt[i - 1] &lt;= c) {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> knapsack.ts<pre><code>/* 0-1 ナップサック問題:空間を最適化した動的計画法 */\nfunction knapsackDPComp(\n wgt: Array&lt;number&gt;,\n val: Array&lt;number&gt;,\n cap: number\n): number {\n const n = wgt.length;\n // dp テーブルを初期化する\n const dp = Array(cap + 1).fill(0);\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n // 逆順に走査する\n for (let c = cap; c &gt;= 1; c--) {\n if (wgt[i - 1] &lt;= c) {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> knapsack.dart<pre><code>/* 0-1 ナップサック問題:空間を最適化した動的計画法 */\nint knapsackDPComp(List&lt;int&gt; wgt, List&lt;int&gt; val, int cap) {\n int n = wgt.length;\n // dp テーブルを初期化する\n List&lt;int&gt; dp = List.filled(cap + 1, 0);\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n // 逆順に走査する\n for (int c = cap; c &gt;= 1; c--) {\n if (wgt[i - 1] &lt;= c) {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> knapsack.rs<pre><code>/* 0-1 ナップサック問題:空間を最適化した動的計画法 */\nfn knapsack_dp_comp(wgt: &amp;[i32], val: &amp;[i32], cap: usize) -&gt; i32 {\n let n = wgt.len();\n // dp テーブルを初期化する\n let mut dp = vec![0; cap + 1];\n // 状態遷移\n for i in 1..=n {\n // 逆順に走査する\n for c in (1..=cap).rev() {\n if wgt[i - 1] &lt;= c as i32 {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);\n }\n }\n }\n dp[cap]\n}\n</code></pre> knapsack.c<pre><code>/* 0-1 ナップサック問題:空間を最適化した動的計画法 */\nint knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {\n int n = wgtSize;\n // dp テーブルを初期化する\n int *dp = calloc(cap + 1, sizeof(int));\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n // 逆順に走査する\n for (int c = cap; c &gt;= 1; c--) {\n if (wgt[i - 1] &lt;= c) {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n int res = dp[cap];\n // メモリを解放する\n free(dp);\n return res;\n}\n</code></pre> knapsack.kt<pre><code>/* 0-1 ナップサック問題:空間を最適化した動的計画法 */\nfun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int {\n val n = wgt.size\n // dp テーブルを初期化する\n val dp = IntArray(cap + 1)\n // 状態遷移\n for (i in 1..n) {\n // 逆順に走査する\n for (c in cap downTo 1) {\n if (wgt[i - 1] &lt;= c) {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])\n }\n }\n }\n return dp[cap]\n}\n</code></pre> knapsack.rb<pre><code>### 0-1 ナップサック問題:空間を最適化した動的計画法 ###\ndef knapsack_dp_comp(wgt, val, cap)\n n = wgt.length\n # dp テーブルを初期化する\n dp = Array.new(cap + 1, 0)\n # 状態遷移\n for i in 1...(n + 1)\n # 逆順に走査する\n for c in cap.downto(1)\n if wgt[i - 1] &gt; c\n # ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c]\n else\n # 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max\n end\n end\n end\n dp[cap]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 14 章   動的計画法","14.4   0-1 ナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/summary/","level":1,"title":"14.7   まとめ","text":"","path":["第 14 章   動的計画法","14.7   まとめ"],"tags":[]},{"location":"chapter_dynamic_programming/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>動的計画法は問題を分解し、部分問題の解を保存して重複計算を避け、計算効率を高めます。</li> <li>時間を度外視すれば、動的計画法の問題はすべてバックトラッキング(全探索)で解けます。ただし、再帰木に大量の重複した部分問題が含まれ、非常に非効率です。記録用リストを導入して計算済みの解を保存すれば、重複する部分問題を 1 回だけ計算できます。</li> <li>メモ化探索は上から下への再帰的な解法です。対応する動的計画法は下から上へ漸化式を適用する解法で、「表を埋める」ように進めます。現在の状態が一部の近隣の状態だけに依存するため、\\(dp\\) テーブルの次元を一つ減らして空間計算量を下げられます。</li> <li>部分問題への分解は汎用的なアルゴリズムの考え方ですが、分割統治、動的計画法、バックトラッキングでは性質が異なります。</li> <li>動的計画法の問題には、部分問題の重複、最適部分構造、後続の判断が過去の経路に依存しない性質という 3 大特徴があります。</li> <li>元の問題の最適解を部分問題の最適解から組み立てられるなら、最適部分構造があります。</li> <li>後続の判断が過去の経路に依存しない性質とは、その後の展開が現在の状態だけで決まり、過去に通ったすべての状態には依存しないことです。多くの組合せ最適化問題はこの性質を持たず、動的計画法で高速に解くことはできません。</li> </ul> <p>ナップサック問題</p> <ul> <li>ナップサック問題は動的計画法の最も典型的な問題の一つで、0-1、個数制限なし、個数制限付きなどの変種があります。</li> <li>0-1 ナップサック問題では、最初の \\(i\\) 個の品物から選んで容量 \\(c\\) のナップサックに入れたときの最大価値を状態に対応させます。「入れない」「入れる」の二つの選択から最適部分構造と状態遷移式が得られます。各状態は真上と左上の状態に依存するため、空間最適化では逆順に走査し、左上の状態が上書きされるのを防ぎます。</li> <li>個数制限なしナップサック問題では各品物を選ぶ個数に制限がないため、入れる場合の状態遷移が 0-1 の場合と異なります。状態が真上と真左に依存するので、空間最適化では順方向に走査します。</li> <li>硬貨の両替問題は、個数制限なしナップサック問題の変種です。価値の「最大化」が硬貨の枚数の「最小化」に変わるため、状態遷移式の \\(\\max()\\) を \\(\\min()\\) に変えます。また、容量を「超えない」条件が目標金額に「ちょうど一致する」条件に変わるため、\\(amt + 1\\) で「目標金額を作れない」という無効な解を表します。</li> <li>硬貨の両替問題 II では、「硬貨の最小枚数」の代わりに「硬貨の組み合わせの数」を求めるため、状態遷移式も \\(\\min()\\) から和に変わります。</li> </ul> <p>編集距離問題</p> <ul> <li>編集距離(Levenshtein 距離)は二つの文字列の類似度を測るもので、一方をもう一方に変換するための最小編集回数と定義されます。編集操作には、挿入、削除、置換があります。</li> <li>編集距離問題では、\\(s\\) の先頭 \\(i\\) 文字を \\(t\\) の先頭 \\(j\\) 文字に変換する最小編集回数を状態に対応させます。\\(s[i-1] \\ne t[j-1]\\) なら挿入・削除・置換の 3 通りの選択があり、それぞれに残る部分問題があります。そこから最適部分構造と状態遷移式を導けます。\\(s[i-1] = t[j-1]\\) なら、その文字は編集不要です。</li> <li>編集距離では状態が真上、真左、左上に依存するため、空間最適化後は順方向でも逆方向でも、そのままでは正しく遷移できません。左上の状態を変数に一時保存すれば、個数制限なしナップサック問題と同じ状況になり、空間最適化後も順方向に走査できます。</li> </ul>","path":["第 14 章   動的計画法","14.7   まとめ"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/","level":1,"title":"14.5   個数制限なしナップサック問題","text":"<p>この節では、もう一つの代表的なナップサック問題である、個数制限なしナップサック問題を解きます。その後、その特殊な場合である硬貨の両替問題を学びます。</p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1451","level":2,"title":"14.5.1   個数制限なしナップサック問題","text":"<p>問題</p> <p>\\(n\\) 個の品物があり、\\(i\\) 番目の重さは \\(wgt[i-1]\\)、価値は \\(val[i-1]\\) です。容量 \\(cap\\) のナップサックがあり、各品物は繰り返し選べます。容量の制限内で入れられる品物の最大価値を求めてください。例を図 14-22 に示します。</p> <p></p> <p> 図 14-22   個数制限なしナップサック問題のデータ例 </p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1","level":3,"title":"1.   動的計画法の考え方","text":"<p>個数制限なしナップサック問題は 0-1 ナップサック問題によく似ており、違いは品物を選ぶ回数に制限がないことだけです。</p> <ul> <li>0-1 ナップサック問題では各品物が 1 個しかないため、品物 \\(i\\) を入れた後は、最初の \\(i-1\\) 個からしか選べません。</li> <li>個数制限なしナップサック問題では各種類の品物が無限にあるため、品物 \\(i\\) を入れた後も、最初の \\(i\\) 個から選べます。</li> </ul> <p>個数制限なしの場合、状態 \\([i, c]\\) の変化は次の二つに分かれます。</p> <ul> <li>品物 \\(i\\) を入れない:0-1 ナップサック問題と同様に、\\([i-1, c]\\) に移ります。</li> <li>品物 \\(i\\) を入れる:0-1 ナップサック問題とは異なり、\\([i, c-wgt[i-1]]\\) に移ります。</li> </ul> <p>したがって、状態遷移式は次のようになります。</p> \\[ dp[i, c] = \\max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) \\]","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2","level":3,"title":"2.   コードの実装","text":"<p>二つの問題のコードを比べると、状態遷移の 1 か所が \\(i-1\\) から \\(i\\) に変わるだけで、ほかはまったく同じです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby unbounded_knapsack.py<pre><code>def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -&gt; int:\n \"\"\"個数制限なしナップサック問題:動的計画法\"\"\"\n n = len(wgt)\n # dp テーブルを初期化する\n dp = [[0] * (cap + 1) for _ in range(n + 1)]\n # 状態遷移\n for i in range(1, n + 1):\n for c in range(1, cap + 1):\n if wgt[i - 1] &gt; c:\n # ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c]\n else:\n # 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])\n return dp[n][cap]\n</code></pre> unbounded_knapsack.cpp<pre><code>/* 個数制限なしナップサック問題:動的計画法 */\nint unboundedKnapsackDP(vector&lt;int&gt; &amp;wgt, vector&lt;int&gt; &amp;val, int cap) {\n int n = wgt.size();\n // dp テーブルを初期化する\n vector&lt;vector&lt;int&gt;&gt; dp(n + 1, vector&lt;int&gt;(cap + 1, 0));\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[n][cap];\n}\n</code></pre> unbounded_knapsack.java<pre><code>/* 個数制限なしナップサック問題:動的計画法 */\nint unboundedKnapsackDP(int[] wgt, int[] val, int cap) {\n int n = wgt.length;\n // dp テーブルを初期化する\n int[][] dp = new int[n + 1][cap + 1];\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[n][cap];\n}\n</code></pre> unbounded_knapsack.cs<pre><code>/* 個数制限なしナップサック問題:動的計画法 */\nint UnboundedKnapsackDP(int[] wgt, int[] val, int cap) {\n int n = wgt.Length;\n // dp テーブルを初期化する\n int[,] dp = new int[n + 1, cap + 1];\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i, c] = dp[i - 1, c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[n, cap];\n}\n</code></pre> unbounded_knapsack.go<pre><code>/* 個数制限なしナップサック問題:動的計画法 */\nfunc unboundedKnapsackDP(wgt, val []int, cap int) int {\n n := len(wgt)\n // dp テーブルを初期化する\n dp := make([][]int, n+1)\n for i := 0; i &lt;= n; i++ {\n dp[i] = make([]int, cap+1)\n }\n // 状態遷移\n for i := 1; i &lt;= n; i++ {\n for c := 1; c &lt;= cap; c++ {\n if wgt[i-1] &gt; c {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i-1][c]\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1])))\n }\n }\n }\n return dp[n][cap]\n}\n</code></pre> unbounded_knapsack.swift<pre><code>/* 個数制限なしナップサック問題:動的計画法 */\nfunc unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -&gt; Int {\n let n = wgt.count\n // dp テーブルを初期化する\n var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1)\n // 状態遷移\n for i in 1 ... n {\n for c in 1 ... cap {\n if wgt[i - 1] &gt; c {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c]\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1])\n }\n }\n }\n return dp[n][cap]\n}\n</code></pre> unbounded_knapsack.js<pre><code>/* 個数制限なしナップサック問題:動的計画法 */\nfunction unboundedKnapsackDP(wgt, val, cap) {\n const n = wgt.length;\n // dp テーブルを初期化する\n const dp = Array.from({ length: n + 1 }, () =&gt;\n Array.from({ length: cap + 1 }, () =&gt; 0)\n );\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = Math.max(\n dp[i - 1][c],\n dp[i][c - wgt[i - 1]] + val[i - 1]\n );\n }\n }\n }\n return dp[n][cap];\n}\n</code></pre> unbounded_knapsack.ts<pre><code>/* 個数制限なしナップサック問題:動的計画法 */\nfunction unboundedKnapsackDP(\n wgt: Array&lt;number&gt;,\n val: Array&lt;number&gt;,\n cap: number\n): number {\n const n = wgt.length;\n // dp テーブルを初期化する\n const dp = Array.from({ length: n + 1 }, () =&gt;\n Array.from({ length: cap + 1 }, () =&gt; 0)\n );\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = Math.max(\n dp[i - 1][c],\n dp[i][c - wgt[i - 1]] + val[i - 1]\n );\n }\n }\n }\n return dp[n][cap];\n}\n</code></pre> unbounded_knapsack.dart<pre><code>/* 個数制限なしナップサック問題:動的計画法 */\nint unboundedKnapsackDP(List&lt;int&gt; wgt, List&lt;int&gt; val, int cap) {\n int n = wgt.length;\n // dp テーブルを初期化する\n List&lt;List&lt;int&gt;&gt; dp = List.generate(n + 1, (index) =&gt; List.filled(cap + 1, 0));\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[n][cap];\n}\n</code></pre> unbounded_knapsack.rs<pre><code>/* 個数制限なしナップサック問題:動的計画法 */\nfn unbounded_knapsack_dp(wgt: &amp;[i32], val: &amp;[i32], cap: usize) -&gt; i32 {\n let n = wgt.len();\n // dp テーブルを初期化する\n let mut dp = vec![vec![0; cap + 1]; n + 1];\n // 状態遷移\n for i in 1..=n {\n for c in 1..=cap {\n if wgt[i - 1] &gt; c as i32 {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]);\n }\n }\n }\n return dp[n][cap];\n}\n</code></pre> unbounded_knapsack.c<pre><code>/* 個数制限なしナップサック問題:動的計画法 */\nint unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) {\n int n = wgtSize;\n // dp テーブルを初期化する\n int **dp = malloc((n + 1) * sizeof(int *));\n for (int i = 0; i &lt;= n; i++) {\n dp[i] = calloc(cap + 1, sizeof(int));\n }\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n int res = dp[n][cap];\n // メモリを解放する\n for (int i = 0; i &lt;= n; i++) {\n free(dp[i]);\n }\n return res;\n}\n</code></pre> unbounded_knapsack.kt<pre><code>/* 個数制限なしナップサック問題:動的計画法 */\nfun unboundedKnapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {\n val n = wgt.size\n // dp テーブルを初期化する\n val dp = Array(n + 1) { IntArray(cap + 1) }\n // 状態遷移\n for (i in 1..n) {\n for (c in 1..cap) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c]\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1])\n }\n }\n }\n return dp[n][cap]\n}\n</code></pre> unbounded_knapsack.rb<pre><code>### 個数制限なしナップサック問題:動的計画法 ###\ndef unbounded_knapsack_dp(wgt, val, cap)\n n = wgt.length\n # dp テーブルを初期化する\n dp = Array.new(n + 1) { Array.new(cap + 1, 0) }\n # 状態遷移\n for i in 1...(n + 1)\n for c in 1...(cap + 1)\n if wgt[i - 1] &gt; c\n # ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c]\n else\n # 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max\n end\n end\n end\n dp[n][cap]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3","level":3,"title":"3.   空間の最適化","text":"<p>現在の状態は左と上の状態から遷移するため、空間最適化後は \\(dp\\) テーブルの各行を順方向に走査する必要があります。</p> <p>この順序は 0-1 ナップサック問題と逆です。図 14-23 で違いを確認してください。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 14-23   空間最適化後の個数制限なしナップサック問題の動的計画法 </p> <p>実装は簡単で、配列 <code>dp</code> の第 1 次元を取り除くだけです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby unbounded_knapsack.py<pre><code>def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -&gt; int:\n \"\"\"個数制限なしナップサック問題:空間を最適化した動的計画法\"\"\"\n n = len(wgt)\n # dp テーブルを初期化する\n dp = [0] * (cap + 1)\n # 状態遷移\n for i in range(1, n + 1):\n # 順方向に走査する\n for c in range(1, cap + 1):\n if wgt[i - 1] &gt; c:\n # ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c]\n else:\n # 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n return dp[cap]\n</code></pre> unbounded_knapsack.cpp<pre><code>/* 個数制限なしナップサック問題:空間を最適化した動的計画法 */\nint unboundedKnapsackDPComp(vector&lt;int&gt; &amp;wgt, vector&lt;int&gt; &amp;val, int cap) {\n int n = wgt.size();\n // dp テーブルを初期化する\n vector&lt;int&gt; dp(cap + 1, 0);\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> unbounded_knapsack.java<pre><code>/* 個数制限なしナップサック問題:空間を最適化した動的計画法 */\nint unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {\n int n = wgt.length;\n // dp テーブルを初期化する\n int[] dp = new int[cap + 1];\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> unbounded_knapsack.cs<pre><code>/* 個数制限なしナップサック問題:空間を最適化した動的計画法 */\nint UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) {\n int n = wgt.Length;\n // dp テーブルを初期化する\n int[] dp = new int[cap + 1];\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> unbounded_knapsack.go<pre><code>/* 個数制限なしナップサック問題:空間を最適化した動的計画法 */\nfunc unboundedKnapsackDPComp(wgt, val []int, cap int) int {\n n := len(wgt)\n // dp テーブルを初期化する\n dp := make([]int, cap+1)\n // 状態遷移\n for i := 1; i &lt;= n; i++ {\n for c := 1; c &lt;= cap; c++ {\n if wgt[i-1] &gt; c {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c]\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1])))\n }\n }\n }\n return dp[cap]\n}\n</code></pre> unbounded_knapsack.swift<pre><code>/* 個数制限なしナップサック問題:空間を最適化した動的計画法 */\nfunc unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -&gt; Int {\n let n = wgt.count\n // dp テーブルを初期化する\n var dp = Array(repeating: 0, count: cap + 1)\n // 状態遷移\n for i in 1 ... n {\n for c in 1 ... cap {\n if wgt[i - 1] &gt; c {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c]\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1])\n }\n }\n }\n return dp[cap]\n}\n</code></pre> unbounded_knapsack.js<pre><code>/* 個数制限なしナップサック問題:空間を最適化した動的計画法 */\nfunction unboundedKnapsackDPComp(wgt, val, cap) {\n const n = wgt.length;\n // dp テーブルを初期化する\n const dp = Array.from({ length: cap + 1 }, () =&gt; 0);\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> unbounded_knapsack.ts<pre><code>/* 個数制限なしナップサック問題:空間を最適化した動的計画法 */\nfunction unboundedKnapsackDPComp(\n wgt: Array&lt;number&gt;,\n val: Array&lt;number&gt;,\n cap: number\n): number {\n const n = wgt.length;\n // dp テーブルを初期化する\n const dp = Array.from({ length: cap + 1 }, () =&gt; 0);\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> unbounded_knapsack.dart<pre><code>/* 個数制限なしナップサック問題:空間を最適化した動的計画法 */\nint unboundedKnapsackDPComp(List&lt;int&gt; wgt, List&lt;int&gt; val, int cap) {\n int n = wgt.length;\n // dp テーブルを初期化する\n List&lt;int&gt; dp = List.filled(cap + 1, 0);\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n return dp[cap];\n}\n</code></pre> unbounded_knapsack.rs<pre><code>/* 個数制限なしナップサック問題:空間を最適化した動的計画法 */\nfn unbounded_knapsack_dp_comp(wgt: &amp;[i32], val: &amp;[i32], cap: usize) -&gt; i32 {\n let n = wgt.len();\n // dp テーブルを初期化する\n let mut dp = vec![0; cap + 1];\n // 状態遷移\n for i in 1..=n {\n for c in 1..=cap {\n if wgt[i - 1] &gt; c as i32 {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]);\n }\n }\n }\n dp[cap]\n}\n</code></pre> unbounded_knapsack.c<pre><code>/* 個数制限なしナップサック問題:空間を最適化した動的計画法 */\nint unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {\n int n = wgtSize;\n // dp テーブルを初期化する\n int *dp = calloc(cap + 1, sizeof(int));\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int c = 1; c &lt;= cap; c++) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c];\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);\n }\n }\n }\n int res = dp[cap];\n // メモリを解放する\n free(dp);\n return res;\n}\n</code></pre> unbounded_knapsack.kt<pre><code>/* 個数制限なしナップサック問題:空間を最適化した動的計画法 */\nfun unboundedKnapsackDPComp(\n wgt: IntArray,\n _val: IntArray,\n cap: Int\n): Int {\n val n = wgt.size\n // dp テーブルを初期化する\n val dp = IntArray(cap + 1)\n // 状態遷移\n for (i in 1..n) {\n for (c in 1..cap) {\n if (wgt[i - 1] &gt; c) {\n // ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c]\n } else {\n // 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])\n }\n }\n }\n return dp[cap]\n}\n</code></pre> unbounded_knapsack.rb<pre><code>### 個数制限なしナップサック問題:動的計画法 ###\ndef unbounded_knapsack_dp(wgt, val, cap)\n n = wgt.length\n # dp テーブルを初期化する\n dp = Array.new(n + 1) { Array.new(cap + 1, 0) }\n # 状態遷移\n for i in 1...(n + 1)\n for c in 1...(cap + 1)\n if wgt[i - 1] &gt; c\n # ナップサックの容量を超える場合、品物 i を選ばない\n dp[i][c] = dp[i - 1][c]\n else\n # 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max\n end\n end\n end\n dp[n][cap]\nend\n\n### 個数制限なしナップサック問題:空間を最適化した動的計画法 ##3\ndef unbounded_knapsack_dp_comp(wgt, val, cap)\n n = wgt.length\n # dp テーブルを初期化する\n dp = Array.new(cap + 1, 0)\n # 状態遷移\n for i in 1...(n + 1)\n # 順方向に走査する\n for c in 1...(cap + 1)\n if wgt[i -1] &gt; c\n # ナップサックの容量を超える場合、品物 i を選ばない\n dp[c] = dp[c]\n else\n # 品物 i を選ばない場合と選ぶ場合の大きいほうの値\n dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max\n end\n end\n end\n dp[cap]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1452","level":2,"title":"14.5.2   硬貨の両替問題","text":"<p>ナップサック問題は、動的計画法の大きな一群を代表する問題であり、硬貨の両替問題など、多くの変種があります。</p> <p>問題</p> <p>\\(n\\) 種類の硬貨があり、\\(i\\) 番目の額面は \\(coins[i - 1]\\)、目標金額は \\(amt\\) です。各種類の硬貨は繰り返し選べます。目標金額を作るための硬貨の最小枚数を求めてください。作れない場合は \\(-1\\) を返します。例を図 14-24 に示します。</p> <p></p> <p> 図 14-24   硬貨の両替問題のデータ例 </p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1_1","level":3,"title":"1.   動的計画法の考え方","text":"<p>硬貨の両替は、個数制限なしナップサック問題の特殊な場合と捉えられます。両者には次の対応と違いがあります。</p> <ul> <li>二つの問題は互いに変換でき、「品物」が「硬貨」、「品物の重さ」が「硬貨の額面」、「ナップサックの容量」が「目標金額」に対応します。</li> <li>最適化の目標は逆で、ナップサック問題では価値を最大にし、両替問題では硬貨の枚数を最小にします。</li> <li>ナップサック問題では容量を「超えない」解を求めますが、両替問題では目標金額に「ちょうど一致する」解を求めます。</li> </ul> <p>第 1 段階:各回の選択を考えて状態を定義し、\\(dp\\) テーブルを作る</p> <p>状態 \\([i, a]\\) に対応する部分問題は、最初の \\(i\\) 種類の硬貨で金額 \\(a\\) を作るための最小枚数で、その解を \\(dp[i, a]\\) とします。</p> <p>二次元の \\(dp\\) テーブルの大きさは \\((n+1) \\times (amt+1)\\) です。</p> <p>第 2 段階:最適部分構造を見つけ、状態遷移式を導く</p> <p>この問題の状態遷移式は、個数制限なしナップサック問題と次の 2 点が異なります。</p> <ul> <li>最小値を求めるので、演算子 \\(\\max()\\) を \\(\\min()\\) に変えます。</li> <li>最適化するのは品物の価値ではなく硬貨の枚数なので、硬貨を選んだときは \\(+1\\) します。</li> </ul> \\[ dp[i, a] = \\min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) \\] <p>第 3 段階:境界条件と状態遷移の順序を決める</p> <p>目標金額が \\(0\\) なら、必要な硬貨の最小枚数は \\(0\\) です。したがって、先頭列の \\(dp[i, 0]\\) はすべて \\(0\\) です。</p> <p>硬貨がなければ、\\(&gt; 0\\) のどの目標金額も作れず、無効な解になります。状態遷移式の \\(\\min()\\) が無効な解を識別して除外できるよう、\\(+ \\infty\\) で表します。つまり、先頭行の \\(dp[0, a]\\) をすべて \\(+ \\infty\\) とします。</p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2_1","level":3,"title":"2.   コードの実装","text":"<p>多くのプログラミング言語では \\(+ \\infty\\) を表す変数がなく、整数型 <code>int</code> の最大値で代用する必要があります。しかし、それでは状態遷移式の \\(+ 1\\) でオーバーフローする可能性があります。</p> <p>そこで、無効な解を \\(amt + 1\\) で表します。金額 \\(amt\\) を作るための硬貨の枚数は、最大でも \\(amt\\) だからです。最後に \\(dp[n, amt]\\) が \\(amt + 1\\) かを調べ、そうなら目標金額を作れないことを示す \\(-1\\) を返します。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change.py<pre><code>def coin_change_dp(coins: list[int], amt: int) -&gt; int:\n \"\"\"硬貨の両替:動的計画法\"\"\"\n n = len(coins)\n MAX = amt + 1\n # dp テーブルを初期化する\n dp = [[0] * (amt + 1) for _ in range(n + 1)]\n # 状態遷移:先頭行と先頭列\n for a in range(1, amt + 1):\n dp[0][a] = MAX\n # 状態遷移:残りの行と列\n for i in range(1, n + 1):\n for a in range(1, amt + 1):\n if coins[i - 1] &gt; a:\n # 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a]\n else:\n # 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n return dp[n][amt] if dp[n][amt] != MAX else -1\n</code></pre> coin_change.cpp<pre><code>/* 硬貨の両替:動的計画法 */\nint coinChangeDP(vector&lt;int&gt; &amp;coins, int amt) {\n int n = coins.size();\n int MAX = amt + 1;\n // dp テーブルを初期化する\n vector&lt;vector&lt;int&gt;&gt; dp(n + 1, vector&lt;int&gt;(amt + 1, 0));\n // 状態遷移:先頭行と先頭列\n for (int a = 1; a &lt;= amt; a++) {\n dp[0][a] = MAX;\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n</code></pre> coin_change.java<pre><code>/* 硬貨の両替:動的計画法 */\nint coinChangeDP(int[] coins, int amt) {\n int n = coins.length;\n int MAX = amt + 1;\n // dp テーブルを初期化する\n int[][] dp = new int[n + 1][amt + 1];\n // 状態遷移:先頭行と先頭列\n for (int a = 1; a &lt;= amt; a++) {\n dp[0][a] = MAX;\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n</code></pre> coin_change.cs<pre><code>/* 硬貨の両替:動的計画法 */\nint CoinChangeDP(int[] coins, int amt) {\n int n = coins.Length;\n int MAX = amt + 1;\n // dp テーブルを初期化する\n int[,] dp = new int[n + 1, amt + 1];\n // 状態遷移:先頭行と先頭列\n for (int a = 1; a &lt;= amt; a++) {\n dp[0, a] = MAX;\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i, a] = dp[i - 1, a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[n, amt] != MAX ? dp[n, amt] : -1;\n}\n</code></pre> coin_change.go<pre><code>/* 硬貨の両替:動的計画法 */\nfunc coinChangeDP(coins []int, amt int) int {\n n := len(coins)\n max := amt + 1\n // dp テーブルを初期化する\n dp := make([][]int, n+1)\n for i := 0; i &lt;= n; i++ {\n dp[i] = make([]int, amt+1)\n }\n // 状態遷移:先頭行と先頭列\n for a := 1; a &lt;= amt; a++ {\n dp[0][a] = max\n }\n // 状態遷移:残りの行と列\n for i := 1; i &lt;= n; i++ {\n for a := 1; a &lt;= amt; a++ {\n if coins[i-1] &gt; a {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i-1][a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1)))\n }\n }\n }\n if dp[n][amt] != max {\n return dp[n][amt]\n }\n return -1\n}\n</code></pre> coin_change.swift<pre><code>/* 硬貨の両替:動的計画法 */\nfunc coinChangeDP(coins: [Int], amt: Int) -&gt; Int {\n let n = coins.count\n let MAX = amt + 1\n // dp テーブルを初期化する\n var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1)\n // 状態遷移:先頭行と先頭列\n for a in 1 ... amt {\n dp[0][a] = MAX\n }\n // 状態遷移:残りの行と列\n for i in 1 ... n {\n for a in 1 ... amt {\n if coins[i - 1] &gt; a {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n }\n }\n }\n return dp[n][amt] != MAX ? dp[n][amt] : -1\n}\n</code></pre> coin_change.js<pre><code>/* 硬貨の両替:動的計画法 */\nfunction coinChangeDP(coins, amt) {\n const n = coins.length;\n const MAX = amt + 1;\n // dp テーブルを初期化する\n const dp = Array.from({ length: n + 1 }, () =&gt;\n Array.from({ length: amt + 1 }, () =&gt; 0)\n );\n // 状態遷移:先頭行と先頭列\n for (let a = 1; a &lt;= amt; a++) {\n dp[0][a] = MAX;\n }\n // 状態遷移:残りの行と列\n for (let i = 1; i &lt;= n; i++) {\n for (let a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[n][amt] !== MAX ? dp[n][amt] : -1;\n}\n</code></pre> coin_change.ts<pre><code>/* 硬貨の両替:動的計画法 */\nfunction coinChangeDP(coins: Array&lt;number&gt;, amt: number): number {\n const n = coins.length;\n const MAX = amt + 1;\n // dp テーブルを初期化する\n const dp = Array.from({ length: n + 1 }, () =&gt;\n Array.from({ length: amt + 1 }, () =&gt; 0)\n );\n // 状態遷移:先頭行と先頭列\n for (let a = 1; a &lt;= amt; a++) {\n dp[0][a] = MAX;\n }\n // 状態遷移:残りの行と列\n for (let i = 1; i &lt;= n; i++) {\n for (let a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[n][amt] !== MAX ? dp[n][amt] : -1;\n}\n</code></pre> coin_change.dart<pre><code>/* 硬貨の両替:動的計画法 */\nint coinChangeDP(List&lt;int&gt; coins, int amt) {\n int n = coins.length;\n int MAX = amt + 1;\n // dp テーブルを初期化する\n List&lt;List&lt;int&gt;&gt; dp = List.generate(n + 1, (index) =&gt; List.filled(amt + 1, 0));\n // 状態遷移:先頭行と先頭列\n for (int a = 1; a &lt;= amt; a++) {\n dp[0][a] = MAX;\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[n][amt] != MAX ? dp[n][amt] : -1;\n}\n</code></pre> coin_change.rs<pre><code>/* 硬貨の両替:動的計画法 */\nfn coin_change_dp(coins: &amp;[i32], amt: usize) -&gt; i32 {\n let n = coins.len();\n let max = amt + 1;\n // dp テーブルを初期化する\n let mut dp = vec![vec![0; amt + 1]; n + 1];\n // 状態遷移:先頭行と先頭列\n for a in 1..=amt {\n dp[0][a] = max;\n }\n // 状態遷移:残りの行と列\n for i in 1..=n {\n for a in 1..=amt {\n if coins[i - 1] &gt; a as i32 {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1);\n }\n }\n }\n if dp[n][amt] != max {\n return dp[n][amt] as i32;\n } else {\n -1\n }\n}\n</code></pre> coin_change.c<pre><code>/* 硬貨の両替:動的計画法 */\nint coinChangeDP(int coins[], int amt, int coinsSize) {\n int n = coinsSize;\n int MAX = amt + 1;\n // dp テーブルを初期化する\n int **dp = malloc((n + 1) * sizeof(int *));\n for (int i = 0; i &lt;= n; i++) {\n dp[i] = calloc(amt + 1, sizeof(int));\n }\n // 状態遷移:先頭行と先頭列\n for (int a = 1; a &lt;= amt; a++) {\n dp[0][a] = MAX;\n }\n // 状態遷移:残りの行と列\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);\n }\n }\n }\n int res = dp[n][amt] != MAX ? dp[n][amt] : -1;\n // メモリを解放する\n for (int i = 0; i &lt;= n; i++) {\n free(dp[i]);\n }\n free(dp);\n return res;\n}\n</code></pre> coin_change.kt<pre><code>/* 硬貨の両替:動的計画法 */\nfun coinChangeDP(coins: IntArray, amt: Int): Int {\n val n = coins.size\n val MAX = amt + 1\n // dp テーブルを初期化する\n val dp = Array(n + 1) { IntArray(amt + 1) }\n // 状態遷移:先頭行と先頭列\n for (a in 1..amt) {\n dp[0][a] = MAX\n }\n // 状態遷移:残りの行と列\n for (i in 1..n) {\n for (a in 1..amt) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)\n }\n }\n }\n return if (dp[n][amt] != MAX) dp[n][amt] else -1\n}\n</code></pre> coin_change.rb<pre><code>### 硬貨の両替:動的計画法 ###\ndef coin_change_dp(coins, amt)\n n = coins.length\n _MAX = amt + 1\n # dp テーブルを初期化する\n dp = Array.new(n + 1) { Array.new(amt + 1, 0) }\n # 状態遷移:先頭行と先頭列\n (1...(amt + 1)).each { |a| dp[0][a] = _MAX }\n # 状態遷移:残りの行と列\n for i in 1...(n + 1)\n for a in 1...(amt + 1)\n if coins[i - 1] &gt; a\n # 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a]\n else\n # 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min\n end\n end\n end\n dp[n][amt] != _MAX ? dp[n][amt] : -1\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 14-25 に、硬貨の両替を動的計画法で解く流れを示します。個数制限なしナップサック問題によく似ています。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt;&lt;11&gt;&lt;12&gt;&lt;13&gt;&lt;14&gt;&lt;15&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 14-25   硬貨の両替問題を動的計画法で解く流れ </p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3_1","level":3,"title":"3.   空間の最適化","text":"<p>硬貨の両替の空間最適化は、個数制限なしナップサック問題と同じ方法で行えます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change.py<pre><code>def coin_change_dp_comp(coins: list[int], amt: int) -&gt; int:\n \"\"\"硬貨の両替:空間を最適化した動的計画法\"\"\"\n n = len(coins)\n MAX = amt + 1\n # dp テーブルを初期化する\n dp = [MAX] * (amt + 1)\n dp[0] = 0\n # 状態遷移\n for i in range(1, n + 1):\n # 順方向に走査する\n for a in range(1, amt + 1):\n if coins[i - 1] &gt; a:\n # 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a]\n else:\n # 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n return dp[amt] if dp[amt] != MAX else -1\n</code></pre> coin_change.cpp<pre><code>/* 硬貨の両替:空間を最適化した動的計画法 */\nint coinChangeDPComp(vector&lt;int&gt; &amp;coins, int amt) {\n int n = coins.size();\n int MAX = amt + 1;\n // dp テーブルを初期化する\n vector&lt;int&gt; dp(amt + 1, MAX);\n dp[0] = 0;\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[amt] != MAX ? dp[amt] : -1;\n}\n</code></pre> coin_change.java<pre><code>/* 硬貨の両替:空間を最適化した動的計画法 */\nint coinChangeDPComp(int[] coins, int amt) {\n int n = coins.length;\n int MAX = amt + 1;\n // dp テーブルを初期化する\n int[] dp = new int[amt + 1];\n Arrays.fill(dp, MAX);\n dp[0] = 0;\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[amt] != MAX ? dp[amt] : -1;\n}\n</code></pre> coin_change.cs<pre><code>/* 硬貨の両替:空間を最適化した動的計画法 */\nint CoinChangeDPComp(int[] coins, int amt) {\n int n = coins.Length;\n int MAX = amt + 1;\n // dp テーブルを初期化する\n int[] dp = new int[amt + 1];\n Array.Fill(dp, MAX);\n dp[0] = 0;\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[amt] != MAX ? dp[amt] : -1;\n}\n</code></pre> coin_change.go<pre><code>/* 硬貨の両替:動的計画法 */\nfunc coinChangeDPComp(coins []int, amt int) int {\n n := len(coins)\n max := amt + 1\n // dp テーブルを初期化する\n dp := make([]int, amt+1)\n for i := 1; i &lt;= amt; i++ {\n dp[i] = max\n }\n // 状態遷移\n for i := 1; i &lt;= n; i++ {\n // 順方向に走査する\n for a := 1; a &lt;= amt; a++ {\n if coins[i-1] &gt; a {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1)))\n }\n }\n }\n if dp[amt] != max {\n return dp[amt]\n }\n return -1\n}\n</code></pre> coin_change.swift<pre><code>/* 硬貨の両替:空間を最適化した動的計画法 */\nfunc coinChangeDPComp(coins: [Int], amt: Int) -&gt; Int {\n let n = coins.count\n let MAX = amt + 1\n // dp テーブルを初期化する\n var dp = Array(repeating: MAX, count: amt + 1)\n dp[0] = 0\n // 状態遷移\n for i in 1 ... n {\n for a in 1 ... amt {\n if coins[i - 1] &gt; a {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n }\n }\n }\n return dp[amt] != MAX ? dp[amt] : -1\n}\n</code></pre> coin_change.js<pre><code>/* 硬貨の両替:空間を最適化した動的計画法 */\nfunction coinChangeDPComp(coins, amt) {\n const n = coins.length;\n const MAX = amt + 1;\n // dp テーブルを初期化する\n const dp = Array.from({ length: amt + 1 }, () =&gt; MAX);\n dp[0] = 0;\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[amt] !== MAX ? dp[amt] : -1;\n}\n</code></pre> coin_change.ts<pre><code>/* 硬貨の両替:空間を最適化した動的計画法 */\nfunction coinChangeDPComp(coins: Array&lt;number&gt;, amt: number): number {\n const n = coins.length;\n const MAX = amt + 1;\n // dp テーブルを初期化する\n const dp = Array.from({ length: amt + 1 }, () =&gt; MAX);\n dp[0] = 0;\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[amt] !== MAX ? dp[amt] : -1;\n}\n</code></pre> coin_change.dart<pre><code>/* 硬貨の両替:空間を最適化した動的計画法 */\nint coinChangeDPComp(List&lt;int&gt; coins, int amt) {\n int n = coins.length;\n int MAX = amt + 1;\n // dp テーブルを初期化する\n List&lt;int&gt; dp = List.filled(amt + 1, MAX);\n dp[0] = 0;\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);\n }\n }\n }\n return dp[amt] != MAX ? dp[amt] : -1;\n}\n</code></pre> coin_change.rs<pre><code>/* 硬貨の両替:空間を最適化した動的計画法 */\nfn coin_change_dp_comp(coins: &amp;[i32], amt: usize) -&gt; i32 {\n let n = coins.len();\n let max = amt + 1;\n // dp テーブルを初期化する\n let mut dp = vec![0; amt + 1];\n dp.fill(max);\n dp[0] = 0;\n // 状態遷移\n for i in 1..=n {\n for a in 1..=amt {\n if coins[i - 1] &gt; a as i32 {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1);\n }\n }\n }\n if dp[amt] != max {\n return dp[amt] as i32;\n } else {\n -1\n }\n}\n</code></pre> coin_change.c<pre><code>/* 硬貨の両替:空間を最適化した動的計画法 */\nint coinChangeDPComp(int coins[], int amt, int coinsSize) {\n int n = coinsSize;\n int MAX = amt + 1;\n // dp テーブルを初期化する\n int *dp = malloc((amt + 1) * sizeof(int));\n for (int j = 1; j &lt;= amt; j++) {\n dp[j] = MAX;\n } \n dp[0] = 0;\n\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1);\n }\n }\n }\n int res = dp[amt] != MAX ? dp[amt] : -1;\n // メモリを解放する\n free(dp);\n return res;\n}\n</code></pre> coin_change.kt<pre><code>/* 硬貨の両替:空間を最適化した動的計画法 */\nfun coinChangeDPComp(coins: IntArray, amt: Int): Int {\n val n = coins.size\n val MAX = amt + 1\n // dp テーブルを初期化する\n val dp = IntArray(amt + 1)\n dp.fill(MAX)\n dp[0] = 0\n // 状態遷移\n for (i in 1..n) {\n for (a in 1..amt) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)\n }\n }\n }\n return if (dp[amt] != MAX) dp[amt] else -1\n}\n</code></pre> coin_change.rb<pre><code>### 硬貨の両替:空間を最適化した動的計画法 ###\ndef coin_change_dp_comp(coins, amt)\n n = coins.length\n _MAX = amt + 1\n # dp テーブルを初期化する\n dp = Array.new(amt + 1, _MAX)\n dp[0] = 0\n # 状態遷移\n for i in 1...(n + 1)\n # 順方向に走査する\n for a in 1...(amt + 1)\n if coins[i - 1] &gt; a\n # 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a]\n else\n # 硬貨 i を選ばない場合と選ぶ場合の小さいほうの値\n dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min\n end\n end\n end\n dp[amt] != _MAX ? dp[amt] : -1\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1453-ii","level":2,"title":"14.5.3   硬貨の両替問題 II","text":"<p>問題</p> <p>\\(n\\) 種類の硬貨があり、\\(i\\) 番目の額面は \\(coins[i - 1]\\)、目標金額は \\(amt\\) です。各種類の硬貨は繰り返し選べます。目標金額を作る硬貨の組み合わせは何通りありますか。例を図 14-26 に示します。</p> <p></p> <p> 図 14-26   硬貨の両替問題 II のデータ例 </p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#1_2","level":3,"title":"1.   動的計画法の考え方","text":"<p>前の問題と異なり、目標は組み合わせの数を求めることです。部分問題は、最初の \\(i\\) 種類の硬貨で金額 \\(a\\) を作る組み合わせの数になります。\\(dp\\) テーブルは引き続き、\\((n+1) \\times (amt + 1)\\) の二次元行列です。</p> <p>現在の状態の組み合わせの数は、現在の硬貨を選ばない場合と選ぶ場合の数の和です。状態遷移式は次のとおりです。</p> \\[ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] \\] <p>目標金額が \\(0\\) なら、何も選ばずに作れるので、先頭列の \\(dp[i, 0]\\) はすべて \\(1\\) で初期化します。硬貨がなければ \\(&gt;0\\) の金額は作れないので、先頭行の \\(dp[0, a]\\) はすべて \\(0\\) です。</p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#2_2","level":3,"title":"2.   コードの実装","text":"PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_ii.py<pre><code>def coin_change_ii_dp(coins: list[int], amt: int) -&gt; int:\n \"\"\"硬貨の両替 II:動的計画法\"\"\"\n n = len(coins)\n # dp テーブルを初期化する\n dp = [[0] * (amt + 1) for _ in range(n + 1)]\n # 先頭列を初期化する\n for i in range(n + 1):\n dp[i][0] = 1\n # 状態遷移\n for i in range(1, n + 1):\n for a in range(1, amt + 1):\n if coins[i - 1] &gt; a:\n # 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a]\n else:\n # 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n return dp[n][amt]\n</code></pre> coin_change_ii.cpp<pre><code>/* 硬貨の両替 II:動的計画法 */\nint coinChangeIIDP(vector&lt;int&gt; &amp;coins, int amt) {\n int n = coins.size();\n // dp テーブルを初期化する\n vector&lt;vector&lt;int&gt;&gt; dp(n + 1, vector&lt;int&gt;(amt + 1, 0));\n // 先頭列を初期化する\n for (int i = 0; i &lt;= n; i++) {\n dp[i][0] = 1;\n }\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n }\n }\n }\n return dp[n][amt];\n}\n</code></pre> coin_change_ii.java<pre><code>/* 硬貨の両替 II:動的計画法 */\nint coinChangeIIDP(int[] coins, int amt) {\n int n = coins.length;\n // dp テーブルを初期化する\n int[][] dp = new int[n + 1][amt + 1];\n // 先頭列を初期化する\n for (int i = 0; i &lt;= n; i++) {\n dp[i][0] = 1;\n }\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n }\n }\n }\n return dp[n][amt];\n}\n</code></pre> coin_change_ii.cs<pre><code>/* 硬貨の両替 II:動的計画法 */\nint CoinChangeIIDP(int[] coins, int amt) {\n int n = coins.Length;\n // dp テーブルを初期化する\n int[,] dp = new int[n + 1, amt + 1];\n // 先頭列を初期化する\n for (int i = 0; i &lt;= n; i++) {\n dp[i, 0] = 1;\n }\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i, a] = dp[i - 1, a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]];\n }\n }\n }\n return dp[n, amt];\n}\n</code></pre> coin_change_ii.go<pre><code>/* 硬貨の両替 II:動的計画法 */\nfunc coinChangeIIDP(coins []int, amt int) int {\n n := len(coins)\n // dp テーブルを初期化する\n dp := make([][]int, n+1)\n for i := 0; i &lt;= n; i++ {\n dp[i] = make([]int, amt+1)\n }\n // 先頭列を初期化する\n for i := 0; i &lt;= n; i++ {\n dp[i][0] = 1\n }\n // 状態遷移:残りの行と列\n for i := 1; i &lt;= n; i++ {\n for a := 1; a &lt;= amt; a++ {\n if coins[i-1] &gt; a {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i-1][a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]]\n }\n }\n }\n return dp[n][amt]\n}\n</code></pre> coin_change_ii.swift<pre><code>/* 硬貨の両替 II:動的計画法 */\nfunc coinChangeIIDP(coins: [Int], amt: Int) -&gt; Int {\n let n = coins.count\n // dp テーブルを初期化する\n var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1)\n // 先頭列を初期化する\n for i in 0 ... n {\n dp[i][0] = 1\n }\n // 状態遷移\n for i in 1 ... n {\n for a in 1 ... amt {\n if coins[i - 1] &gt; a {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n }\n }\n }\n return dp[n][amt]\n}\n</code></pre> coin_change_ii.js<pre><code>/* 硬貨の両替 II:動的計画法 */\nfunction coinChangeIIDP(coins, amt) {\n const n = coins.length;\n // dp テーブルを初期化する\n const dp = Array.from({ length: n + 1 }, () =&gt;\n Array.from({ length: amt + 1 }, () =&gt; 0)\n );\n // 先頭列を初期化する\n for (let i = 0; i &lt;= n; i++) {\n dp[i][0] = 1;\n }\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n }\n }\n }\n return dp[n][amt];\n}\n</code></pre> coin_change_ii.ts<pre><code>/* 硬貨の両替 II:動的計画法 */\nfunction coinChangeIIDP(coins: Array&lt;number&gt;, amt: number): number {\n const n = coins.length;\n // dp テーブルを初期化する\n const dp = Array.from({ length: n + 1 }, () =&gt;\n Array.from({ length: amt + 1 }, () =&gt; 0)\n );\n // 先頭列を初期化する\n for (let i = 0; i &lt;= n; i++) {\n dp[i][0] = 1;\n }\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n }\n }\n }\n return dp[n][amt];\n}\n</code></pre> coin_change_ii.dart<pre><code>/* 硬貨の両替 II:動的計画法 */\nint coinChangeIIDP(List&lt;int&gt; coins, int amt) {\n int n = coins.length;\n // dp テーブルを初期化する\n List&lt;List&lt;int&gt;&gt; dp = List.generate(n + 1, (index) =&gt; List.filled(amt + 1, 0));\n // 先頭列を初期化する\n for (int i = 0; i &lt;= n; i++) {\n dp[i][0] = 1;\n }\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n }\n }\n }\n return dp[n][amt];\n}\n</code></pre> coin_change_ii.rs<pre><code>/* 硬貨の両替 II:動的計画法 */\nfn coin_change_ii_dp(coins: &amp;[i32], amt: usize) -&gt; i32 {\n let n = coins.len();\n // dp テーブルを初期化する\n let mut dp = vec![vec![0; amt + 1]; n + 1];\n // 先頭列を初期化する\n for i in 0..=n {\n dp[i][0] = 1;\n }\n // 状態遷移\n for i in 1..=n {\n for a in 1..=amt {\n if coins[i - 1] &gt; a as i32 {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize];\n }\n }\n }\n dp[n][amt]\n}\n</code></pre> coin_change_ii.c<pre><code>/* 硬貨の両替 II:動的計画法 */\nint coinChangeIIDP(int coins[], int amt, int coinsSize) {\n int n = coinsSize;\n // dp テーブルを初期化する\n int **dp = malloc((n + 1) * sizeof(int *));\n for (int i = 0; i &lt;= n; i++) {\n dp[i] = calloc(amt + 1, sizeof(int));\n }\n // 先頭列を初期化する\n for (int i = 0; i &lt;= n; i++) {\n dp[i][0] = 1;\n }\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];\n }\n }\n }\n int res = dp[n][amt];\n // メモリを解放する\n for (int i = 0; i &lt;= n; i++) {\n free(dp[i]);\n }\n free(dp);\n return res;\n}\n</code></pre> coin_change_ii.kt<pre><code>/* 硬貨の両替 II:動的計画法 */\nfun coinChangeIIDP(coins: IntArray, amt: Int): Int {\n val n = coins.size\n // dp テーブルを初期化する\n val dp = Array(n + 1) { IntArray(amt + 1) }\n // 先頭列を初期化する\n for (i in 0..n) {\n dp[i][0] = 1\n }\n // 状態遷移\n for (i in 1..n) {\n for (a in 1..amt) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n }\n }\n }\n return dp[n][amt]\n}\n</code></pre> coin_change_ii.rb<pre><code>### 硬貨の両替 II:動的計画法 ###\ndef coin_change_ii_dp(coins, amt)\n n = coins.length\n # dp テーブルを初期化する\n dp = Array.new(n + 1) { Array.new(amt + 1, 0) }\n # 先頭列を初期化する\n (0...(n + 1)).each { |i| dp[i][0] = 1 }\n # 状態遷移\n for i in 1...(n + 1)\n for a in 1...(amt + 1)\n if coins[i - 1] &gt; a\n # 目標金額を超える場合、硬貨 i を選ばない\n dp[i][a] = dp[i - 1][a]\n else\n # 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]\n end\n end\n end\n dp[n][amt]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_dynamic_programming/unbounded_knapsack_problem/#3_2","level":3,"title":"3.   空間の最適化","text":"<p>空間最適化も同じで、硬貨の次元を取り除けば実現できます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_ii.py<pre><code>def coin_change_ii_dp_comp(coins: list[int], amt: int) -&gt; int:\n \"\"\"硬貨の両替 II:空間を最適化した動的計画法\"\"\"\n n = len(coins)\n # dp テーブルを初期化する\n dp = [0] * (amt + 1)\n dp[0] = 1\n # 状態遷移\n for i in range(1, n + 1):\n # 順方向に走査する\n for a in range(1, amt + 1):\n if coins[i - 1] &gt; a:\n # 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a]\n else:\n # 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1]]\n return dp[amt]\n</code></pre> coin_change_ii.cpp<pre><code>/* 硬貨の両替 II:空間を最適化した動的計画法 */\nint coinChangeIIDPComp(vector&lt;int&gt; &amp;coins, int amt) {\n int n = coins.size();\n // dp テーブルを初期化する\n vector&lt;int&gt; dp(amt + 1, 0);\n dp[0] = 1;\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1]];\n }\n }\n }\n return dp[amt];\n}\n</code></pre> coin_change_ii.java<pre><code>/* 硬貨の両替 II:空間を最適化した動的計画法 */\nint coinChangeIIDPComp(int[] coins, int amt) {\n int n = coins.length;\n // dp テーブルを初期化する\n int[] dp = new int[amt + 1];\n dp[0] = 1;\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1]];\n }\n }\n }\n return dp[amt];\n}\n</code></pre> coin_change_ii.cs<pre><code>/* 硬貨の両替 II:空間を最適化した動的計画法 */\nint CoinChangeIIDPComp(int[] coins, int amt) {\n int n = coins.Length;\n // dp テーブルを初期化する\n int[] dp = new int[amt + 1];\n dp[0] = 1;\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1]];\n }\n }\n }\n return dp[amt];\n}\n</code></pre> coin_change_ii.go<pre><code>/* 硬貨の両替 II:空間を最適化した動的計画法 */\nfunc coinChangeIIDPComp(coins []int, amt int) int {\n n := len(coins)\n // dp テーブルを初期化する\n dp := make([]int, amt+1)\n dp[0] = 1\n // 状態遷移\n for i := 1; i &lt;= n; i++ {\n // 順方向に走査する\n for a := 1; a &lt;= amt; a++ {\n if coins[i-1] &gt; a {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a-coins[i-1]]\n }\n }\n }\n return dp[amt]\n}\n</code></pre> coin_change_ii.swift<pre><code>/* 硬貨の両替 II:空間を最適化した動的計画法 */\nfunc coinChangeIIDPComp(coins: [Int], amt: Int) -&gt; Int {\n let n = coins.count\n // dp テーブルを初期化する\n var dp = Array(repeating: 0, count: amt + 1)\n dp[0] = 1\n // 状態遷移\n for i in 1 ... n {\n for a in 1 ... amt {\n if coins[i - 1] &gt; a {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1]]\n }\n }\n }\n return dp[amt]\n}\n</code></pre> coin_change_ii.js<pre><code>/* 硬貨の両替 II:空間を最適化した動的計画法 */\nfunction coinChangeIIDPComp(coins, amt) {\n const n = coins.length;\n // dp テーブルを初期化する\n const dp = Array.from({ length: amt + 1 }, () =&gt; 0);\n dp[0] = 1;\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1]];\n }\n }\n }\n return dp[amt];\n}\n</code></pre> coin_change_ii.ts<pre><code>/* 硬貨の両替 II:空間を最適化した動的計画法 */\nfunction coinChangeIIDPComp(coins: Array&lt;number&gt;, amt: number): number {\n const n = coins.length;\n // dp テーブルを初期化する\n const dp = Array.from({ length: amt + 1 }, () =&gt; 0);\n dp[0] = 1;\n // 状態遷移\n for (let i = 1; i &lt;= n; i++) {\n for (let a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1]];\n }\n }\n }\n return dp[amt];\n}\n</code></pre> coin_change_ii.dart<pre><code>/* 硬貨の両替 II:空間を最適化した動的計画法 */\nint coinChangeIIDPComp(List&lt;int&gt; coins, int amt) {\n int n = coins.length;\n // dp テーブルを初期化する\n List&lt;int&gt; dp = List.filled(amt + 1, 0);\n dp[0] = 1;\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1]];\n }\n }\n }\n return dp[amt];\n}\n</code></pre> coin_change_ii.rs<pre><code>/* 硬貨の両替 II:空間を最適化した動的計画法 */\nfn coin_change_ii_dp_comp(coins: &amp;[i32], amt: usize) -&gt; i32 {\n let n = coins.len();\n // dp テーブルを初期化する\n let mut dp = vec![0; amt + 1];\n dp[0] = 1;\n // 状態遷移\n for i in 1..=n {\n for a in 1..=amt {\n if coins[i - 1] &gt; a as i32 {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1] as usize];\n }\n }\n }\n dp[amt]\n}\n</code></pre> coin_change_ii.c<pre><code>/* 硬貨の両替 II:空間を最適化した動的計画法 */\nint coinChangeIIDPComp(int coins[], int amt, int coinsSize) {\n int n = coinsSize;\n // dp テーブルを初期化する\n int *dp = calloc(amt + 1, sizeof(int));\n dp[0] = 1;\n // 状態遷移\n for (int i = 1; i &lt;= n; i++) {\n for (int a = 1; a &lt;= amt; a++) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a];\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1]];\n }\n }\n }\n int res = dp[amt];\n // メモリを解放する\n free(dp);\n return res;\n}\n</code></pre> coin_change_ii.kt<pre><code>/* 硬貨の両替 II:空間を最適化した動的計画法 */\nfun coinChangeIIDPComp(coins: IntArray, amt: Int): Int {\n val n = coins.size\n // dp テーブルを初期化する\n val dp = IntArray(amt + 1)\n dp[0] = 1\n // 状態遷移\n for (i in 1..n) {\n for (a in 1..amt) {\n if (coins[i - 1] &gt; a) {\n // 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a]\n } else {\n // 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1]]\n }\n }\n }\n return dp[amt]\n}\n</code></pre> coin_change_ii.rb<pre><code>### 硬貨の両替 II:空間を最適化した動的計画法 ###\ndef coin_change_ii_dp_comp(coins, amt)\n n = coins.length\n # dp テーブルを初期化する\n dp = Array.new(amt + 1, 0)\n dp[0] = 1\n # 状態遷移\n for i in 1...(n + 1)\n # 順方向に走査する\n for a in 1...(amt + 1)\n if coins[i - 1] &gt; a\n # 目標金額を超える場合、硬貨 i を選ばない\n dp[a] = dp[a]\n else\n # 硬貨 i を選ばない場合と選ぶ場合の方法の数の和\n dp[a] = dp[a] + dp[a - coins[i - 1]]\n end\n end\n end\n dp[amt]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 14 章   動的計画法","14.5   個数制限なしナップサック問題"],"tags":[]},{"location":"chapter_graph/","level":1,"title":"第 9 章   グラフ","text":"<p>概要</p> <p>人生の旅路で、私たちは一つひとつのノードのように、無数の見えない辺で結ばれています。</p> <p>一つひとつの出会いと別れが、この巨大なネットワークにそれぞれの足跡を残します。</p>","path":["第 9 章   グラフ"],"tags":[]},{"location":"chapter_graph/#_1","level":2,"title":"この章の内容","text":"<ul> <li>9.1   グラフ</li> <li>9.2   グラフの基本操作</li> <li>9.3   グラフの走査</li> <li>9.4   まとめ</li> <li>9.5   練習問題</li> </ul>","path":["第 9 章   グラフ"],"tags":[]},{"location":"chapter_graph/exercises/","level":1,"title":"9.5   練習問題","text":"","path":["第 9 章   グラフ","9.5   練習問題"],"tags":[]},{"location":"chapter_graph/exercises/#951","level":2,"title":"9.5.1   知識の確認","text":"","path":["第 9 章   グラフ","9.5   練習問題"],"tags":[]},{"location":"chapter_graph/exercises/#1","level":3,"title":"1.   同じグラフを二つの方法で表す","text":"<p>無向グラフに 4 個の頂点 <code>A、B、C、D</code> があり、辺は <code>A-B、A-C、B-C、C-D</code> です。</p> <ol> <li>隣接リストを書いてください。</li> <li>0 と 1 だけからなる隣接行列を埋めてください。</li> <li><code>A</code> と <code>D</code> が直接つながっているかを判定するとき、一つの格納項目を見るだけでよいのはどちらの表現ですか。</li> <li>頂点が多く、辺が少ないグラフでは、通常どちらが領域を節約できますか。</li> </ol> 解答例 <ol> <li> <p>隣接リストは次のとおりです。</p> <pre><code>A: B, C\nB: A, C\nC: A, B, D\nD: C\n</code></pre> </li> <li> <p>隣接行列は次のとおりです。</p> A B C D A 0 1 1 0 B 1 0 1 0 C 1 1 0 1 D 0 0 1 0 </li> <li> <p>隣接行列では、<code>A</code> 行 <code>D</code> 列を直接見られるため、任意の二頂点が直接つながっているかの判定に適しています。</p> </li> <li> <p>頂点が多く辺が少ない場合、隣接リストは実在する辺だけを記録するため、頂点のすべての組に領域を用意する隣接行列より、通常は領域を節約できます。</p> </li> </ol>","path":["第 9 章   グラフ","9.5   練習問題"],"tags":[]},{"location":"chapter_graph/exercises/#2","level":3,"title":"2.   幅優先と深さ優先の訪問順序","text":"<p>無向グラフの頂点は <code>A、B、C、D、E</code> で、辺は <code>A-B、A-C、B-D、C-D、D-E</code> です。</p> <p>A から出発し、未訪問の隣接頂点が複数ある場合はアルファベット順に選ぶものとします。</p> <ol> <li>幅優先走査(BFS)の訪問順を書いてください。</li> <li>再帰による深さ優先走査(DFS)の訪問順を書いてください。</li> <li>どちらの走査でも、訪問済みの頂点を記録する必要があるのはなぜですか。</li> </ol> 解答例 <ol> <li> <p>BFS の訪問順は <code>A, B, C, D, E</code> です。まず A から辺 1 本の距離にある B、C を訪問し、 その後、より遠い D、E を訪問します。</p> </li> <li> <p>DFS の訪問順は <code>A, B, D, C, E</code> です。現在の頂点の未訪問の隣接頂点へ順に進むため、 まず <code>A → B → D → C</code> をたどります。C には新しい隣接頂点がないので D に戻り、E を訪問します。</p> </li> <li> <p>グラフには、例えば <code>A-B-D-C-A</code> のような環があります。訪問済みの頂点を記録しなければ、 環に沿って同じ頂点群を繰り返し訪問し、正常に終了できない可能性があります。</p> </li> </ol>","path":["第 9 章   グラフ","9.5   練習問題"],"tags":[]},{"location":"chapter_graph/exercises/#3-bfs","level":3,"title":"3.   一回の BFS でグラフ全体を訪問できるか","text":"<p>無向グラフに頂点 <code>A、B、C、D、E、F</code> があり、辺は <code>A-B、B-C、D-E</code> だけです。</p> <ol> <li>A から一回 BFS を行うと、どの頂点を訪問できますか。</li> <li>問い 1 の結果から、この一回の BFS でグラフの全頂点を訪問できたといえますか。理由も説明してください。</li> <li>全頂点をアルファベット順に調べ、未訪問の頂点に出会うたびに BFS を開始すると、 各 BFS の始点はどこになりますか。このグラフは、互いにつながらない部分(連結成分)にいくつ分かれますか。</li> </ol> 解答例 <ol> <li> <p>A から訪問できるのは <code>A、B、C</code> だけです。</p> </li> <li> <p>全頂点は訪問できていません。<code>D、E</code> は別の連結した部分を作り、F は孤立した頂点です。 いずれも A との間に経路がないため、A からは到達できません。</p> </li> <li> <p>三回の BFS の始点は順に <code>A、D、F</code> で、それぞれ <code>{A, B, C}</code>、<code>{D, E}</code>、<code>{F}</code> を訪問します。したがって、このグラフの連結成分は 3 個です。</p> </li> </ol>","path":["第 9 章   グラフ","9.5   練習問題"],"tags":[]},{"location":"chapter_graph/exercises/#952","level":2,"title":"9.5.2   プログラミング演習","text":"","path":["第 9 章   グラフ","9.5   練習問題"],"tags":[]},{"location":"chapter_graph/exercises/#1_1","level":3,"title":"1.   無向グラフに経路があるかを判定する","text":"<p>\\(n\\) 個の頂点を持つ無向グラフが与えられ、頂点には \\(0\\) から \\(n-1\\) の番号が付いています。配列 <code>edges</code> の各項目 <code>[u, v]</code> は、頂点 <code>u</code> と <code>v</code> の間に無向辺があることを表します。</p> <p>さらに、始点 <code>source</code> と終点 <code>destination</code> が与えられます。まず <code>edges</code> から隣接リストを作り、次に BFS または DFS を使って、 <code>source</code> から <code>destination</code> への経路があるかを判定してください。あれば <code>true</code>、なければ <code>false</code> を返します。 グラフには環がある場合も、連結でない場合もあります。</p> 解くためのヒント <ol> <li>各無向辺は、両方向に追加する必要があります</li> <li>グラフには環がある場合があるため、訪問済みのノードを必ず記録します</li> <li>source から出発し、destination に出会えば true を、走査が終わっても出会わなければ false を返します</li> </ol> <p>LeetCode</p>","path":["第 9 章   グラフ","9.5   練習問題"],"tags":[]},{"location":"chapter_graph/graph/","level":1,"title":"9.1   グラフ","text":"<p>グラフ(graph)は、頂点(vertex)と辺(edge)からなる非線形データ構造です。グラフ \\(G\\) は、頂点の集合 \\(V\\) と辺の集合 \\(E\\) をまとめたものとして抽象的に表せます。次の例は、5 個の頂点と 7 本の辺を持つグラフです。</p> \\[ \\begin{aligned} V &amp; = \\{ 1, 2, 3, 4, 5 \\} \\newline E &amp; = \\{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \\} \\newline G &amp; = \\{ V, E \\} \\newline \\end{aligned} \\] <p>頂点をノード、辺をノード同士をつなぐ参照(ポインタ)と考えると、グラフは連結リストを拡張したデータ構造として捉えられます。図 9-1 のように、線形の関係(連結リスト)や分割統治の関係(木)よりも、ネットワークの関係(グラフ)は自由度が高く、その分複雑になります。</p> <p></p> <p> 図 9-1   連結リスト、木、グラフの関係 </p>","path":["第 9 章   グラフ","9.1   グラフ"],"tags":[]},{"location":"chapter_graph/graph/#911","level":2,"title":"9.1.1   グラフの主な種類と用語","text":"<p>辺に向きがあるかどうかによって、図 9-2 のように無向グラフ(undirected graph)と有向グラフ(directed graph)に分けられます。</p> <ul> <li>無向グラフの辺は、2 頂点間の「双方向」のつながりを表します。WeChat や QQ の「友達関係」がその例です。</li> <li>有向グラフの辺には向きがあります。つまり、\\(A \\rightarrow B\\) と \\(A \\leftarrow B\\) の二つの向きの辺は互いに独立しています。Weibo や Douyin の「フォローする」「フォローされる」という関係がその例です。</li> </ul> <p></p> <p> 図 9-2   有向グラフと無向グラフ </p> <p>すべての頂点がつながっているかどうかによって、図 9-3 のように連結グラフ(connected graph)と非連結グラフ(disconnected graph)に分けられます。</p> <ul> <li>連結グラフでは、ある頂点から出発して、ほかのどの頂点にも到達できます。</li> <li>非連結グラフでは、ある頂点から出発したとき、到達できない頂点が少なくとも一つあります。</li> </ul> <p></p> <p> 図 9-3   連結グラフと非連結グラフ </p> <p>辺に「重み」という変数を加えると、図 9-4 の重み付きグラフ(weighted graph)になります。たとえば『王者荣耀』などのモバイルゲームでは、一緒に遊んだ時間からプレイヤー間の「親密度」を計算します。この親密度のネットワークは、重み付きグラフで表せます。</p> <p></p> <p> 図 9-4   重み付きグラフと重みなしグラフ </p> <p>グラフでは、次の用語をよく使います。</p> <ul> <li>隣接(adjacency):2 頂点の間に辺があるとき、これらの頂点は「隣接している」といいます。図 9-4 では、頂点 1 に隣接するのは頂点 2、3、5 です。</li> <li>経路(path):頂点 A から頂点 B までに通る辺の列を、A から B への「経路」といいます。図 9-4 の辺の列 1-5-2-4 は、頂点 1 から頂点 4 への経路の一つです。</li> <li>次数(degree):ある頂点が持つ辺の本数です。有向グラフでは、入次数(in-degree)はその頂点に入る辺の本数、出次数(out-degree)はその頂点から出る辺の本数を表します。</li> </ul>","path":["第 9 章   グラフ","9.1   グラフ"],"tags":[]},{"location":"chapter_graph/graph/#912","level":2,"title":"9.1.2   グラフの表現","text":"<p>グラフの代表的な表現方法には、「隣接行列」と「隣接リスト」があります。以下では無向グラフを例に説明します。</p>","path":["第 9 章   グラフ","9.1   グラフ"],"tags":[]},{"location":"chapter_graph/graph/#1","level":3,"title":"1.   隣接行列","text":"<p>グラフの頂点数を \\(n\\) とします。隣接行列(adjacency matrix)は、\\(n \\times n\\) の行列でグラフを表します。各行(列)が一つの頂点に対応し、行列の要素が辺を表します。2 頂点間に辺があるかどうかを \\(1\\) または \\(0\\) で示します。</p> <p>図 9-5 のように、隣接行列を \\(M\\)、頂点のリストを \\(V\\) とすると、行列の要素 \\(M[i, j] = 1\\) は頂点 \\(V[i]\\) から頂点 \\(V[j]\\) への辺があることを表し、\\(M[i, j] = 0\\) は辺がないことを表します。</p> <p></p> <p> 図 9-5   隣接行列によるグラフの表現 </p> <p>隣接行列には、次の特徴があります。</p> <ul> <li>単純グラフでは頂点を自分自身につなげられないため、隣接行列の主対角線上の要素は意味を持ちません。</li> <li>無向グラフでは両方向の辺が同じものを表すため、隣接行列は主対角線について対称です。</li> <li>隣接行列の要素 \\(1\\) と \\(0\\) を重みに置き換えると、重み付きグラフを表せます。</li> </ul> <p>隣接行列でグラフを表すと、行列の要素に直接アクセスして辺を取得できるため、辺の追加・削除・検索・更新は効率が高く、時間計算量はいずれも \\(O(1)\\) です。ただし、行列の空間計算量は \\(O(n^2)\\) で、メモリを多く使います。</p>","path":["第 9 章   グラフ","9.1   グラフ"],"tags":[]},{"location":"chapter_graph/graph/#2","level":3,"title":"2.   隣接リスト","text":"<p>隣接リスト(adjacency list)は、\\(n\\) 個の連結リストでグラフを表し、連結リストのノードが頂点を表します。\\(i\\) 番目の連結リストは頂点 \\(i\\) に対応し、その頂点に隣接するすべての頂点(その頂点につながる頂点)を格納します。図 9-6 に、隣接リストで格納したグラフの例を示します。</p> <p></p> <p> 図 9-6   隣接リストによるグラフの表現 </p> <p>隣接リストは実際に存在する辺だけを保存します。通常、辺の総数は \\(n^2\\) よりはるかに少ないため、空間を節約できます。ただし、辺を探すには連結リストを走査する必要があり、時間効率は隣接行列より低くなります。</p> <p>図 9-6 を見ると、隣接リストの構造はハッシュテーブルの「チェイン法」によく似ているため、同じような方法で効率を改善できます。たとえば、連結リストが長い場合は AVL 木や赤黒木に変換し、時間計算量を \\(O(n)\\) から \\(O(\\log n)\\) に改善できます。ハッシュテーブルに変換すれば、時間計算量を \\(O(1)\\) にできます。</p>","path":["第 9 章   グラフ","9.1   グラフ"],"tags":[]},{"location":"chapter_graph/graph/#913","level":2,"title":"9.1.3   グラフの主な用途","text":"<p>表 9-1 のように、多くの現実のシステムをグラフでモデル化でき、それらに関する問題をグラフ上の計算問題に帰着できます。</p> <p> 表 9-1   日常生活に見られるグラフ </p> 頂点 辺 グラフ上の計算問題 ソーシャルネットワーク ユーザー 友達関係 友達候補の推薦 地下鉄の路線 駅 駅同士の接続 最短経路の推薦 太陽系 天体 天体間の万有引力 惑星の軌道計算","path":["第 9 章   グラフ","9.1   グラフ"],"tags":[]},{"location":"chapter_graph/graph_operations/","level":1,"title":"9.2   グラフの基本操作","text":"<p>グラフの基本操作には、「辺」に対する操作と「頂点」に対する操作があります。「隣接行列」と「隣接リスト」では、実装方法が異なります。</p>","path":["第 9 章   グラフ","9.2   グラフの基本操作"],"tags":[]},{"location":"chapter_graph/graph_operations/#921","level":2,"title":"9.2.1   隣接行列による実装","text":"<p>頂点数 \\(n\\) の無向グラフに対する各種操作は、図 9-7 のように実装できます。</p> <ul> <li>辺の追加・削除:隣接行列の指定した辺を直接変更するだけなので、\\(O(1)\\) 時間です。無向グラフのため、両方向の辺を同時に更新する必要があります。</li> <li>頂点の追加:隣接行列の末尾に行と列を一つずつ追加し、すべて \\(0\\) で埋めます。\\(O(n)\\) 時間かかります。</li> <li>頂点の削除:隣接行列から行と列を一つずつ削除します。最悪の場合は先頭の行と列を削除するときで、\\((n-1)^2\\) 個の要素を「左上に移動」するため、\\(O(n^2)\\) 時間かかります。</li> <li>初期化:\\(n\\) 個の頂点を受け取り、長さ \\(n\\) の頂点リスト <code>vertices</code> を \\(O(n)\\) 時間で初期化します。\\(n \\times n\\) の隣接行列 <code>adjMat</code> の初期化には \\(O(n^2)\\) 時間かかります。</li> </ul> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 9-7   隣接行列の初期化、辺の追加・削除、頂点の追加・削除 </p> <p>隣接行列によるグラフの実装を次に示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_adjacency_matrix.py<pre><code>class GraphAdjMat:\n \"\"\"隣接行列で実装した無向グラフクラス\"\"\"\n\n def __init__(self, vertices: list[int], edges: list[list[int]]):\n \"\"\"コンストラクタ\"\"\"\n # 頂点リスト。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n self.vertices: list[int] = []\n # 隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n self.adj_mat: list[list[int]] = []\n # 頂点を追加する\n for val in vertices:\n self.add_vertex(val)\n # 辺を追加する\n # edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n for e in edges:\n self.add_edge(e[0], e[1])\n\n def size(self) -&gt; int:\n \"\"\"頂点数を取得する\"\"\"\n return len(self.vertices)\n\n def add_vertex(self, val: int):\n \"\"\"頂点を追加する\"\"\"\n n = self.size()\n # 頂点リストに新しい頂点の値を追加する\n self.vertices.append(val)\n # 隣接行列に 1 行追加する\n new_row = [0] * n\n self.adj_mat.append(new_row)\n # 隣接行列に 1 列追加する\n for row in self.adj_mat:\n row.append(0)\n\n def remove_vertex(self, index: int):\n \"\"\"頂点を削除する\"\"\"\n if index &gt;= self.size():\n raise IndexError()\n # 頂点リストからインデックス index の頂点を削除する\n self.vertices.pop(index)\n # 隣接行列からインデックス index の行を削除する\n self.adj_mat.pop(index)\n # 隣接行列からインデックス index の列を削除する\n for row in self.adj_mat:\n row.pop(index)\n\n def add_edge(self, i: int, j: int):\n \"\"\"辺を追加する\"\"\"\n # 引数 i, j は vertices の要素のインデックスに対応する\n # 添字が範囲外または互いに等しい場合の処理\n if i &lt; 0 or j &lt; 0 or i &gt;= self.size() or j &gt;= self.size() or i == j:\n raise IndexError()\n # 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) == (j, i) を満たす\n self.adj_mat[i][j] = 1\n self.adj_mat[j][i] = 1\n\n def remove_edge(self, i: int, j: int):\n \"\"\"辺を削除する\"\"\"\n # 引数 i, j は vertices の要素のインデックスに対応する\n # 添字が範囲外または互いに等しい場合の処理\n if i &lt; 0 or j &lt; 0 or i &gt;= self.size() or j &gt;= self.size() or i == j:\n raise IndexError()\n self.adj_mat[i][j] = 0\n self.adj_mat[j][i] = 0\n\n def print(self):\n \"\"\"隣接行列を表示する\"\"\"\n print(\"頂点リスト =\", self.vertices)\n print(\"隣接行列 =\")\n print_matrix(self.adj_mat)\n</code></pre> graph_adjacency_matrix.cpp<pre><code>/* 隣接行列で実装した無向グラフクラス */\nclass GraphAdjMat {\n vector&lt;int&gt; vertices; // 頂点リスト。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n vector&lt;vector&lt;int&gt;&gt; adjMat; // 隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n\n public:\n /* コンストラクタ */\n GraphAdjMat(const vector&lt;int&gt; &amp;vertices, const vector&lt;vector&lt;int&gt;&gt; &amp;edges) {\n // 頂点を追加する\n for (int val : vertices) {\n addVertex(val);\n }\n // 辺を追加する\n // edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n for (const vector&lt;int&gt; &amp;edge : edges) {\n addEdge(edge[0], edge[1]);\n }\n }\n\n /* 頂点数を取得する */\n int size() const {\n return vertices.size();\n }\n\n /* 頂点を追加する */\n void addVertex(int val) {\n int n = size();\n // 頂点リストに新しい頂点の値を追加する\n vertices.push_back(val);\n // 隣接行列に 1 行追加する\n adjMat.emplace_back(vector&lt;int&gt;(n, 0));\n // 隣接行列に 1 列追加する\n for (vector&lt;int&gt; &amp;row : adjMat) {\n row.push_back(0);\n }\n }\n\n /* 頂点を削除する */\n void removeVertex(int index) {\n if (index &gt;= size()) {\n throw out_of_range(\"頂点が存在しません\");\n }\n // 頂点リストからインデックス index の頂点を削除する\n vertices.erase(vertices.begin() + index);\n // 隣接行列からインデックス index の行を削除する\n adjMat.erase(adjMat.begin() + index);\n // 隣接行列からインデックス index の列を削除する\n for (vector&lt;int&gt; &amp;row : adjMat) {\n row.erase(row.begin() + index);\n }\n }\n\n /* 辺を追加する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n void addEdge(int i, int j) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= size() || j &gt;= size() || i == j) {\n throw out_of_range(\"頂点が存在しません\");\n }\n // 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) == (j, i) を満たす\n adjMat[i][j] = 1;\n adjMat[j][i] = 1;\n }\n\n /* 辺を削除する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n void removeEdge(int i, int j) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= size() || j &gt;= size() || i == j) {\n throw out_of_range(\"頂点が存在しません\");\n }\n adjMat[i][j] = 0;\n adjMat[j][i] = 0;\n }\n\n /* 隣接行列を表示する */\n void print() {\n cout &lt;&lt; \"頂点リスト = \";\n printVector(vertices);\n cout &lt;&lt; \"隣接行列 =\" &lt;&lt; endl;\n printVectorMatrix(adjMat);\n }\n};\n</code></pre> graph_adjacency_matrix.java<pre><code>/* 隣接行列で実装した無向グラフクラス */\nclass GraphAdjMat {\n List&lt;Integer&gt; vertices; // 頂点リスト。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n List&lt;List&lt;Integer&gt;&gt; adjMat; // 隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n\n /* コンストラクタ */\n public GraphAdjMat(int[] vertices, int[][] edges) {\n this.vertices = new ArrayList&lt;&gt;();\n this.adjMat = new ArrayList&lt;&gt;();\n // 頂点を追加する\n for (int val : vertices) {\n addVertex(val);\n }\n // 辺を追加する\n // edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n for (int[] e : edges) {\n addEdge(e[0], e[1]);\n }\n }\n\n /* 頂点数を取得する */\n public int size() {\n return vertices.size();\n }\n\n /* 頂点を追加する */\n public void addVertex(int val) {\n int n = size();\n // 頂点リストに新しい頂点の値を追加する\n vertices.add(val);\n // 隣接行列に 1 行追加する\n List&lt;Integer&gt; newRow = new ArrayList&lt;&gt;(n);\n for (int j = 0; j &lt; n; j++) {\n newRow.add(0);\n }\n adjMat.add(newRow);\n // 隣接行列に 1 列追加する\n for (List&lt;Integer&gt; row : adjMat) {\n row.add(0);\n }\n }\n\n /* 頂点を削除する */\n public void removeVertex(int index) {\n if (index &gt;= size())\n throw new IndexOutOfBoundsException();\n // 頂点リストからインデックス index の頂点を削除する\n vertices.remove(index);\n // 隣接行列からインデックス index の行を削除する\n adjMat.remove(index);\n // 隣接行列からインデックス index の列を削除する\n for (List&lt;Integer&gt; row : adjMat) {\n row.remove(index);\n }\n }\n\n /* 辺を追加する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n public void addEdge(int i, int j) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= size() || j &gt;= size() || i == j)\n throw new IndexOutOfBoundsException();\n // 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) == (j, i) を満たす\n adjMat.get(i).set(j, 1);\n adjMat.get(j).set(i, 1);\n }\n\n /* 辺を削除する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n public void removeEdge(int i, int j) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= size() || j &gt;= size() || i == j)\n throw new IndexOutOfBoundsException();\n adjMat.get(i).set(j, 0);\n adjMat.get(j).set(i, 0);\n }\n\n /* 隣接行列を表示する */\n public void print() {\n System.out.print(\"頂点リスト = \");\n System.out.println(vertices);\n System.out.println(\"隣接行列 =\");\n PrintUtil.printMatrix(adjMat);\n }\n}\n</code></pre> graph_adjacency_matrix.cs<pre><code>/* 隣接行列で実装した無向グラフクラス */\nclass GraphAdjMat {\n List&lt;int&gt; vertices; // 頂点リスト。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n List&lt;List&lt;int&gt;&gt; adjMat; // 隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n\n /* コンストラクタ */\n public GraphAdjMat(int[] vertices, int[][] edges) {\n this.vertices = [];\n this.adjMat = [];\n // 頂点を追加する\n foreach (int val in vertices) {\n AddVertex(val);\n }\n // 辺を追加する\n // edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n foreach (int[] e in edges) {\n AddEdge(e[0], e[1]);\n }\n }\n\n /* 頂点数を取得する */\n int Size() {\n return vertices.Count;\n }\n\n /* 頂点を追加する */\n public void AddVertex(int val) {\n int n = Size();\n // 頂点リストに新しい頂点の値を追加する\n vertices.Add(val);\n // 隣接行列に 1 行追加する\n List&lt;int&gt; newRow = new(n);\n for (int j = 0; j &lt; n; j++) {\n newRow.Add(0);\n }\n adjMat.Add(newRow);\n // 隣接行列に 1 列追加する\n foreach (List&lt;int&gt; row in adjMat) {\n row.Add(0);\n }\n }\n\n /* 頂点を削除する */\n public void RemoveVertex(int index) {\n if (index &gt;= Size())\n throw new IndexOutOfRangeException();\n // 頂点リストからインデックス index の頂点を削除する\n vertices.RemoveAt(index);\n // 隣接行列からインデックス index の行を削除する\n adjMat.RemoveAt(index);\n // 隣接行列からインデックス index の列を削除する\n foreach (List&lt;int&gt; row in adjMat) {\n row.RemoveAt(index);\n }\n }\n\n /* 辺を追加する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n public void AddEdge(int i, int j) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= Size() || j &gt;= Size() || i == j)\n throw new IndexOutOfRangeException();\n // 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) == (j, i) を満たす\n adjMat[i][j] = 1;\n adjMat[j][i] = 1;\n }\n\n /* 辺を削除する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n public void RemoveEdge(int i, int j) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= Size() || j &gt;= Size() || i == j)\n throw new IndexOutOfRangeException();\n adjMat[i][j] = 0;\n adjMat[j][i] = 0;\n }\n\n /* 隣接行列を表示する */\n public void Print() {\n Console.Write(\"頂点リスト = \");\n PrintUtil.PrintList(vertices);\n Console.WriteLine(\"隣接行列 =\");\n PrintUtil.PrintMatrix(adjMat);\n }\n}\n</code></pre> graph_adjacency_matrix.go<pre><code>/* 隣接行列で実装した無向グラフクラス */\ntype graphAdjMat struct {\n // 頂点リスト。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n vertices []int\n // 隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n adjMat [][]int\n}\n\n/* コンストラクタ */\nfunc newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat {\n // 頂点を追加する\n n := len(vertices)\n adjMat := make([][]int, n)\n for i := range adjMat {\n adjMat[i] = make([]int, n)\n }\n // グラフを初期化する\n g := &amp;graphAdjMat{\n vertices: vertices,\n adjMat: adjMat,\n }\n // 辺を追加する\n // edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n for i := range edges {\n g.addEdge(edges[i][0], edges[i][1])\n }\n return g\n}\n\n/* 頂点数を取得する */\nfunc (g *graphAdjMat) size() int {\n return len(g.vertices)\n}\n\n/* 頂点を追加する */\nfunc (g *graphAdjMat) addVertex(val int) {\n n := g.size()\n // 頂点リストに新しい頂点の値を追加する\n g.vertices = append(g.vertices, val)\n // 隣接行列に 1 行追加する\n newRow := make([]int, n)\n g.adjMat = append(g.adjMat, newRow)\n // 隣接行列に 1 列追加する\n for i := range g.adjMat {\n g.adjMat[i] = append(g.adjMat[i], 0)\n }\n}\n\n/* 頂点を削除する */\nfunc (g *graphAdjMat) removeVertex(index int) {\n if index &gt;= g.size() {\n return\n }\n // 頂点リストからインデックス index の頂点を削除する\n g.vertices = append(g.vertices[:index], g.vertices[index+1:]...)\n // 隣接行列からインデックス index の行を削除する\n g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...)\n // 隣接行列からインデックス index の列を削除する\n for i := range g.adjMat {\n g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...)\n }\n}\n\n/* 辺を追加する */\n// 引数 i, j は vertices の要素のインデックスに対応する\nfunc (g *graphAdjMat) addEdge(i, j int) {\n // 添字が範囲外または互いに等しい場合の処理\n if i &lt; 0 || j &lt; 0 || i &gt;= g.size() || j &gt;= g.size() || i == j {\n fmt.Errorf(\"%s\", \"インデックス範囲外の例外\")\n }\n // 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) == (j, i) を満たす\n g.adjMat[i][j] = 1\n g.adjMat[j][i] = 1\n}\n\n/* 辺を削除する */\n// 引数 i, j は vertices の要素のインデックスに対応する\nfunc (g *graphAdjMat) removeEdge(i, j int) {\n // 添字が範囲外または互いに等しい場合の処理\n if i &lt; 0 || j &lt; 0 || i &gt;= g.size() || j &gt;= g.size() || i == j {\n fmt.Errorf(\"%s\", \"インデックス範囲外の例外\")\n }\n g.adjMat[i][j] = 0\n g.adjMat[j][i] = 0\n}\n\n/* 隣接行列を表示する */\nfunc (g *graphAdjMat) print() {\n fmt.Printf(\"\\t頂点リスト = %v\\n\", g.vertices)\n fmt.Printf(\"\\t隣接行列 = \\n\")\n for i := range g.adjMat {\n fmt.Printf(\"\\t\\t\\t%v\\n\", g.adjMat[i])\n }\n}\n</code></pre> graph_adjacency_matrix.swift<pre><code>/* 隣接行列で実装した無向グラフクラス */\nclass GraphAdjMat {\n private var vertices: [Int] // 頂点リスト。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n private var adjMat: [[Int]] // 隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n\n /* コンストラクタ */\n init(vertices: [Int], edges: [[Int]]) {\n self.vertices = []\n adjMat = []\n // 頂点を追加する\n for val in vertices {\n addVertex(val: val)\n }\n // 辺を追加する\n // edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n for e in edges {\n addEdge(i: e[0], j: e[1])\n }\n }\n\n /* 頂点数を取得する */\n func size() -&gt; Int {\n vertices.count\n }\n\n /* 頂点を追加する */\n func addVertex(val: Int) {\n let n = size()\n // 頂点リストに新しい頂点の値を追加する\n vertices.append(val)\n // 隣接行列に 1 行追加する\n let newRow = Array(repeating: 0, count: n)\n adjMat.append(newRow)\n // 隣接行列に 1 列追加する\n for i in adjMat.indices {\n adjMat[i].append(0)\n }\n }\n\n /* 頂点を削除する */\n func removeVertex(index: Int) {\n if index &gt;= size() {\n fatalError(\"範囲外です\")\n }\n // 頂点リストからインデックス index の頂点を削除する\n vertices.remove(at: index)\n // 隣接行列からインデックス index の行を削除する\n adjMat.remove(at: index)\n // 隣接行列からインデックス index の列を削除する\n for i in adjMat.indices {\n adjMat[i].remove(at: index)\n }\n }\n\n /* 辺を追加する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n func addEdge(i: Int, j: Int) {\n // 添字が範囲外または互いに等しい場合の処理\n if i &lt; 0 || j &lt; 0 || i &gt;= size() || j &gt;= size() || i == j {\n fatalError(\"範囲外です\")\n }\n // 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) == (j, i) を満たす\n adjMat[i][j] = 1\n adjMat[j][i] = 1\n }\n\n /* 辺を削除する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n func removeEdge(i: Int, j: Int) {\n // 添字が範囲外または互いに等しい場合の処理\n if i &lt; 0 || j &lt; 0 || i &gt;= size() || j &gt;= size() || i == j {\n fatalError(\"範囲外です\")\n }\n adjMat[i][j] = 0\n adjMat[j][i] = 0\n }\n\n /* 隣接行列を表示する */\n func print() {\n Swift.print(\"頂点リスト = \", terminator: \"\")\n Swift.print(vertices)\n Swift.print(\"隣接行列 =\")\n PrintUtil.printMatrix(matrix: adjMat)\n }\n}\n</code></pre> graph_adjacency_matrix.js<pre><code>/* 隣接行列で実装した無向グラフクラス */\nclass GraphAdjMat {\n vertices; // 頂点リスト。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n adjMat; // 隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n\n /* コンストラクタ */\n constructor(vertices, edges) {\n this.vertices = [];\n this.adjMat = [];\n // 頂点を追加する\n for (const val of vertices) {\n this.addVertex(val);\n }\n // 辺を追加する\n // edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n for (const e of edges) {\n this.addEdge(e[0], e[1]);\n }\n }\n\n /* 頂点数を取得する */\n size() {\n return this.vertices.length;\n }\n\n /* 頂点を追加する */\n addVertex(val) {\n const n = this.size();\n // 頂点リストに新しい頂点の値を追加する\n this.vertices.push(val);\n // 隣接行列に 1 行追加する\n const newRow = [];\n for (let j = 0; j &lt; n; j++) {\n newRow.push(0);\n }\n this.adjMat.push(newRow);\n // 隣接行列に 1 列追加する\n for (const row of this.adjMat) {\n row.push(0);\n }\n }\n\n /* 頂点を削除する */\n removeVertex(index) {\n if (index &gt;= this.size()) {\n throw new RangeError('インデックス範囲外の例外');\n }\n // 頂点リストからインデックス index の頂点を削除する\n this.vertices.splice(index, 1);\n\n // 隣接行列からインデックス index の行を削除する\n this.adjMat.splice(index, 1);\n // 隣接行列からインデックス index の列を削除する\n for (const row of this.adjMat) {\n row.splice(index, 1);\n }\n }\n\n /* 辺を追加する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n addEdge(i, j) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= this.size() || j &gt;= this.size() || i === j) {\n throw new RangeError('インデックス範囲外の例外');\n }\n // 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) === (j, i) を満たす\n this.adjMat[i][j] = 1;\n this.adjMat[j][i] = 1;\n }\n\n /* 辺を削除する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n removeEdge(i, j) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= this.size() || j &gt;= this.size() || i === j) {\n throw new RangeError('インデックス範囲外の例外');\n }\n this.adjMat[i][j] = 0;\n this.adjMat[j][i] = 0;\n }\n\n /* 隣接行列を表示する */\n print() {\n console.log('頂点リスト = ', this.vertices);\n console.log('隣接行列 =', this.adjMat);\n }\n}\n</code></pre> graph_adjacency_matrix.ts<pre><code>/* 隣接行列で実装した無向グラフクラス */\nclass GraphAdjMat {\n vertices: number[]; // 頂点リスト。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n adjMat: number[][]; // 隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n\n /* コンストラクタ */\n constructor(vertices: number[], edges: number[][]) {\n this.vertices = [];\n this.adjMat = [];\n // 頂点を追加する\n for (const val of vertices) {\n this.addVertex(val);\n }\n // 辺を追加する\n // edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n for (const e of edges) {\n this.addEdge(e[0], e[1]);\n }\n }\n\n /* 頂点数を取得する */\n size(): number {\n return this.vertices.length;\n }\n\n /* 頂点を追加する */\n addVertex(val: number): void {\n const n: number = this.size();\n // 頂点リストに新しい頂点の値を追加する\n this.vertices.push(val);\n // 隣接行列に 1 行追加する\n const newRow: number[] = [];\n for (let j: number = 0; j &lt; n; j++) {\n newRow.push(0);\n }\n this.adjMat.push(newRow);\n // 隣接行列に 1 列追加する\n for (const row of this.adjMat) {\n row.push(0);\n }\n }\n\n /* 頂点を削除する */\n removeVertex(index: number): void {\n if (index &gt;= this.size()) {\n throw new RangeError('インデックス範囲外の例外');\n }\n // 頂点リストからインデックス index の頂点を削除する\n this.vertices.splice(index, 1);\n\n // 隣接行列からインデックス index の行を削除する\n this.adjMat.splice(index, 1);\n // 隣接行列からインデックス index の列を削除する\n for (const row of this.adjMat) {\n row.splice(index, 1);\n }\n }\n\n /* 辺を追加する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n addEdge(i: number, j: number): void {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= this.size() || j &gt;= this.size() || i === j) {\n throw new RangeError('インデックス範囲外の例外');\n }\n // 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) === (j, i) を満たす\n this.adjMat[i][j] = 1;\n this.adjMat[j][i] = 1;\n }\n\n /* 辺を削除する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n removeEdge(i: number, j: number): void {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= this.size() || j &gt;= this.size() || i === j) {\n throw new RangeError('インデックス範囲外の例外');\n }\n this.adjMat[i][j] = 0;\n this.adjMat[j][i] = 0;\n }\n\n /* 隣接行列を表示する */\n print(): void {\n console.log('頂点リスト = ', this.vertices);\n console.log('隣接行列 =', this.adjMat);\n }\n}\n</code></pre> graph_adjacency_matrix.dart<pre><code>/* 隣接行列で実装した無向グラフクラス */\nclass GraphAdjMat {\n List&lt;int&gt; vertices = []; // 頂点要素。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n List&lt;List&lt;int&gt;&gt; adjMat = []; //隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n\n /* コンストラクタ */\n GraphAdjMat(List&lt;int&gt; vertices, List&lt;List&lt;int&gt;&gt; edges) {\n this.vertices = [];\n this.adjMat = [];\n // 頂点を追加する\n for (int val in vertices) {\n addVertex(val);\n }\n // 辺を追加する\n // edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n for (List&lt;int&gt; e in edges) {\n addEdge(e[0], e[1]);\n }\n }\n\n /* 頂点数を取得する */\n int size() {\n return vertices.length;\n }\n\n /* 頂点を追加する */\n void addVertex(int val) {\n int n = size();\n // 頂点リストに新しい頂点の値を追加する\n vertices.add(val);\n // 隣接行列に 1 行追加する\n List&lt;int&gt; newRow = List.filled(n, 0, growable: true);\n adjMat.add(newRow);\n // 隣接行列に 1 列追加する\n for (List&lt;int&gt; row in adjMat) {\n row.add(0);\n }\n }\n\n /* 頂点を削除する */\n void removeVertex(int index) {\n if (index &gt;= size()) {\n throw IndexError;\n }\n // 頂点リストからインデックス index の頂点を削除する\n vertices.removeAt(index);\n // 隣接行列からインデックス index の行を削除する\n adjMat.removeAt(index);\n // 隣接行列からインデックス index の列を削除する\n for (List&lt;int&gt; row in adjMat) {\n row.removeAt(index);\n }\n }\n\n /* 辺を追加する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n void addEdge(int i, int j) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= size() || j &gt;= size() || i == j) {\n throw IndexError;\n }\n // 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) == (j, i) を満たす\n adjMat[i][j] = 1;\n adjMat[j][i] = 1;\n }\n\n /* 辺を削除する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n void removeEdge(int i, int j) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= size() || j &gt;= size() || i == j) {\n throw IndexError;\n }\n adjMat[i][j] = 0;\n adjMat[j][i] = 0;\n }\n\n /* 隣接行列を表示する */\n void printAdjMat() {\n print(\"頂点リスト = $vertices\");\n print(\"隣接行列 = \");\n printMatrix(adjMat);\n }\n}\n</code></pre> graph_adjacency_matrix.rs<pre><code>/* 隣接行列で実装した無向グラフの型 */\npub struct GraphAdjMat {\n // 頂点リスト。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n pub vertices: Vec&lt;i32&gt;,\n // 隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n pub adj_mat: Vec&lt;Vec&lt;i32&gt;&gt;,\n}\n\nimpl GraphAdjMat {\n /* コンストラクタ */\n pub fn new(vertices: Vec&lt;i32&gt;, edges: Vec&lt;[usize; 2]&gt;) -&gt; Self {\n let mut graph = GraphAdjMat {\n vertices: vec![],\n adj_mat: vec![],\n };\n // 頂点を追加する\n for val in vertices {\n graph.add_vertex(val);\n }\n // 辺を追加する\n // edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n for edge in edges {\n graph.add_edge(edge[0], edge[1])\n }\n\n graph\n }\n\n /* 頂点数を取得する */\n pub fn size(&amp;self) -&gt; usize {\n self.vertices.len()\n }\n\n /* 頂点を追加する */\n pub fn add_vertex(&amp;mut self, val: i32) {\n let n = self.size();\n // 頂点リストに新しい頂点の値を追加する\n self.vertices.push(val);\n // 隣接行列に 1 行追加する\n self.adj_mat.push(vec![0; n]);\n // 隣接行列に 1 列追加する\n for row in self.adj_mat.iter_mut() {\n row.push(0);\n }\n }\n\n /* 頂点を削除する */\n pub fn remove_vertex(&amp;mut self, index: usize) {\n if index &gt;= self.size() {\n panic!(\"インデックスのエラー\")\n }\n // 頂点リストからインデックス index の頂点を削除する\n self.vertices.remove(index);\n // 隣接行列からインデックス index の行を削除する\n self.adj_mat.remove(index);\n // 隣接行列からインデックス index の列を削除する\n for row in self.adj_mat.iter_mut() {\n row.remove(index);\n }\n }\n\n /* 辺を追加する */\n pub fn add_edge(&amp;mut self, i: usize, j: usize) {\n // 引数 i, j は vertices の要素のインデックスに対応する\n // 添字が範囲外または互いに等しい場合の処理\n if i &gt;= self.size() || j &gt;= self.size() || i == j {\n panic!(\"インデックスのエラー\")\n }\n // 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) == (j, i) を満たす\n self.adj_mat[i][j] = 1;\n self.adj_mat[j][i] = 1;\n }\n\n /* 辺を削除する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n pub fn remove_edge(&amp;mut self, i: usize, j: usize) {\n // 引数 i, j は vertices の要素のインデックスに対応する\n // 添字が範囲外または互いに等しい場合の処理\n if i &gt;= self.size() || j &gt;= self.size() || i == j {\n panic!(\"インデックスのエラー\")\n }\n self.adj_mat[i][j] = 0;\n self.adj_mat[j][i] = 0;\n }\n\n /* 隣接行列を表示する */\n pub fn print(&amp;self) {\n println!(\"頂点リスト = {:?}\", self.vertices);\n println!(\"隣接行列 =\");\n println!(\"[\");\n for row in &amp;self.adj_mat {\n println!(\" {:?},\", row);\n }\n println!(\"]\")\n }\n}\n</code></pre> graph_adjacency_matrix.c<pre><code>/* 隣接行列で実装した無向グラフの構造体 */\ntypedef struct {\n int vertices[MAX_SIZE];\n int adjMat[MAX_SIZE][MAX_SIZE];\n int size;\n} GraphAdjMat;\n\n/* コンストラクタ */\nGraphAdjMat *newGraphAdjMat() {\n GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat));\n graph-&gt;size = 0;\n for (int i = 0; i &lt; MAX_SIZE; i++) {\n for (int j = 0; j &lt; MAX_SIZE; j++) {\n graph-&gt;adjMat[i][j] = 0;\n }\n }\n return graph;\n}\n\n/* デストラクタ */\nvoid delGraphAdjMat(GraphAdjMat *graph) {\n free(graph);\n}\n\n/* 頂点を追加する */\nvoid addVertex(GraphAdjMat *graph, int val) {\n if (graph-&gt;size == MAX_SIZE) {\n fprintf(stderr, \"グラフの頂点数が上限に達しました\\n\");\n return;\n }\n // n 番目の頂点を追加し、n 行目と n 列目をゼロにする\n int n = graph-&gt;size;\n graph-&gt;vertices[n] = val;\n for (int i = 0; i &lt;= n; i++) {\n graph-&gt;adjMat[n][i] = graph-&gt;adjMat[i][n] = 0;\n }\n graph-&gt;size++;\n}\n\n/* 頂点を削除する */\nvoid removeVertex(GraphAdjMat *graph, int index) {\n if (index &lt; 0 || index &gt;= graph-&gt;size) {\n fprintf(stderr, \"頂点のインデックスが範囲外です\\n\");\n return;\n }\n // 頂点リストからインデックス index の頂点を削除する\n for (int i = index; i &lt; graph-&gt;size - 1; i++) {\n graph-&gt;vertices[i] = graph-&gt;vertices[i + 1];\n }\n // 隣接行列からインデックス index の行を削除する\n for (int i = index; i &lt; graph-&gt;size - 1; i++) {\n for (int j = 0; j &lt; graph-&gt;size; j++) {\n graph-&gt;adjMat[i][j] = graph-&gt;adjMat[i + 1][j];\n }\n }\n // 隣接行列からインデックス index の列を削除する\n for (int i = 0; i &lt; graph-&gt;size; i++) {\n for (int j = index; j &lt; graph-&gt;size - 1; j++) {\n graph-&gt;adjMat[i][j] = graph-&gt;adjMat[i][j + 1];\n }\n }\n graph-&gt;size--;\n}\n\n/* 辺を追加する */\n// 引数 i, j は vertices の要素のインデックスに対応する\nvoid addEdge(GraphAdjMat *graph, int i, int j) {\n if (i &lt; 0 || j &lt; 0 || i &gt;= graph-&gt;size || j &gt;= graph-&gt;size || i == j) {\n fprintf(stderr, \"辺のインデックスが範囲外か、互いに等しい値です\\n\");\n return;\n }\n graph-&gt;adjMat[i][j] = 1;\n graph-&gt;adjMat[j][i] = 1;\n}\n\n/* 辺を削除する */\n// 引数 i, j は vertices の要素のインデックスに対応する\nvoid removeEdge(GraphAdjMat *graph, int i, int j) {\n if (i &lt; 0 || j &lt; 0 || i &gt;= graph-&gt;size || j &gt;= graph-&gt;size || i == j) {\n fprintf(stderr, \"辺のインデックスが範囲外か、互いに等しい値です\\n\");\n return;\n }\n graph-&gt;adjMat[i][j] = 0;\n graph-&gt;adjMat[j][i] = 0;\n}\n\n/* 隣接行列を表示する */\nvoid printGraphAdjMat(GraphAdjMat *graph) {\n printf(\"頂点リスト = \");\n printArray(graph-&gt;vertices, graph-&gt;size);\n printf(\"隣接行列 =\\n\");\n for (int i = 0; i &lt; graph-&gt;size; i++) {\n printArray(graph-&gt;adjMat[i], graph-&gt;size);\n }\n}\n</code></pre> graph_adjacency_matrix.kt<pre><code>/* 隣接行列で実装した無向グラフクラス */\nclass GraphAdjMat(vertices: IntArray, edges: Array&lt;IntArray&gt;) {\n val vertices = mutableListOf&lt;Int&gt;() // 頂点リスト。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n val adjMat = mutableListOf&lt;MutableList&lt;Int&gt;&gt;() // 隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n\n /* コンストラクタ */\n init {\n // 頂点を追加する\n for (vertex in vertices) {\n addVertex(vertex)\n }\n // 辺を追加する\n // edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n for (edge in edges) {\n addEdge(edge[0], edge[1])\n }\n }\n\n /* 頂点数を取得する */\n fun size(): Int {\n return vertices.size\n }\n\n /* 頂点を追加する */\n fun addVertex(_val: Int) {\n val n = size()\n // 頂点リストに新しい頂点の値を追加する\n vertices.add(_val)\n // 隣接行列に 1 行追加する\n val newRow = mutableListOf&lt;Int&gt;()\n for (j in 0..&lt;n) {\n newRow.add(0)\n }\n adjMat.add(newRow)\n // 隣接行列に 1 列追加する\n for (row in adjMat) {\n row.add(0)\n }\n }\n\n /* 頂点を削除する */\n fun removeVertex(index: Int) {\n if (index &gt;= size())\n throw IndexOutOfBoundsException()\n // 頂点リストからインデックス index の頂点を削除する\n vertices.removeAt(index)\n // 隣接行列からインデックス index の行を削除する\n adjMat.removeAt(index)\n // 隣接行列からインデックス index の列を削除する\n for (row in adjMat) {\n row.removeAt(index)\n }\n }\n\n /* 辺を追加する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n fun addEdge(i: Int, j: Int) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= size() || j &gt;= size() || i == j)\n throw IndexOutOfBoundsException()\n // 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) == (j, i) を満たす\n adjMat[i][j] = 1\n adjMat[j][i] = 1\n }\n\n /* 辺を削除する */\n // 引数 i, j は vertices の要素のインデックスに対応する\n fun removeEdge(i: Int, j: Int) {\n // 添字が範囲外または互いに等しい場合の処理\n if (i &lt; 0 || j &lt; 0 || i &gt;= size() || j &gt;= size() || i == j)\n throw IndexOutOfBoundsException()\n adjMat[i][j] = 0\n adjMat[j][i] = 0\n }\n\n /* 隣接行列を表示する */\n fun print() {\n print(\"頂点リスト = \")\n println(vertices)\n println(\"隣接行列 =\")\n printMatrix(adjMat)\n }\n}\n</code></pre> graph_adjacency_matrix.rb<pre><code>### 隣接行列で実装した無向グラフクラス ###\nclass GraphAdjMat\n def initialize(vertices, edges)\n ### コンストラクタ ###\n # 頂点リスト。要素は「頂点の値」、インデックスは「頂点のインデックス」を表す\n @vertices = []\n # 隣接行列。行と列のインデックスは「頂点のインデックス」に対応する\n @adj_mat = []\n # 頂点を追加する\n vertices.each { |val| add_vertex(val) }\n # 辺を追加する\n # edges の要素は頂点のインデックス、すなわち vertices の要素のインデックスを表すことに注意\n edges.each { |e| add_edge(e[0], e[1]) }\n end\n\n ### 頂点数を取得する ###\n def size\n @vertices.length\n end\n\n ### 頂点を追加する ###\n def add_vertex(val)\n n = size\n # 頂点リストに新しい頂点の値を追加する\n @vertices &lt;&lt; val\n # 隣接行列に 1 行追加する\n new_row = Array.new(n, 0)\n @adj_mat &lt;&lt; new_row\n # 隣接行列に 1 列追加する\n @adj_mat.each { |row| row &lt;&lt; 0 }\n end\n\n ### 頂点を削除する ###\n def remove_vertex(index)\n raise IndexError if index &gt;= size\n\n # 頂点リストからインデックス index の頂点を削除する\n @vertices.delete_at(index)\n # 隣接行列からインデックス index の行を削除する\n @adj_mat.delete_at(index)\n # 隣接行列からインデックス index の列を削除する\n @adj_mat.each { |row| row.delete_at(index) }\n end\n\n ### 辺を追加する ###\n def add_edge(i, j)\n # 引数 i, j は vertices の要素のインデックスに対応する\n # 添字が範囲外または互いに等しい場合の処理\n if i &lt; 0 || j &lt; 0 || i &gt;= size || j &gt;= size || i == j\n raise IndexError\n end\n # 無向グラフの隣接行列は主対角線に関して対称であり、(i, j) == (j, i) を満たす\n @adj_mat[i][j] = 1\n @adj_mat[j][i] = 1\n end\n\n ### 辺を削除する ###\n def remove_edge(i, j)\n # 引数 i, j は vertices の要素のインデックスに対応する\n # 添字が範囲外または互いに等しい場合の処理\n if i &lt; 0 || j &lt; 0 || i &gt;= size || j &gt;= size || i == j\n raise IndexError\n end\n @adj_mat[i][j] = 0\n @adj_mat[j][i] = 0\n end\n\n ### 隣接行列を表示する ###\n def __print__\n puts \"頂点リスト = #{@vertices}\"\n puts '隣接行列 ='\n print_matrix(@adj_mat)\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 9 章   グラフ","9.2   グラフの基本操作"],"tags":[]},{"location":"chapter_graph/graph_operations/#922","level":2,"title":"9.2.2   隣接リストによる実装","text":"<p>無向グラフの頂点数を \\(n\\)、辺の総数を \\(m\\) とすると、図 9-8 の方法で各種操作を実装できます。</p> <ul> <li>辺の追加:頂点に対応する連結リストの末尾に辺を追加するだけなので、\\(O(1)\\) 時間です。無向グラフのため、両方向の辺を同時に追加する必要があります。</li> <li>辺の削除:頂点に対応する連結リストで指定した辺を探して削除するため、\\(O(m)\\) 時間かかります。無向グラフでは、両方向の辺を同時に削除する必要があります。</li> <li>頂点の追加:隣接リストに連結リストを一つ追加し、新しい頂点をその先頭ノードにします。\\(O(1)\\) 時間です。</li> <li>頂点の削除:隣接リスト全体を走査し、指定した頂点を含むすべての辺を削除するため、\\(O(n + m)\\) 時間かかります。</li> <li>初期化:隣接リストに \\(n\\) 個の頂点と \\(2m\\) 本の辺を作るため、\\(O(n + m)\\) 時間かかります。</li> </ul> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 9-8   隣接リストの初期化、辺の追加・削除、頂点の追加・削除 </p> <p>隣接リストの実装を次に示します。実際のコードは、図 9-8 と次の点が異なります。</p> <ul> <li>頂点の追加と削除をしやすくし、コードを簡潔にするため、連結リストの代わりにリスト(動的配列)を使います。</li> <li>隣接リストをハッシュテーブルに格納します。<code>key</code> は頂点のインスタンス、<code>value</code> はその頂点に隣接する頂点のリスト(連結リスト)です。</li> </ul> <p>また、隣接リストでは <code>Vertex</code> クラスで頂点を表します。隣接行列と同じようにリストのインデックスで頂点を区別すると、インデックス \\(i\\) の頂点を削除する際に、隣接リスト全体を走査し、\\(i\\) より大きいインデックスをすべて \\(1\\) ずつ減らす必要があり、非効率だからです。各頂点を固有の <code>Vertex</code> インスタンスにすれば、ある頂点を削除しても、ほかの頂点を変更する必要がありません。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_adjacency_list.py<pre><code>class GraphAdjList:\n \"\"\"隣接リストで実装した無向グラフクラス\"\"\"\n\n def __init__(self, edges: list[list[Vertex]]):\n \"\"\"コンストラクタ\"\"\"\n # 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n self.adj_list = dict[Vertex, list[Vertex]]()\n # すべての頂点と辺を追加する\n for edge in edges:\n self.add_vertex(edge[0])\n self.add_vertex(edge[1])\n self.add_edge(edge[0], edge[1])\n\n def size(self) -&gt; int:\n \"\"\"頂点数を取得する\"\"\"\n return len(self.adj_list)\n\n def add_edge(self, vet1: Vertex, vet2: Vertex):\n \"\"\"辺を追加する\"\"\"\n if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:\n raise ValueError()\n # 辺 vet1 - vet2 を追加する\n self.adj_list[vet1].append(vet2)\n self.adj_list[vet2].append(vet1)\n\n def remove_edge(self, vet1: Vertex, vet2: Vertex):\n \"\"\"辺を削除する\"\"\"\n if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2:\n raise ValueError()\n # 辺 vet1 - vet2 を削除する\n self.adj_list[vet1].remove(vet2)\n self.adj_list[vet2].remove(vet1)\n\n def add_vertex(self, vet: Vertex):\n \"\"\"頂点を追加する\"\"\"\n if vet in self.adj_list:\n return\n # 隣接リストに新しい連結リストを追加する\n self.adj_list[vet] = []\n\n def remove_vertex(self, vet: Vertex):\n \"\"\"頂点を削除する\"\"\"\n if vet not in self.adj_list:\n raise ValueError()\n # 隣接リストから頂点 vet に対応する連結リストを削除する\n self.adj_list.pop(vet)\n # 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n for vertex in self.adj_list:\n if vet in self.adj_list[vertex]:\n self.adj_list[vertex].remove(vet)\n\n def print(self):\n \"\"\"隣接リストを表示する\"\"\"\n print(\"隣接リスト =\")\n for vertex in self.adj_list:\n tmp = [v.val for v in self.adj_list[vertex]]\n print(f\"{vertex.val}: {tmp},\")\n</code></pre> graph_adjacency_list.cpp<pre><code>/* 隣接リストで実装した無向グラフクラス */\nclass GraphAdjList {\n public:\n // 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n unordered_map&lt;Vertex *, vector&lt;Vertex *&gt;&gt; adjList;\n\n /* vector から指定されたノードを削除する */\n void remove(vector&lt;Vertex *&gt; &amp;vec, Vertex *vet) {\n for (int i = 0; i &lt; vec.size(); i++) {\n if (vec[i] == vet) {\n vec.erase(vec.begin() + i);\n break;\n }\n }\n }\n\n /* コンストラクタ */\n GraphAdjList(const vector&lt;vector&lt;Vertex *&gt;&gt; &amp;edges) {\n // すべての頂点と辺を追加する\n for (const vector&lt;Vertex *&gt; &amp;edge : edges) {\n addVertex(edge[0]);\n addVertex(edge[1]);\n addEdge(edge[0], edge[1]);\n }\n }\n\n /* 頂点数を取得する */\n int size() {\n return adjList.size();\n }\n\n /* 辺を追加する */\n void addEdge(Vertex *vet1, Vertex *vet2) {\n if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)\n throw invalid_argument(\"頂点が存在しません\");\n // 辺 vet1 - vet2 を追加する\n adjList[vet1].push_back(vet2);\n adjList[vet2].push_back(vet1);\n }\n\n /* 辺を削除する */\n void removeEdge(Vertex *vet1, Vertex *vet2) {\n if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)\n throw invalid_argument(\"頂点が存在しません\");\n // 辺 vet1 - vet2 を削除する\n remove(adjList[vet1], vet2);\n remove(adjList[vet2], vet1);\n }\n\n /* 頂点を追加する */\n void addVertex(Vertex *vet) {\n if (adjList.count(vet))\n return;\n // 隣接リストに新しい連結リストを追加する\n adjList[vet] = vector&lt;Vertex *&gt;();\n }\n\n /* 頂点を削除する */\n void removeVertex(Vertex *vet) {\n if (!adjList.count(vet))\n throw invalid_argument(\"頂点が存在しません\");\n // 隣接リストから頂点 vet に対応する連結リストを削除する\n adjList.erase(vet);\n // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n for (auto &amp;adj : adjList) {\n remove(adj.second, vet);\n }\n }\n\n /* 隣接リストを表示する */\n void print() {\n cout &lt;&lt; \"隣接リスト =\" &lt;&lt; endl;\n for (auto &amp;adj : adjList) {\n const auto &amp;key = adj.first;\n const auto &amp;vec = adj.second;\n cout &lt;&lt; key-&gt;val &lt;&lt; \": \";\n printVector(vetsToVals(vec));\n }\n }\n};\n</code></pre> graph_adjacency_list.java<pre><code>/* 隣接リストで実装した無向グラフクラス */\nclass GraphAdjList {\n // 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n Map&lt;Vertex, List&lt;Vertex&gt;&gt; adjList;\n\n /* コンストラクタ */\n public GraphAdjList(Vertex[][] edges) {\n this.adjList = new HashMap&lt;&gt;();\n // すべての頂点と辺を追加する\n for (Vertex[] edge : edges) {\n addVertex(edge[0]);\n addVertex(edge[1]);\n addEdge(edge[0], edge[1]);\n }\n }\n\n /* 頂点数を取得する */\n public int size() {\n return adjList.size();\n }\n\n /* 辺を追加する */\n public void addEdge(Vertex vet1, Vertex vet2) {\n if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n throw new IllegalArgumentException();\n // 辺 vet1 - vet2 を追加する\n adjList.get(vet1).add(vet2);\n adjList.get(vet2).add(vet1);\n }\n\n /* 辺を削除する */\n public void removeEdge(Vertex vet1, Vertex vet2) {\n if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n throw new IllegalArgumentException();\n // 辺 vet1 - vet2 を削除する\n adjList.get(vet1).remove(vet2);\n adjList.get(vet2).remove(vet1);\n }\n\n /* 頂点を追加する */\n public void addVertex(Vertex vet) {\n if (adjList.containsKey(vet))\n return;\n // 隣接リストに新しい連結リストを追加する\n adjList.put(vet, new ArrayList&lt;&gt;());\n }\n\n /* 頂点を削除する */\n public void removeVertex(Vertex vet) {\n if (!adjList.containsKey(vet))\n throw new IllegalArgumentException();\n // 隣接リストから頂点 vet に対応する連結リストを削除する\n adjList.remove(vet);\n // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n for (List&lt;Vertex&gt; list : adjList.values()) {\n list.remove(vet);\n }\n }\n\n /* 隣接リストを表示する */\n public void print() {\n System.out.println(\"隣接リスト =\");\n for (Map.Entry&lt;Vertex, List&lt;Vertex&gt;&gt; pair : adjList.entrySet()) {\n List&lt;Integer&gt; tmp = new ArrayList&lt;&gt;();\n for (Vertex vertex : pair.getValue())\n tmp.add(vertex.val);\n System.out.println(pair.getKey().val + \": \" + tmp + \",\");\n }\n }\n}\n</code></pre> graph_adjacency_list.cs<pre><code>/* 隣接リストで実装した無向グラフクラス */\nclass GraphAdjList {\n // 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n public Dictionary&lt;Vertex, List&lt;Vertex&gt;&gt; adjList;\n\n /* コンストラクタ */\n public GraphAdjList(Vertex[][] edges) {\n adjList = [];\n // すべての頂点と辺を追加する\n foreach (Vertex[] edge in edges) {\n AddVertex(edge[0]);\n AddVertex(edge[1]);\n AddEdge(edge[0], edge[1]);\n }\n }\n\n /* 頂点数を取得する */\n int Size() {\n return adjList.Count;\n }\n\n /* 辺を追加する */\n public void AddEdge(Vertex vet1, Vertex vet2) {\n if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2)\n throw new InvalidOperationException();\n // 辺 vet1 - vet2 を追加する\n adjList[vet1].Add(vet2);\n adjList[vet2].Add(vet1);\n }\n\n /* 辺を削除する */\n public void RemoveEdge(Vertex vet1, Vertex vet2) {\n if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2)\n throw new InvalidOperationException();\n // 辺 vet1 - vet2 を削除する\n adjList[vet1].Remove(vet2);\n adjList[vet2].Remove(vet1);\n }\n\n /* 頂点を追加する */\n public void AddVertex(Vertex vet) {\n if (adjList.ContainsKey(vet))\n return;\n // 隣接リストに新しい連結リストを追加する\n adjList.Add(vet, []);\n }\n\n /* 頂点を削除する */\n public void RemoveVertex(Vertex vet) {\n if (!adjList.ContainsKey(vet))\n throw new InvalidOperationException();\n // 隣接リストから頂点 vet に対応する連結リストを削除する\n adjList.Remove(vet);\n // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n foreach (List&lt;Vertex&gt; list in adjList.Values) {\n list.Remove(vet);\n }\n }\n\n /* 隣接リストを表示する */\n public void Print() {\n Console.WriteLine(\"隣接リスト =\");\n foreach (KeyValuePair&lt;Vertex, List&lt;Vertex&gt;&gt; pair in adjList) {\n List&lt;int&gt; tmp = [];\n foreach (Vertex vertex in pair.Value)\n tmp.Add(vertex.val);\n Console.WriteLine(pair.Key.val + \": [\" + string.Join(\", \", tmp) + \"],\");\n }\n }\n}\n</code></pre> graph_adjacency_list.go<pre><code>/* 隣接リストで実装した無向グラフクラス */\ntype graphAdjList struct {\n // 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n adjList map[Vertex][]Vertex\n}\n\n/* コンストラクタ */\nfunc newGraphAdjList(edges [][]Vertex) *graphAdjList {\n g := &amp;graphAdjList{\n adjList: make(map[Vertex][]Vertex),\n }\n // すべての頂点と辺を追加する\n for _, edge := range edges {\n g.addVertex(edge[0])\n g.addVertex(edge[1])\n g.addEdge(edge[0], edge[1])\n }\n return g\n}\n\n/* 頂点数を取得する */\nfunc (g *graphAdjList) size() int {\n return len(g.adjList)\n}\n\n/* 辺を追加する */\nfunc (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) {\n _, ok1 := g.adjList[vet1]\n _, ok2 := g.adjList[vet2]\n if !ok1 || !ok2 || vet1 == vet2 {\n panic(\"エラー\")\n }\n // 辺 vet1 - vet2 を追加し、無名の struct{} を追加する\n g.adjList[vet1] = append(g.adjList[vet1], vet2)\n g.adjList[vet2] = append(g.adjList[vet2], vet1)\n}\n\n/* 辺を削除する */\nfunc (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) {\n _, ok1 := g.adjList[vet1]\n _, ok2 := g.adjList[vet2]\n if !ok1 || !ok2 || vet1 == vet2 {\n panic(\"エラー\")\n }\n // 辺 vet1 - vet2 を削除する\n g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2)\n g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1)\n}\n\n/* 頂点を追加する */\nfunc (g *graphAdjList) addVertex(vet Vertex) {\n _, ok := g.adjList[vet]\n if ok {\n return\n }\n // 隣接リストに新しい連結リストを追加する\n g.adjList[vet] = make([]Vertex, 0)\n}\n\n/* 頂点を削除する */\nfunc (g *graphAdjList) removeVertex(vet Vertex) {\n _, ok := g.adjList[vet]\n if !ok {\n panic(\"エラー\")\n }\n // 隣接リストから頂点 vet に対応する連結リストを削除する\n delete(g.adjList, vet)\n // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n for v, list := range g.adjList {\n g.adjList[v] = DeleteSliceElms(list, vet)\n }\n}\n\n/* 隣接リストを表示する */\nfunc (g *graphAdjList) print() {\n var builder strings.Builder\n fmt.Printf(\"隣接リスト = \\n\")\n for k, v := range g.adjList {\n builder.WriteString(\"\\t\\t\" + strconv.Itoa(k.Val) + \": \")\n for _, vet := range v {\n builder.WriteString(strconv.Itoa(vet.Val) + \" \")\n }\n fmt.Println(builder.String())\n builder.Reset()\n }\n}\n</code></pre> graph_adjacency_list.swift<pre><code>/* 隣接リストで実装した無向グラフクラス */\nclass GraphAdjList {\n // 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n public private(set) var adjList: [Vertex: [Vertex]]\n\n /* コンストラクタ */\n public init(edges: [[Vertex]]) {\n adjList = [:]\n // すべての頂点と辺を追加する\n for edge in edges {\n addVertex(vet: edge[0])\n addVertex(vet: edge[1])\n addEdge(vet1: edge[0], vet2: edge[1])\n }\n }\n\n /* 頂点数を取得する */\n public func size() -&gt; Int {\n adjList.count\n }\n\n /* 辺を追加する */\n public func addEdge(vet1: Vertex, vet2: Vertex) {\n if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\n fatalError(\"引数が不正です\")\n }\n // 辺 vet1 - vet2 を追加する\n adjList[vet1]?.append(vet2)\n adjList[vet2]?.append(vet1)\n }\n\n /* 辺を削除する */\n public func removeEdge(vet1: Vertex, vet2: Vertex) {\n if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 {\n fatalError(\"引数が不正です\")\n }\n // 辺 vet1 - vet2 を削除する\n adjList[vet1]?.removeAll { $0 == vet2 }\n adjList[vet2]?.removeAll { $0 == vet1 }\n }\n\n /* 頂点を追加する */\n public func addVertex(vet: Vertex) {\n if adjList[vet] != nil {\n return\n }\n // 隣接リストに新しい連結リストを追加する\n adjList[vet] = []\n }\n\n /* 頂点を削除する */\n public func removeVertex(vet: Vertex) {\n if adjList[vet] == nil {\n fatalError(\"引数が不正です\")\n }\n // 隣接リストから頂点 vet に対応する連結リストを削除する\n adjList.removeValue(forKey: vet)\n // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n for key in adjList.keys {\n adjList[key]?.removeAll { $0 == vet }\n }\n }\n\n /* 隣接リストを表示する */\n public func print() {\n Swift.print(\"隣接リスト =\")\n for (vertex, list) in adjList {\n let list = list.map { $0.val }\n Swift.print(\"\\(vertex.val): \\(list),\")\n }\n }\n}\n</code></pre> graph_adjacency_list.js<pre><code>/* 隣接リストで実装した無向グラフクラス */\nclass GraphAdjList {\n // 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n adjList;\n\n /* コンストラクタ */\n constructor(edges) {\n this.adjList = new Map();\n // すべての頂点と辺を追加する\n for (const edge of edges) {\n this.addVertex(edge[0]);\n this.addVertex(edge[1]);\n this.addEdge(edge[0], edge[1]);\n }\n }\n\n /* 頂点数を取得する */\n size() {\n return this.adjList.size;\n }\n\n /* 辺を追加する */\n addEdge(vet1, vet2) {\n if (\n !this.adjList.has(vet1) ||\n !this.adjList.has(vet2) ||\n vet1 === vet2\n ) {\n throw new Error('不正な引数の例外');\n }\n // 辺 vet1 - vet2 を追加する\n this.adjList.get(vet1).push(vet2);\n this.adjList.get(vet2).push(vet1);\n }\n\n /* 辺を削除する */\n removeEdge(vet1, vet2) {\n if (\n !this.adjList.has(vet1) ||\n !this.adjList.has(vet2) ||\n vet1 === vet2 ||\n this.adjList.get(vet1).indexOf(vet2) === -1\n ) {\n throw new Error('不正な引数の例外');\n }\n // 辺 vet1 - vet2 を削除する\n this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1);\n this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1);\n }\n\n /* 頂点を追加する */\n addVertex(vet) {\n if (this.adjList.has(vet)) return;\n // 隣接リストに新しい連結リストを追加する\n this.adjList.set(vet, []);\n }\n\n /* 頂点を削除する */\n removeVertex(vet) {\n if (!this.adjList.has(vet)) {\n throw new Error('不正な引数の例外');\n }\n // 隣接リストから頂点 vet に対応する連結リストを削除する\n this.adjList.delete(vet);\n // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n for (const set of this.adjList.values()) {\n const index = set.indexOf(vet);\n if (index &gt; -1) {\n set.splice(index, 1);\n }\n }\n }\n\n /* 隣接リストを表示する */\n print() {\n console.log('隣接リスト =');\n for (const [key, value] of this.adjList) {\n const tmp = [];\n for (const vertex of value) {\n tmp.push(vertex.val);\n }\n console.log(key.val + ': ' + tmp.join());\n }\n }\n}\n</code></pre> graph_adjacency_list.ts<pre><code>/* 隣接リストで実装した無向グラフクラス */\nclass GraphAdjList {\n // 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n adjList: Map&lt;Vertex, Vertex[]&gt;;\n\n /* コンストラクタ */\n constructor(edges: Vertex[][]) {\n this.adjList = new Map();\n // すべての頂点と辺を追加する\n for (const edge of edges) {\n this.addVertex(edge[0]);\n this.addVertex(edge[1]);\n this.addEdge(edge[0], edge[1]);\n }\n }\n\n /* 頂点数を取得する */\n size(): number {\n return this.adjList.size;\n }\n\n /* 辺を追加する */\n addEdge(vet1: Vertex, vet2: Vertex): void {\n if (\n !this.adjList.has(vet1) ||\n !this.adjList.has(vet2) ||\n vet1 === vet2\n ) {\n throw new Error('不正な引数の例外');\n }\n // 辺 vet1 - vet2 を追加する\n this.adjList.get(vet1).push(vet2);\n this.adjList.get(vet2).push(vet1);\n }\n\n /* 辺を削除する */\n removeEdge(vet1: Vertex, vet2: Vertex): void {\n if (\n !this.adjList.has(vet1) ||\n !this.adjList.has(vet2) ||\n vet1 === vet2 ||\n this.adjList.get(vet1).indexOf(vet2) === -1\n ) {\n throw new Error('不正な引数の例外');\n }\n // 辺 vet1 - vet2 を削除する\n this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1);\n this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1);\n }\n\n /* 頂点を追加する */\n addVertex(vet: Vertex): void {\n if (this.adjList.has(vet)) return;\n // 隣接リストに新しい連結リストを追加する\n this.adjList.set(vet, []);\n }\n\n /* 頂点を削除する */\n removeVertex(vet: Vertex): void {\n if (!this.adjList.has(vet)) {\n throw new Error('不正な引数の例外');\n }\n // 隣接リストから頂点 vet に対応する連結リストを削除する\n this.adjList.delete(vet);\n // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n for (const set of this.adjList.values()) {\n const index: number = set.indexOf(vet);\n if (index &gt; -1) {\n set.splice(index, 1);\n }\n }\n }\n\n /* 隣接リストを表示する */\n print(): void {\n console.log('隣接リスト =');\n for (const [key, value] of this.adjList.entries()) {\n const tmp = [];\n for (const vertex of value) {\n tmp.push(vertex.val);\n }\n console.log(key.val + ': ' + tmp.join());\n }\n }\n}\n</code></pre> graph_adjacency_list.dart<pre><code>/* 隣接リストで実装した無向グラフクラス */\nclass GraphAdjList {\n // 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n Map&lt;Vertex, List&lt;Vertex&gt;&gt; adjList = {};\n\n /* コンストラクタ */\n GraphAdjList(List&lt;List&lt;Vertex&gt;&gt; edges) {\n for (List&lt;Vertex&gt; edge in edges) {\n addVertex(edge[0]);\n addVertex(edge[1]);\n addEdge(edge[0], edge[1]);\n }\n }\n\n /* 頂点数を取得する */\n int size() {\n return adjList.length;\n }\n\n /* 辺を追加する */\n void addEdge(Vertex vet1, Vertex vet2) {\n if (!adjList.containsKey(vet1) ||\n !adjList.containsKey(vet2) ||\n vet1 == vet2) {\n throw ArgumentError;\n }\n // 辺 vet1 - vet2 を追加する\n adjList[vet1]!.add(vet2);\n adjList[vet2]!.add(vet1);\n }\n\n /* 辺を削除する */\n void removeEdge(Vertex vet1, Vertex vet2) {\n if (!adjList.containsKey(vet1) ||\n !adjList.containsKey(vet2) ||\n vet1 == vet2) {\n throw ArgumentError;\n }\n // 辺 vet1 - vet2 を削除する\n adjList[vet1]!.remove(vet2);\n adjList[vet2]!.remove(vet1);\n }\n\n /* 頂点を追加する */\n void addVertex(Vertex vet) {\n if (adjList.containsKey(vet)) return;\n // 隣接リストに新しい連結リストを追加する\n adjList[vet] = [];\n }\n\n /* 頂点を削除する */\n void removeVertex(Vertex vet) {\n if (!adjList.containsKey(vet)) {\n throw ArgumentError;\n }\n // 隣接リストから頂点 vet に対応する連結リストを削除する\n adjList.remove(vet);\n // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n adjList.forEach((key, value) {\n value.remove(vet);\n });\n }\n\n /* 隣接リストを表示する */\n void printAdjList() {\n print(\"隣接リスト =\");\n adjList.forEach((key, value) {\n List&lt;int&gt; tmp = [];\n for (Vertex vertex in value) {\n tmp.add(vertex.val);\n }\n print(\"${key.val}: $tmp,\");\n });\n }\n}\n</code></pre> graph_adjacency_list.rs<pre><code>/* 隣接リストで実装した無向グラフの型 */\npub struct GraphAdjList {\n // 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n pub adj_list: HashMap&lt;Vertex, Vec&lt;Vertex&gt;&gt;, // 値の部分には HashSet&lt;Vertex&gt; のほうがよいかもしれない?\n}\n\nimpl GraphAdjList {\n /* コンストラクタ */\n pub fn new(edges: Vec&lt;[Vertex; 2]&gt;) -&gt; Self {\n let mut graph = GraphAdjList {\n adj_list: HashMap::new(),\n };\n // すべての頂点と辺を追加する\n for edge in edges {\n graph.add_vertex(edge[0]);\n graph.add_vertex(edge[1]);\n graph.add_edge(edge[0], edge[1]);\n }\n\n graph\n }\n\n /* 頂点数を取得する */\n #[allow(unused)]\n pub fn size(&amp;self) -&gt; usize {\n self.adj_list.len()\n }\n\n /* 辺を追加する */\n pub fn add_edge(&amp;mut self, vet1: Vertex, vet2: Vertex) {\n if vet1 == vet2 {\n panic!(\"値のエラー\");\n }\n // 辺 vet1 - vet2 を追加する\n self.adj_list.entry(vet1).or_default().push(vet2);\n self.adj_list.entry(vet2).or_default().push(vet1);\n }\n\n /* 辺を削除する */\n #[allow(unused)]\n pub fn remove_edge(&amp;mut self, vet1: Vertex, vet2: Vertex) {\n if vet1 == vet2 {\n panic!(\"値のエラー\");\n }\n // 辺 vet1 - vet2 を削除する\n self.adj_list\n .entry(vet1)\n .and_modify(|v| v.retain(|&amp;e| e != vet2));\n self.adj_list\n .entry(vet2)\n .and_modify(|v| v.retain(|&amp;e| e != vet1));\n }\n\n /* 頂点を追加する */\n pub fn add_vertex(&amp;mut self, vet: Vertex) {\n if self.adj_list.contains_key(&amp;vet) {\n return;\n }\n // 隣接リストに新しい連結リストを追加する\n self.adj_list.insert(vet, vec![]);\n }\n\n /* 頂点を削除する */\n #[allow(unused)]\n pub fn remove_vertex(&amp;mut self, vet: Vertex) {\n // 隣接リストから頂点 vet に対応する連結リストを削除する\n self.adj_list.remove(&amp;vet);\n // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n for list in self.adj_list.values_mut() {\n list.retain(|&amp;v| v != vet);\n }\n }\n\n /* 隣接リストを表示する */\n pub fn print(&amp;self) {\n println!(\"隣接リスト =\");\n for (vertex, list) in &amp;self.adj_list {\n let list = list.iter().map(|vertex| vertex.val).collect::&lt;Vec&lt;i32&gt;&gt;();\n println!(\"{}: {:?},\", vertex.val, list);\n }\n }\n}\n</code></pre> graph_adjacency_list.c<pre><code>/* ノードの構造体 */\ntypedef struct AdjListNode {\n Vertex *vertex; // 頂点\n struct AdjListNode *next; // 後続ノード\n} AdjListNode;\n\n/* 頂点に対応するノードを探索する */\nAdjListNode *findNode(GraphAdjList *graph, Vertex *vet) {\n for (int i = 0; i &lt; graph-&gt;size; i++) {\n if (graph-&gt;heads[i]-&gt;vertex == vet) {\n return graph-&gt;heads[i];\n }\n }\n return NULL;\n}\n\n/* 辺を追加する補助関数 */\nvoid addEdgeHelper(AdjListNode *head, Vertex *vet) {\n AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode));\n node-&gt;vertex = vet;\n // 先頭への挿入\n node-&gt;next = head-&gt;next;\n head-&gt;next = node;\n}\n\n/* 辺を削除する補助関数 */\nvoid removeEdgeHelper(AdjListNode *head, Vertex *vet) {\n AdjListNode *pre = head;\n AdjListNode *cur = head-&gt;next;\n // 連結リストから vet に対応するノードを探索する\n while (cur != NULL &amp;&amp; cur-&gt;vertex != vet) {\n pre = cur;\n cur = cur-&gt;next;\n }\n if (cur == NULL)\n return;\n // vet に対応するノードを連結リストから削除する\n pre-&gt;next = cur-&gt;next;\n // メモリを解放する\n free(cur);\n}\n\n/* 隣接リストで実装した無向グラフクラス */\ntypedef struct {\n AdjListNode *heads[MAX_SIZE]; // ノード配列\n int size; // ノード数\n} GraphAdjList;\n\n/* コンストラクタ */\nGraphAdjList *newGraphAdjList() {\n GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList));\n if (!graph) {\n return NULL;\n }\n graph-&gt;size = 0;\n for (int i = 0; i &lt; MAX_SIZE; i++) {\n graph-&gt;heads[i] = NULL;\n }\n return graph;\n}\n\n/* デストラクタ */\nvoid delGraphAdjList(GraphAdjList *graph) {\n for (int i = 0; i &lt; graph-&gt;size; i++) {\n AdjListNode *cur = graph-&gt;heads[i];\n while (cur != NULL) {\n AdjListNode *next = cur-&gt;next;\n if (cur != graph-&gt;heads[i]) {\n free(cur);\n }\n cur = next;\n }\n free(graph-&gt;heads[i]-&gt;vertex);\n free(graph-&gt;heads[i]);\n }\n free(graph);\n}\n\n/* 頂点に対応するノードを探索する */\nAdjListNode *findNode(GraphAdjList *graph, Vertex *vet) {\n for (int i = 0; i &lt; graph-&gt;size; i++) {\n if (graph-&gt;heads[i]-&gt;vertex == vet) {\n return graph-&gt;heads[i];\n }\n }\n return NULL;\n}\n\n/* 辺を追加する */\nvoid addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {\n AdjListNode *head1 = findNode(graph, vet1);\n AdjListNode *head2 = findNode(graph, vet2);\n assert(head1 != NULL &amp;&amp; head2 != NULL &amp;&amp; head1 != head2);\n // 辺 vet1 - vet2 を追加する\n addEdgeHelper(head1, vet2);\n addEdgeHelper(head2, vet1);\n}\n\n/* 辺を削除する */\nvoid removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {\n AdjListNode *head1 = findNode(graph, vet1);\n AdjListNode *head2 = findNode(graph, vet2);\n assert(head1 != NULL &amp;&amp; head2 != NULL);\n // 辺 vet1 - vet2 を削除する\n removeEdgeHelper(head1, head2-&gt;vertex);\n removeEdgeHelper(head2, head1-&gt;vertex);\n}\n\n/* 頂点を追加する */\nvoid addVertex(GraphAdjList *graph, Vertex *vet) {\n assert(graph != NULL &amp;&amp; graph-&gt;size &lt; MAX_SIZE);\n AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode));\n head-&gt;vertex = vet;\n head-&gt;next = NULL;\n // 隣接リストに新しい連結リストを追加する\n graph-&gt;heads[graph-&gt;size++] = head;\n}\n\n/* 頂点を削除する */\nvoid removeVertex(GraphAdjList *graph, Vertex *vet) {\n AdjListNode *node = findNode(graph, vet);\n assert(node != NULL);\n // 隣接リストから頂点 vet に対応する連結リストを削除する\n AdjListNode *cur = node, *pre = NULL;\n while (cur) {\n pre = cur;\n cur = cur-&gt;next;\n free(pre);\n }\n // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n for (int i = 0; i &lt; graph-&gt;size; i++) {\n cur = graph-&gt;heads[i];\n pre = NULL;\n while (cur) {\n pre = cur;\n cur = cur-&gt;next;\n if (cur &amp;&amp; cur-&gt;vertex == vet) {\n pre-&gt;next = cur-&gt;next;\n free(cur);\n break;\n }\n }\n }\n // この頂点より後の頂点を前にずらし、空きを埋める\n int i;\n for (i = 0; i &lt; graph-&gt;size; i++) {\n if (graph-&gt;heads[i] == node)\n break;\n }\n for (int j = i; j &lt; graph-&gt;size - 1; j++) {\n graph-&gt;heads[j] = graph-&gt;heads[j + 1];\n }\n graph-&gt;size--;\n free(vet);\n}\n</code></pre> graph_adjacency_list.kt<pre><code>/* 隣接リストで実装した無向グラフクラス */\nclass GraphAdjList(edges: Array&lt;Array&lt;Vertex?&gt;&gt;) {\n // 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n val adjList = HashMap&lt;Vertex, MutableList&lt;Vertex&gt;&gt;()\n\n /* コンストラクタ */\n init {\n // すべての頂点と辺を追加する\n for (edge in edges) {\n addVertex(edge[0]!!)\n addVertex(edge[1]!!)\n addEdge(edge[0]!!, edge[1]!!)\n }\n }\n\n /* 頂点数を取得する */\n fun size(): Int {\n return adjList.size\n }\n\n /* 辺を追加する */\n fun addEdge(vet1: Vertex, vet2: Vertex) {\n if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n throw IllegalArgumentException()\n // 辺 vet1 - vet2 を追加する\n adjList[vet1]?.add(vet2)\n adjList[vet2]?.add(vet1)\n }\n\n /* 辺を削除する */\n fun removeEdge(vet1: Vertex, vet2: Vertex) {\n if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)\n throw IllegalArgumentException()\n // 辺 vet1 - vet2 を削除する\n adjList[vet1]?.remove(vet2)\n adjList[vet2]?.remove(vet1)\n }\n\n /* 頂点を追加する */\n fun addVertex(vet: Vertex) {\n if (adjList.containsKey(vet))\n return\n // 隣接リストに新しい連結リストを追加する\n adjList[vet] = mutableListOf()\n }\n\n /* 頂点を削除する */\n fun removeVertex(vet: Vertex) {\n if (!adjList.containsKey(vet))\n throw IllegalArgumentException()\n // 隣接リストから頂点 vet に対応する連結リストを削除する\n adjList.remove(vet)\n // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n for (list in adjList.values) {\n list.remove(vet)\n }\n }\n\n /* 隣接リストを表示する */\n fun print() {\n println(\"隣接リスト =\")\n for (pair in adjList.entries) {\n val tmp = mutableListOf&lt;Int&gt;()\n for (vertex in pair.value) {\n tmp.add(vertex._val)\n }\n println(\"${pair.key._val}: $tmp,\")\n }\n }\n}\n</code></pre> graph_adjacency_list.rb<pre><code>### 隣接リストで実装した無向グラフクラス ###\nclass GraphAdjList\n attr_reader :adj_list\n\n ### コンストラクタ ###\n def initialize(edges)\n # 隣接リスト。key:頂点、value:その頂点に隣接する全頂点\n @adj_list = {}\n # すべての頂点と辺を追加する\n for edge in edges\n add_vertex(edge[0])\n add_vertex(edge[1])\n add_edge(edge[0], edge[1])\n end\n end\n\n ### 頂点数を取得する ###\n def size\n @adj_list.length\n end\n\n ### 辺を追加する ###\n def add_edge(vet1, vet2)\n raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)\n\n @adj_list[vet1] &lt;&lt; vet2\n @adj_list[vet2] &lt;&lt; vet1\n end\n\n ### 辺を削除する ###\n def remove_edge(vet1, vet2)\n raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2)\n\n # 辺 vet1 - vet2 を削除する\n @adj_list[vet1].delete(vet2)\n @adj_list[vet2].delete(vet1)\n end\n\n ### 頂点を追加する ###\n def add_vertex(vet)\n return if @adj_list.include?(vet)\n\n # 隣接リストに新しい連結リストを追加する\n @adj_list[vet] = []\n end\n\n ### 頂点を削除する ###\n def remove_vertex(vet)\n raise ArgumentError unless @adj_list.include?(vet)\n\n # 隣接リストから頂点 vet に対応する連結リストを削除する\n @adj_list.delete(vet)\n # 他の頂点の連結リストを走査し、vet を含むすべての辺を削除する\n for vertex in @adj_list\n @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet)\n end\n end\n\n ### 隣接リストを表示する ###\n def __print__\n puts '隣接リスト ='\n for vertex in @adj_list\n tmp = @adj_list[vertex.first].map { |v| v.val }\n puts \"#{vertex.first.val}: #{tmp},\"\n end\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 9 章   グラフ","9.2   グラフの基本操作"],"tags":[]},{"location":"chapter_graph/graph_operations/#923","level":2,"title":"9.2.3   効率の比較","text":"<p>グラフに \\(n\\) 個の頂点と \\(m\\) 本の辺があるとします。表 9-2 で、隣接行列と隣接リストの時間効率と空間効率を比較します。隣接リスト(連結リスト)は本節の実装に対応し、隣接リスト(ハッシュテーブル)は、すべての連結リストをハッシュテーブルに置き換えた実装を指します。</p> <p> 表 9-2   隣接行列と隣接リストの比較 </p> 隣接行列 隣接リスト(連結リスト) 隣接リスト(ハッシュテーブル) 隣接しているかの判定 \\(O(1)\\) \\(O(n)\\) \\(O(1)\\) 辺の追加 \\(O(1)\\) \\(O(1)\\) \\(O(1)\\) 辺の削除 \\(O(1)\\) \\(O(n)\\) \\(O(1)\\) 頂点の追加 \\(O(n)\\) \\(O(1)\\) \\(O(1)\\) 頂点の削除 \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n)\\) メモリ使用量 \\(O(n^2)\\) \\(O(n + m)\\) \\(O(n + m)\\) <p>表 9-2 だけを見ると、隣接リスト(ハッシュテーブル)が時間効率・空間効率ともに最も優れているように思えます。しかし実際には、隣接行列なら配列へのアクセスまたは代入が 1 回で済むため、辺の操作はより高速です。全体として、隣接行列は「空間を使って時間を節約する」考え方を、隣接リストは「時間を使って空間を節約する」考え方を表しています。</p>","path":["第 9 章   グラフ","9.2   グラフの基本操作"],"tags":[]},{"location":"chapter_graph/graph_traversal/","level":1,"title":"9.3   グラフの走査","text":"<p>木は「一対多」の関係を表しますが、グラフは自由度が高く、任意の「多対多」の関係を表せます。そのため、木はグラフの特殊な場合と考えられます。当然、木の走査も、グラフの走査の特殊な場合です。</p> <p>グラフも木も、走査には探索アルゴリズムを使います。グラフの走査方法も、幅優先走査と深さ優先走査の二つに分けられます。</p>","path":["第 9 章   グラフ","9.3   グラフの走査"],"tags":[]},{"location":"chapter_graph/graph_traversal/#931","level":2,"title":"9.3.1   幅優先走査","text":"<p>幅優先走査は、あるノードから始め、距離の近い頂点から優先して訪問し、層ごとに外側へ広がっていく走査方法です。図 9-9 のように、左上の頂点から始めて、その頂点に隣接するすべての頂点を訪問します。続いて次の頂点に隣接するすべての頂点を訪問し、これをすべての頂点に到達するまで繰り返します。</p> <p></p> <p> 図 9-9   グラフの幅優先走査 </p>","path":["第 9 章   グラフ","9.3   グラフの走査"],"tags":[]},{"location":"chapter_graph/graph_traversal/#1","level":3,"title":"1.   アルゴリズムの実装","text":"<p>BFS は通常、次のコードのようにキューで実装します。キューの「先入れ先出し」という性質は、BFS の「近いところから遠いところへ」という考え方に合っています。</p> <ol> <li>走査を始める頂点 <code>startVet</code> をキューに入れ、ループを開始します。</li> <li>各反復で、キューの先頭の頂点を取り出して訪問を記録し、その頂点に隣接するすべての頂点をキューの末尾に入れます。</li> <li>手順 <code>2.</code> を繰り返し、すべての頂点を訪問したら終了します。</li> </ol> <p>同じ頂点を繰り返し訪問しないよう、ハッシュ集合 <code>visited</code> に訪問済みのノードを記録します。</p> <p>ヒント</p> <p>ハッシュ集合は、<code>key</code> だけを保存し、<code>value</code> を保存しないハッシュテーブルと考えられます。<code>key</code> の追加・削除・検索・更新を時間計算量 \\(O(1)\\) で行えます。<code>key</code> が重複しない性質を活かし、データの重複除去などによく使われます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_bfs.py<pre><code>def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -&gt; list[Vertex]:\n \"\"\"幅優先走査\"\"\"\n # 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\n # 頂点の走査順序\n res = []\n # 訪問済みの頂点を記録するハッシュ集合\n visited = set[Vertex]([start_vet])\n # BFS の実装にキューを使う\n que = deque[Vertex]([start_vet])\n # 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while len(que) &gt; 0:\n vet = que.popleft() # キューの先頭の頂点をデキューする\n res.append(vet) # 訪問した頂点を記録する\n # この頂点に隣接する全頂点を走査する\n for adj_vet in graph.adj_list[vet]:\n if adj_vet in visited:\n continue # 訪問済みの頂点をスキップする\n que.append(adj_vet) # 未訪問の頂点だけをエンキューする\n visited.add(adj_vet) # この頂点を訪問済みにする\n # 頂点の走査順序を返す\n return res\n</code></pre> graph_bfs.cpp<pre><code>/* 幅優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nvector&lt;Vertex *&gt; graphBFS(GraphAdjList &amp;graph, Vertex *startVet) {\n // 頂点の走査順序\n vector&lt;Vertex *&gt; res;\n // 訪問済みの頂点を記録するハッシュ集合\n unordered_set&lt;Vertex *&gt; visited = {startVet};\n // BFS の実装にキューを使う\n queue&lt;Vertex *&gt; que;\n que.push(startVet);\n // 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while (!que.empty()) {\n Vertex *vet = que.front();\n que.pop(); // キューの先頭の頂点をデキューする\n res.push_back(vet); // 訪問した頂点を記録する\n // この頂点に隣接する全頂点を走査する\n for (auto adjVet : graph.adjList[vet]) {\n if (visited.count(adjVet))\n continue; // 訪問済みの頂点をスキップする\n que.push(adjVet); // 未訪問の頂点だけをエンキューする\n visited.emplace(adjVet); // この頂点を訪問済みにする\n }\n }\n // 頂点の走査順序を返す\n return res;\n}\n</code></pre> graph_bfs.java<pre><code>/* 幅優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nList&lt;Vertex&gt; graphBFS(GraphAdjList graph, Vertex startVet) {\n // 頂点の走査順序\n List&lt;Vertex&gt; res = new ArrayList&lt;&gt;();\n // 訪問済みの頂点を記録するハッシュ集合\n Set&lt;Vertex&gt; visited = new HashSet&lt;&gt;();\n visited.add(startVet);\n // BFS の実装にキューを使う\n Queue&lt;Vertex&gt; que = new LinkedList&lt;&gt;();\n que.offer(startVet);\n // 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while (!que.isEmpty()) {\n Vertex vet = que.poll(); // キューの先頭の頂点をデキューする\n res.add(vet); // 訪問した頂点を記録する\n // この頂点に隣接する全頂点を走査する\n for (Vertex adjVet : graph.adjList.get(vet)) {\n if (visited.contains(adjVet))\n continue; // 訪問済みの頂点をスキップする\n que.offer(adjVet); // 未訪問の頂点だけをエンキューする\n visited.add(adjVet); // この頂点を訪問済みにする\n }\n }\n // 頂点の走査順序を返す\n return res;\n}\n</code></pre> graph_bfs.cs<pre><code>/* 幅優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nList&lt;Vertex&gt; GraphBFS(GraphAdjList graph, Vertex startVet) {\n // 頂点の走査順序\n List&lt;Vertex&gt; res = [];\n // 訪問済みの頂点を記録するハッシュ集合\n HashSet&lt;Vertex&gt; visited = [startVet];\n // BFS の実装にキューを使う\n Queue&lt;Vertex&gt; que = new();\n que.Enqueue(startVet);\n // 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while (que.Count &gt; 0) {\n Vertex vet = que.Dequeue(); // キューの先頭の頂点をデキューする\n res.Add(vet); // 訪問した頂点を記録する\n foreach (Vertex adjVet in graph.adjList[vet]) {\n if (visited.Contains(adjVet)) {\n continue; // 訪問済みの頂点をスキップする\n }\n que.Enqueue(adjVet); // 未訪問の頂点だけをエンキューする\n visited.Add(adjVet); // この頂点を訪問済みにする\n }\n }\n\n // 頂点の走査順序を返す\n return res;\n}\n</code></pre> graph_bfs.go<pre><code>/* 幅優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfunc graphBFS(g *graphAdjList, startVet Vertex) []Vertex {\n // 頂点の走査順序\n res := make([]Vertex, 0)\n // 訪問済みの頂点を記録するハッシュ集合\n visited := make(map[Vertex]struct{})\n visited[startVet] = struct{}{}\n // BFS の実装にキューを使い、スライスでキューを再現する\n queue := make([]Vertex, 0)\n queue = append(queue, startVet)\n // 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n for len(queue) &gt; 0 {\n // キューの先頭の頂点をデキューする\n vet := queue[0]\n queue = queue[1:]\n // 訪問した頂点を記録する\n res = append(res, vet)\n // この頂点に隣接する全頂点を走査する\n for _, adjVet := range g.adjList[vet] {\n _, isExist := visited[adjVet]\n // 未訪問の頂点だけをエンキューする\n if !isExist {\n queue = append(queue, adjVet)\n visited[adjVet] = struct{}{}\n }\n }\n }\n // 頂点の走査順序を返す\n return res\n}\n</code></pre> graph_bfs.swift<pre><code>/* 幅優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfunc graphBFS(graph: GraphAdjList, startVet: Vertex) -&gt; [Vertex] {\n // 頂点の走査順序\n var res: [Vertex] = []\n // 訪問済みの頂点を記録するハッシュ集合\n var visited: Set&lt;Vertex&gt; = [startVet]\n // BFS の実装にキューを使う\n var que: [Vertex] = [startVet]\n // 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while !que.isEmpty {\n let vet = que.removeFirst() // キューの先頭の頂点をデキューする\n res.append(vet) // 訪問した頂点を記録する\n // この頂点に隣接する全頂点を走査する\n for adjVet in graph.adjList[vet] ?? [] {\n if visited.contains(adjVet) {\n continue // 訪問済みの頂点をスキップする\n }\n que.append(adjVet) // 未訪問の頂点だけをエンキューする\n visited.insert(adjVet) // この頂点を訪問済みにする\n }\n }\n // 頂点の走査順序を返す\n return res\n}\n</code></pre> graph_bfs.js<pre><code>/* 幅優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfunction graphBFS(graph, startVet) {\n // 頂点の走査順序\n const res = [];\n // 訪問済みの頂点を記録するハッシュ集合\n const visited = new Set();\n visited.add(startVet);\n // BFS の実装にキューを使う\n const que = [startVet];\n // 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while (que.length) {\n const vet = que.shift(); // キューの先頭の頂点をデキューする\n res.push(vet); // 訪問した頂点を記録する\n // この頂点に隣接する全頂点を走査する\n for (const adjVet of graph.adjList.get(vet) ?? []) {\n if (visited.has(adjVet)) {\n continue; // 訪問済みの頂点をスキップする\n }\n que.push(adjVet); // 未訪問の頂点だけをエンキューする\n visited.add(adjVet); // この頂点を訪問済みにする\n }\n }\n // 頂点の走査順序を返す\n return res;\n}\n</code></pre> graph_bfs.ts<pre><code>/* 幅優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfunction graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {\n // 頂点の走査順序\n const res: Vertex[] = [];\n // 訪問済みの頂点を記録するハッシュ集合\n const visited: Set&lt;Vertex&gt; = new Set();\n visited.add(startVet);\n // BFS の実装にキューを使う\n const que = [startVet];\n // 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while (que.length) {\n const vet = que.shift(); // キューの先頭の頂点をデキューする\n res.push(vet); // 訪問した頂点を記録する\n // この頂点に隣接する全頂点を走査する\n for (const adjVet of graph.adjList.get(vet) ?? []) {\n if (visited.has(adjVet)) {\n continue; // 訪問済みの頂点をスキップする\n }\n que.push(adjVet); // 未訪問のものだけをエンキューする\n visited.add(adjVet); // この頂点を訪問済みにする\n }\n }\n // 頂点の走査順序を返す\n return res;\n}\n</code></pre> graph_bfs.dart<pre><code>/* 幅優先走査 */\nList&lt;Vertex&gt; graphBFS(GraphAdjList graph, Vertex startVet) {\n // 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\n // 頂点の走査順序\n List&lt;Vertex&gt; res = [];\n // 訪問済みの頂点を記録するハッシュ集合\n Set&lt;Vertex&gt; visited = {};\n visited.add(startVet);\n // BFS の実装にキューを使う\n Queue&lt;Vertex&gt; que = Queue();\n que.add(startVet);\n // 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while (que.isNotEmpty) {\n Vertex vet = que.removeFirst(); // キューの先頭の頂点をデキューする\n res.add(vet); // 訪問した頂点を記録する\n // この頂点に隣接する全頂点を走査する\n for (Vertex adjVet in graph.adjList[vet]!) {\n if (visited.contains(adjVet)) {\n continue; // 訪問済みの頂点をスキップする\n }\n que.add(adjVet); // 未訪問の頂点だけをエンキューする\n visited.add(adjVet); // この頂点を訪問済みにする\n }\n }\n // 頂点の走査順序を返す\n return res;\n}\n</code></pre> graph_bfs.rs<pre><code>/* 幅優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -&gt; Vec&lt;Vertex&gt; {\n // 頂点の走査順序\n let mut res = vec![];\n // 訪問済みの頂点を記録するハッシュ集合\n let mut visited = HashSet::new();\n visited.insert(start_vet);\n // BFS の実装にキューを使う\n let mut que = VecDeque::new();\n que.push_back(start_vet);\n // 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while let Some(vet) = que.pop_front() {\n res.push(vet); // 訪問した頂点を記録する\n\n // この頂点に隣接する全頂点を走査する\n if let Some(adj_vets) = graph.adj_list.get(&amp;vet) {\n for &amp;adj_vet in adj_vets {\n if visited.contains(&amp;adj_vet) {\n continue; // 訪問済みの頂点をスキップする\n }\n que.push_back(adj_vet); // 未訪問の頂点だけをエンキューする\n visited.insert(adj_vet); // この頂点を訪問済みにする\n }\n }\n }\n // 頂点の走査順序を返す\n res\n}\n</code></pre> graph_bfs.c<pre><code>/* ノードのキューの構造体 */\ntypedef struct {\n Vertex *vertices[MAX_SIZE];\n int front, rear, size;\n} Queue;\n\n/* コンストラクタ */\nQueue *newQueue() {\n Queue *q = (Queue *)malloc(sizeof(Queue));\n q-&gt;front = q-&gt;rear = q-&gt;size = 0;\n return q;\n}\n\n/* キューが空か判定 */\nint isEmpty(Queue *q) {\n return q-&gt;size == 0;\n}\n\n/* エンキュー操作 */\nvoid enqueue(Queue *q, Vertex *vet) {\n q-&gt;vertices[q-&gt;rear] = vet;\n q-&gt;rear = (q-&gt;rear + 1) % MAX_SIZE;\n q-&gt;size++;\n}\n\n/* デキュー操作 */\nVertex *dequeue(Queue *q) {\n Vertex *vet = q-&gt;vertices[q-&gt;front];\n q-&gt;front = (q-&gt;front + 1) % MAX_SIZE;\n q-&gt;size--;\n return vet;\n}\n\n/* 頂点が訪問済みかどうかを確認する */\nint isVisited(Vertex **visited, int size, Vertex *vet) {\n // 走査してノードを探すため、O(n) の時間を要する\n for (int i = 0; i &lt; size; i++) {\n if (visited[i] == vet)\n return 1;\n }\n return 0;\n}\n\n/* 幅優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nvoid graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) {\n // BFS の実装にキューを使う\n Queue *queue = newQueue();\n enqueue(queue, startVet);\n visited[(*visitedSize)++] = startVet;\n // 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while (!isEmpty(queue)) {\n Vertex *vet = dequeue(queue); // キューの先頭の頂点をデキューする\n res[(*resSize)++] = vet; // 訪問した頂点を記録する\n // この頂点に隣接する全頂点を走査する\n AdjListNode *node = findNode(graph, vet);\n while (node != NULL) {\n // 訪問済みの頂点をスキップする\n if (!isVisited(visited, *visitedSize, node-&gt;vertex)) {\n enqueue(queue, node-&gt;vertex); // 未訪問の頂点だけをエンキューする\n visited[(*visitedSize)++] = node-&gt;vertex; // この頂点を訪問済みにする\n }\n node = node-&gt;next;\n }\n }\n // メモリを解放する\n free(queue);\n}\n</code></pre> graph_bfs.kt<pre><code>/* 幅優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList&lt;Vertex?&gt; {\n // 頂点の走査順序\n val res = mutableListOf&lt;Vertex?&gt;()\n // 訪問済みの頂点を記録するハッシュ集合\n val visited = HashSet&lt;Vertex&gt;()\n visited.add(startVet)\n // BFS の実装にキューを使う\n val que = LinkedList&lt;Vertex&gt;()\n que.offer(startVet)\n // 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while (!que.isEmpty()) {\n val vet = que.poll() // キューの先頭の頂点をデキューする\n res.add(vet) // 訪問した頂点を記録する\n // この頂点に隣接する全頂点を走査する\n for (adjVet in graph.adjList[vet]!!) {\n if (visited.contains(adjVet))\n continue // 訪問済みの頂点をスキップする\n que.offer(adjVet) // 未訪問の頂点だけをエンキューする\n visited.add(adjVet) // この頂点を訪問済みにする\n }\n }\n // 頂点の走査順序を返す\n return res\n}\n</code></pre> graph_bfs.rb<pre><code>### 幅優先走査 ###\ndef graph_bfs(graph, start_vet)\n # 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\n # 頂点の走査順序\n res = []\n # 訪問済みの頂点を記録するハッシュ集合\n visited = Set.new([start_vet])\n # BFS の実装にキューを使う\n que = [start_vet]\n # 頂点 vet を始点として、すべての頂点を訪問するまで繰り返す\n while que.length &gt; 0\n vet = que.shift # キューの先頭の頂点をデキューする\n res &lt;&lt; vet # 訪問した頂点を記録する\n # この頂点に隣接する全頂点を走査する\n for adj_vet in graph.adj_list[vet]\n next if visited.include?(adj_vet) # 訪問済みの頂点をスキップする\n que &lt;&lt; adj_vet # 未訪問の頂点だけをエンキューする\n visited.add(adj_vet) # この頂点を訪問済みにする\n end\n end\n # 頂点の走査順序を返す\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>コードだけでは抽象的なので、図 9-10 と照らし合わせると理解が深まります。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt;&lt;11&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 9-10   グラフの幅優先走査の手順 </p> <p>幅優先走査の順序は一意ですか?</p> <p>一意ではありません。幅優先走査で必要なのは「近いところから遠いところへ」という順序だけで、同じ距離にある複数の頂点は、どの順に訪問してもかまいません。図 9-10 では、頂点 \\(1\\) と \\(3\\) の訪問順を入れ替えられますし、頂点 \\(2\\)、\\(4\\)、\\(6\\) も任意の順に訪問できます。</p>","path":["第 9 章   グラフ","9.3   グラフの走査"],"tags":[]},{"location":"chapter_graph/graph_traversal/#2","level":3,"title":"2.   計算量の解析","text":"<p>時間計算量:すべての頂点を 1 回ずつキューに入れて取り出すため、\\(O(|V|)\\) 時間かかります。隣接する頂点を走査する際は、無向グラフなので、すべての辺を \\(2\\) 回ずつ調べ、\\(O(2|E|)\\) 時間かかります。全体では \\(O(|V| + |E|)\\) 時間です。</p> <p>空間計算量:リスト <code>res</code>、ハッシュ集合 <code>visited</code>、キュー <code>que</code> が保持する頂点数は最大 \\(|V|\\) なので、\\(O(|V|)\\) の空間を使います。</p>","path":["第 9 章   グラフ","9.3   グラフの走査"],"tags":[]},{"location":"chapter_graph/graph_traversal/#932","level":2,"title":"9.3.2   深さ優先走査","text":"<p>深さ優先走査は、まず進めるところまで進み、行き止まりになったら引き返す走査方法です。図 9-11 のように、左上の頂点から出発し、現在の頂点に隣接する頂点の一つを訪問して、行き止まりまで進んだら戻ります。別の方向でも同じように進んで戻る操作を繰り返し、すべての頂点を走査します。</p> <p></p> <p> 図 9-11   グラフの深さ優先走査 </p>","path":["第 9 章   グラフ","9.3   グラフの走査"],"tags":[]},{"location":"chapter_graph/graph_traversal/#1_1","level":3,"title":"1.   アルゴリズムの実装","text":"<p>この「行き止まりまで進んでから戻る」というアルゴリズムの形は、通常、再帰で実装します。幅優先走査と同様に、深さ優先走査でもハッシュ集合 <code>visited</code> に訪問済みの頂点を記録し、重複して訪問するのを防ぎます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby graph_dfs.py<pre><code>def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex):\n \"\"\"深さ優先走査の補助関数\"\"\"\n res.append(vet) # 訪問した頂点を記録する\n visited.add(vet) # この頂点を訪問済みにする\n # この頂点に隣接する全頂点を走査する\n for adjVet in graph.adj_list[vet]:\n if adjVet in visited:\n continue # 訪問済みの頂点をスキップする\n # 隣接する頂点を再帰的に訪問する\n dfs(graph, visited, res, adjVet)\n\ndef graph_dfs(graph: GraphAdjList, start_vet: Vertex) -&gt; list[Vertex]:\n \"\"\"深さ優先走査\"\"\"\n # 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\n # 頂点の走査順序\n res = []\n # 訪問済みの頂点を記録するハッシュ集合\n visited = set[Vertex]()\n dfs(graph, visited, res, start_vet)\n return res\n</code></pre> graph_dfs.cpp<pre><code>/* 深さ優先走査の補助関数 */\nvoid dfs(GraphAdjList &amp;graph, unordered_set&lt;Vertex *&gt; &amp;visited, vector&lt;Vertex *&gt; &amp;res, Vertex *vet) {\n res.push_back(vet); // 訪問した頂点を記録する\n visited.emplace(vet); // この頂点を訪問済みにする\n // この頂点に隣接する全頂点を走査する\n for (Vertex *adjVet : graph.adjList[vet]) {\n if (visited.count(adjVet))\n continue; // 訪問済みの頂点をスキップする\n // 隣接する頂点を再帰的に訪問する\n dfs(graph, visited, res, adjVet);\n }\n}\n\n/* 深さ優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nvector&lt;Vertex *&gt; graphDFS(GraphAdjList &amp;graph, Vertex *startVet) {\n // 頂点の走査順序\n vector&lt;Vertex *&gt; res;\n // 訪問済みの頂点を記録するハッシュ集合\n unordered_set&lt;Vertex *&gt; visited;\n dfs(graph, visited, res, startVet);\n return res;\n}\n</code></pre> graph_dfs.java<pre><code>/* 深さ優先走査の補助関数 */\nvoid dfs(GraphAdjList graph, Set&lt;Vertex&gt; visited, List&lt;Vertex&gt; res, Vertex vet) {\n res.add(vet); // 訪問した頂点を記録する\n visited.add(vet); // この頂点を訪問済みにする\n // この頂点に隣接する全頂点を走査する\n for (Vertex adjVet : graph.adjList.get(vet)) {\n if (visited.contains(adjVet))\n continue; // 訪問済みの頂点をスキップする\n // 隣接する頂点を再帰的に訪問する\n dfs(graph, visited, res, adjVet);\n }\n}\n\n/* 深さ優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nList&lt;Vertex&gt; graphDFS(GraphAdjList graph, Vertex startVet) {\n // 頂点の走査順序\n List&lt;Vertex&gt; res = new ArrayList&lt;&gt;();\n // 訪問済みの頂点を記録するハッシュ集合\n Set&lt;Vertex&gt; visited = new HashSet&lt;&gt;();\n dfs(graph, visited, res, startVet);\n return res;\n}\n</code></pre> graph_dfs.cs<pre><code>/* 深さ優先走査の補助関数 */\nvoid DFS(GraphAdjList graph, HashSet&lt;Vertex&gt; visited, List&lt;Vertex&gt; res, Vertex vet) {\n res.Add(vet); // 訪問した頂点を記録する\n visited.Add(vet); // この頂点を訪問済みにする\n // この頂点に隣接する全頂点を走査する\n foreach (Vertex adjVet in graph.adjList[vet]) {\n if (visited.Contains(adjVet)) {\n continue; // 訪問済みの頂点をスキップする \n }\n // 隣接する頂点を再帰的に訪問する\n DFS(graph, visited, res, adjVet);\n }\n}\n\n/* 深さ優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nList&lt;Vertex&gt; GraphDFS(GraphAdjList graph, Vertex startVet) {\n // 頂点の走査順序\n List&lt;Vertex&gt; res = [];\n // 訪問済みの頂点を記録するハッシュ集合\n HashSet&lt;Vertex&gt; visited = [];\n DFS(graph, visited, res, startVet);\n return res;\n}\n</code></pre> graph_dfs.go<pre><code>/* 深さ優先走査の補助関数 */\nfunc dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) {\n // append 操作は新しい参照を返すため、元の参照に新しいスライスの参照を代入し直す必要がある\n *res = append(*res, vet)\n visited[vet] = struct{}{}\n // この頂点に隣接する全頂点を走査する\n for _, adjVet := range g.adjList[vet] {\n _, isExist := visited[adjVet]\n // 隣接する頂点を再帰的に訪問する\n if !isExist {\n dfs(g, visited, res, adjVet)\n }\n }\n}\n\n/* 深さ優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfunc graphDFS(g *graphAdjList, startVet Vertex) []Vertex {\n // 頂点の走査順序\n res := make([]Vertex, 0)\n // 訪問済みの頂点を記録するハッシュ集合\n visited := make(map[Vertex]struct{})\n dfs(g, visited, &amp;res, startVet)\n // 頂点の走査順序を返す\n return res\n}\n</code></pre> graph_dfs.swift<pre><code>/* 深さ優先走査の補助関数 */\nfunc dfs(graph: GraphAdjList, visited: inout Set&lt;Vertex&gt;, res: inout [Vertex], vet: Vertex) {\n res.append(vet) // 訪問した頂点を記録する\n visited.insert(vet) // この頂点を訪問済みにする\n // この頂点に隣接する全頂点を走査する\n for adjVet in graph.adjList[vet] ?? [] {\n if visited.contains(adjVet) {\n continue // 訪問済みの頂点をスキップする\n }\n // 隣接する頂点を再帰的に訪問する\n dfs(graph: graph, visited: &amp;visited, res: &amp;res, vet: adjVet)\n }\n}\n\n/* 深さ優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfunc graphDFS(graph: GraphAdjList, startVet: Vertex) -&gt; [Vertex] {\n // 頂点の走査順序\n var res: [Vertex] = []\n // 訪問済みの頂点を記録するハッシュ集合\n var visited: Set&lt;Vertex&gt; = []\n dfs(graph: graph, visited: &amp;visited, res: &amp;res, vet: startVet)\n return res\n}\n</code></pre> graph_dfs.js<pre><code>/* 深さ優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfunction dfs(graph, visited, res, vet) {\n res.push(vet); // 訪問した頂点を記録する\n visited.add(vet); // この頂点を訪問済みにする\n // この頂点に隣接する全頂点を走査する\n for (const adjVet of graph.adjList.get(vet)) {\n if (visited.has(adjVet)) {\n continue; // 訪問済みの頂点をスキップする\n }\n // 隣接する頂点を再帰的に訪問する\n dfs(graph, visited, res, adjVet);\n }\n}\n\n/* 深さ優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfunction graphDFS(graph, startVet) {\n // 頂点の走査順序\n const res = [];\n // 訪問済みの頂点を記録するハッシュ集合\n const visited = new Set();\n dfs(graph, visited, res, startVet);\n return res;\n}\n</code></pre> graph_dfs.ts<pre><code>/* 深さ優先走査の補助関数 */\nfunction dfs(\n graph: GraphAdjList,\n visited: Set&lt;Vertex&gt;,\n res: Vertex[],\n vet: Vertex\n): void {\n res.push(vet); // 訪問した頂点を記録する\n visited.add(vet); // この頂点を訪問済みにする\n // この頂点に隣接する全頂点を走査する\n for (const adjVet of graph.adjList.get(vet)) {\n if (visited.has(adjVet)) {\n continue; // 訪問済みの頂点をスキップする\n }\n // 隣接する頂点を再帰的に訪問する\n dfs(graph, visited, res, adjVet);\n }\n}\n\n/* 深さ優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfunction graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] {\n // 頂点の走査順序\n const res: Vertex[] = [];\n // 訪問済みの頂点を記録するハッシュ集合\n const visited: Set&lt;Vertex&gt; = new Set();\n dfs(graph, visited, res, startVet);\n return res;\n}\n</code></pre> graph_dfs.dart<pre><code>/* 深さ優先走査の補助関数 */\nvoid dfs(\n GraphAdjList graph,\n Set&lt;Vertex&gt; visited,\n List&lt;Vertex&gt; res,\n Vertex vet,\n) {\n res.add(vet); // 訪問した頂点を記録する\n visited.add(vet); // この頂点を訪問済みにする\n // この頂点に隣接する全頂点を走査する\n for (Vertex adjVet in graph.adjList[vet]!) {\n if (visited.contains(adjVet)) {\n continue; // 訪問済みの頂点をスキップする\n }\n // 隣接する頂点を再帰的に訪問する\n dfs(graph, visited, res, adjVet);\n }\n}\n\n/* 深さ優先走査 */\nList&lt;Vertex&gt; graphDFS(GraphAdjList graph, Vertex startVet) {\n // 頂点の走査順序\n List&lt;Vertex&gt; res = [];\n // 訪問済みの頂点を記録するハッシュ集合\n Set&lt;Vertex&gt; visited = {};\n dfs(graph, visited, res, startVet);\n return res;\n}\n</code></pre> graph_dfs.rs<pre><code>/* 深さ優先走査の補助関数 */\nfn dfs(graph: &amp;GraphAdjList, visited: &amp;mut HashSet&lt;Vertex&gt;, res: &amp;mut Vec&lt;Vertex&gt;, vet: Vertex) {\n res.push(vet); // 訪問した頂点を記録する\n visited.insert(vet); // この頂点を訪問済みにする\n // この頂点に隣接する全頂点を走査する\n if let Some(adj_vets) = graph.adj_list.get(&amp;vet) {\n for &amp;adj_vet in adj_vets {\n if visited.contains(&amp;adj_vet) {\n continue; // 訪問済みの頂点をスキップする\n }\n // 隣接する頂点を再帰的に訪問する\n dfs(graph, visited, res, adj_vet);\n }\n }\n}\n\n/* 深さ優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -&gt; Vec&lt;Vertex&gt; {\n // 頂点の走査順序\n let mut res = vec![];\n // 訪問済みの頂点を記録するハッシュ集合\n let mut visited = HashSet::new();\n dfs(&amp;graph, &amp;mut visited, &amp;mut res, start_vet);\n\n res\n}\n</code></pre> graph_dfs.c<pre><code>/* 頂点が訪問済みかどうかを確認する */\nint isVisited(Vertex **res, int size, Vertex *vet) {\n // 走査してノードを探すため、O(n) の時間を要する\n for (int i = 0; i &lt; size; i++) {\n if (res[i] == vet) {\n return 1;\n }\n }\n return 0;\n}\n\n/* 深さ優先走査の補助関数 */\nvoid dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) {\n // 訪問した頂点を記録する\n res[(*resSize)++] = vet;\n // この頂点に隣接する全頂点を走査する\n AdjListNode *node = findNode(graph, vet);\n while (node != NULL) {\n // 訪問済みの頂点をスキップする\n if (!isVisited(res, *resSize, node-&gt;vertex)) {\n // 隣接する頂点を再帰的に訪問する\n dfs(graph, res, resSize, node-&gt;vertex);\n }\n node = node-&gt;next;\n }\n}\n\n/* 深さ優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nvoid graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) {\n dfs(graph, res, resSize, startVet);\n}\n</code></pre> graph_dfs.kt<pre><code>/* 深さ優先走査の補助関数 */\nfun dfs(\n graph: GraphAdjList,\n visited: MutableSet&lt;Vertex?&gt;,\n res: MutableList&lt;Vertex?&gt;,\n vet: Vertex?\n) {\n res.add(vet) // 訪問した頂点を記録する\n visited.add(vet) // この頂点を訪問済みにする\n // この頂点に隣接する全頂点を走査する\n for (adjVet in graph.adjList[vet]!!) {\n if (visited.contains(adjVet))\n continue // 訪問済みの頂点をスキップする\n // 隣接する頂点を再帰的に訪問する\n dfs(graph, visited, res, adjVet)\n }\n}\n\n/* 深さ優先走査 */\n// 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\nfun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList&lt;Vertex?&gt; {\n // 頂点の走査順序\n val res = mutableListOf&lt;Vertex?&gt;()\n // 訪問済みの頂点を記録するハッシュ集合\n val visited = HashSet&lt;Vertex?&gt;()\n dfs(graph, visited, res, startVet)\n return res\n}\n</code></pre> graph_dfs.rb<pre><code>### 深さ優先走査の補助関数 ###\ndef dfs(graph, visited, res, vet)\n res &lt;&lt; vet # 訪問した頂点を記録する\n visited.add(vet) # この頂点を訪問済みにする\n # この頂点に隣接する全頂点を走査する\n for adj_vet in graph.adj_list[vet]\n next if visited.include?(adj_vet) # 訪問済みの頂点をスキップする\n # 隣接する頂点を再帰的に訪問する\n dfs(graph, visited, res, adj_vet)\n end\nend\n\n### 深さ優先走査 ###\ndef graph_dfs(graph, start_vet)\n # 指定した頂点に隣接する全頂点を取得しやすいように、グラフを隣接リストで表す\n # 頂点の走査順序\n res = []\n # 訪問済みの頂点を記録するハッシュ集合\n visited = Set.new\n dfs(graph, visited, res, start_vet)\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>深さ優先走査のアルゴリズムの流れを図 9-12 に示します。</p> <ul> <li>直線の破線は、下へ進む再帰呼び出しを表し、新しい再帰メソッドを呼び出して、新しい頂点を訪問することを示します。</li> <li>曲線の破線は、上へ戻る動きを表し、再帰メソッドから戻り、そのメソッドを呼び出した位置に復帰することを示します。</li> </ul> <p>理解を深めるため、図 9-12 とコードを照らし合わせ、各再帰メソッドがいつ呼ばれ、いつ戻るかを含めた DFS 全体の流れを、頭の中でたどるか、紙に描いてみてください。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt;&lt;11&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 9-12   グラフの深さ優先走査の手順 </p> <p>深さ優先走査の順序は一意ですか?</p> <p>幅優先走査と同様に、深さ優先走査の順序も一意ではありません。ある頂点からどの方向を先に探索してもよく、隣接する頂点の順序を任意に入れ替えても、深さ優先走査であることは変わりません。</p> <p>木の走査を例にすると、「根 \\(\\rightarrow\\) 左 \\(\\rightarrow\\) 右」「左 \\(\\rightarrow\\) 根 \\(\\rightarrow\\) 右」「左 \\(\\rightarrow\\) 右 \\(\\rightarrow\\) 根」は、それぞれ行きがけ順、通りがけ順、帰りがけ順の走査です。訪問の優先順位は異なりますが、いずれも深さ優先走査です。</p>","path":["第 9 章   グラフ","9.3   グラフの走査"],"tags":[]},{"location":"chapter_graph/graph_traversal/#2_1","level":3,"title":"2.   計算量の解析","text":"<p>時間計算量:すべての頂点を \\(1\\) 回ずつ訪問するのに \\(O(|V|)\\) 時間、すべての辺を \\(2\\) 回ずつ調べるのに \\(O(2|E|)\\) 時間かかります。全体では \\(O(|V| + |E|)\\) 時間です。</p> <p>空間計算量:リスト <code>res</code> とハッシュ集合 <code>visited</code> の頂点数は最大 \\(|V|\\)、再帰の深さも最大 \\(|V|\\) なので、\\(O(|V|)\\) の空間を使います。</p>","path":["第 9 章   グラフ","9.3   グラフの走査"],"tags":[]},{"location":"chapter_graph/summary/","level":1,"title":"9.4   まとめ","text":"","path":["第 9 章   グラフ","9.4   まとめ"],"tags":[]},{"location":"chapter_graph/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>グラフは頂点と辺で構成され、頂点の集合と辺の集合をまとめたものとして表せます。</li> <li>線形の関係(連結リスト)や分割統治の関係(木)に比べ、ネットワークの関係(グラフ)は自由度が高く、その分複雑です。</li> <li>有向グラフの辺には向きがあります。連結グラフではどの頂点にも到達でき、重み付きグラフでは各辺に重みの変数があります。</li> <li>隣接行列は行列でグラフを表します。各行(列)が一つの頂点、行列の要素が辺に対応し、\\(1\\) または \\(0\\) で 2 頂点間に辺があるかどうかを示します。辺の追加・削除・検索・更新は効率的ですが、多くの空間を使います。</li> <li>隣接リストは複数の連結リストでグラフを表します。\\(i\\) 番目の連結リストは頂点 \\(i\\) に対応し、その頂点に隣接するすべての頂点を格納します。隣接行列より空間を節約できますが、辺を探すには連結リストを走査する必要があるため、時間効率は低くなります。</li> <li>隣接リスト内の連結リストが長くなりすぎた場合は、赤黒木やハッシュテーブルに変換して探索効率を高められます。</li> <li>アルゴリズムの考え方として、隣接行列は「空間を使って時間を節約する」、隣接リストは「時間を使って空間を節約する」方法です。</li> <li>グラフは、ソーシャルネットワークや地下鉄の路線など、さまざまな現実のシステムのモデル化に使えます。</li> <li>木はグラフの特殊な場合であり、木の走査もグラフの走査の特殊な場合です。</li> <li>グラフの幅優先走査は、近いところから遠いところへ、層ごとに広がる探索方法で、通常はキューを使って実装します。</li> <li>グラフの深さ優先走査は、まず進めるところまで進み、進めなくなったら引き返す探索方法で、通常は再帰で実装します。</li> </ul>","path":["第 9 章   グラフ","9.4   まとめ"],"tags":[]},{"location":"chapter_graph/summary/#2-q-a","level":3,"title":"2.   Q &amp; A","text":"<p>Q:経路は頂点の列ですか、それとも辺の列ですか。</p> <p>Wikipedia では言語版によって定義が異なります。英語版では「経路は辺の列」、中国語版では「経路は頂点の列」とされています。英語版の原文は次のとおりです。In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices.</p> <p>本書では、経路を頂点の列ではなく、辺の列として扱います。2 頂点間を複数の辺が結ぶことがあり、その場合はそれぞれの辺が一つの経路に対応するためです。</p> <p>Q:非連結グラフには、走査で到達できない頂点がありますか。</p> <p>非連結グラフでは、ある頂点から出発したとき、到達できない頂点が少なくとも一つあります。すべての連結成分を走査するには、複数の開始点を設定する必要があります。</p> <p>Q:隣接リストにある「その頂点につながるすべての頂点」の順序には、決まりがありますか。</p> <p>順序は任意です。ただし、実際の用途では、頂点を追加した順や頂点の値の大小順など、特定の規則に従って並べることがあります。これにより、「何らかの値が最大または最小である頂点」をすばやく探しやすくなります。</p>","path":["第 9 章   グラフ","9.4   まとめ"],"tags":[]},{"location":"chapter_greedy/","level":1,"title":"第 15 章   貪欲法","text":"<p>概要</p> <p>ヒマワリは太陽の方を向き、いつも最大限に成長しようとします。</p> <p>貪欲戦略は、単純な選択を繰り返しながら、最良の答えへと少しずつ導きます。</p>","path":["第 15 章   貪欲法"],"tags":[]},{"location":"chapter_greedy/#_1","level":2,"title":"この章の内容","text":"<ul> <li>15.1   貪欲アルゴリズム</li> <li>15.2   分割可能ナップサック問題</li> <li>15.3   最大容量問題</li> <li>15.4   整数を分割して積を最大にする問題</li> <li>15.5   まとめ</li> <li>15.6   練習問題</li> </ul>","path":["第 15 章   貪欲法"],"tags":[]},{"location":"chapter_greedy/exercises/","level":1,"title":"15.6   練習問題","text":"","path":["第 15 章   貪欲法","15.6   練習問題"],"tags":[]},{"location":"chapter_greedy/exercises/#1561","level":2,"title":"15.6.1   知識の確認","text":"","path":["第 15 章   貪欲法","15.6   練習問題"],"tags":[]},{"location":"chapter_greedy/exercises/#1","level":3,"title":"1.   毎回最大の硬貨を選べば最適になるか","text":"<p>硬貨の額面は <code>[1, 7, 10]</code>、目標金額は 14 です。</p> <ol> <li>「残りの金額を超えない最大の額面を毎回選ぶ」方法を実行し、選ぶ硬貨を書いてください。</li> <li>より少ない枚数で済む方法はありますか。あれば一つ挙げ、なければ理由を説明してください。</li> <li>このことから、この貪欲法の戦略が任意の硬貨の額面で正しいといえますか。</li> </ol> 解答例 <ol> <li> <p>貪欲法では <code>10 + 1 + 1 + 1 + 1</code> の順に選び、計 5 枚になります。</p> </li> <li> <p>より少ない枚数で済む方法があります。<code>7 + 7</code> なら 2 枚だけです。</p> </li> <li> <p>いえません。この反例は、任意の額面に対して、その時点で選べる最大の額面を選んでも、最少枚数になるとは限らないことを示します。 目先の最大の選択によって、その後のよりよい組み合わせを使えなくなる可能性があります。</p> </li> </ol>","path":["第 15 章   貪欲法","15.6   練習問題"],"tags":[]},{"location":"chapter_greedy/exercises/#2","level":3,"title":"2.   ナップサックに先に入れる品物","text":"<p>容量 4 キログラムのナップサックに、次の品物を入れられます。品物の一部分だけを取ることもでき、 得られる価値は重さに比例します。</p> <ul> <li>品物 A:重さ 4 キログラム、価値 20</li> <li>品物 B:重さ 3 キログラム、価値 18</li> </ul> <ol> <li>各品物の 1 キログラム当たりの価値はいくつですか。どちらを先に入れますか。</li> <li>分割可能ナップサック問題の貪欲法の戦略で満杯にすると、最終的な価値はいくつですか。</li> <li>品物を分割でき、重さの合計に制限がある場合、選択時に比べるべきなのは総価値と 1 キログラム当たりの価値のどちらですか。理由も説明してください。</li> </ol> 解答例 <ol> <li> <p>1 キログラム当たりの価値は、A が <code>20 ÷ 4 = 5</code>、B が <code>18 ÷ 3 = 6</code> です。 したがって、単位当たりの価値が高い B を先に入れます。</p> </li> <li> <p>まず B を全部入れ、3 キログラムを使って価値 18 を得ます。残りの容量は 1 キログラムなので、 A を 1 キログラム入れて価値 5 を得ます。最終的な価値は <code>18 + 5 = 23</code> です。</p> </li> <li> <p>制限されるのは重さの合計であり、品物は分割できるため、単位重量当たりの価値を比較すべきです。 A は総価値こそ高いものの、1 キログラム当たりの価値は B より低く、先に A を全部入れると価値 20 しか得られません。</p> </li> </ol>","path":["第 15 章   貪欲法","15.6   練習問題"],"tags":[]},{"location":"chapter_greedy/exercises/#3","level":3,"title":"3.   二つのポインタを次にどう動かすか","text":"<p>仕切り板の高さが <code>[1, 8, 6, 2, 5]</code> で、両端から始める二つのポインタで最大容量を探します。 容量は「二枚のうち短い仕切り板の高さ × 二枚の仕切り板のインデックスの差」です。</p> <ol> <li>初期状態で左ポインタはインデックス 0、右ポインタはインデックス 4 にあります。現在の容量はいくつですか。次にどちらのポインタを動かしますか。</li> <li>問い 1 の選択に従って一回動かすと、各ポインタはどのインデックスにありますか。そのときの容量はいくつで、次はどちらを動かしますか。</li> <li>現在の一組の仕切り板では、短い板に対応するポインタも、長い板に対応するポインタも動かせます。より大きな容量を得られる可能性が残るのはどちらですか。理由も説明してください。</li> </ol> 解答例 <ol> <li> <p>現在の容量は <code>min(1, 5) × (4 - 0) = 4</code> です。左の板が短いため、左ポインタを動かします。</p> </li> <li> <p>左ポインタを動かすと、二つのポインタはインデックス 1 と 4 にあります。現在の容量は <code>min(8, 5) × (4 - 1) = 15</code> です。右の板が短いため、次は右ポインタを動かします。</p> </li> <li> <p>短い板に対応するポインタを動かした場合にだけ、より大きな容量を得られる可能性が残ります。長い板を動かすと、板の間の距離は必ず短くなり、容器の高さは、動かしていない短い板に引き続き制限されるため、 変わらないか低くなるだけです。したがって、容量は移動前を超えられません。短い板を動かして初めて、より高い板に出会える可能性があります。</p> </li> </ol>","path":["第 15 章   貪欲法","15.6   練習問題"],"tags":[]},{"location":"chapter_greedy/exercises/#1562","level":2,"title":"15.6.2   プログラミング演習","text":"","path":["第 15 章   貪欲法","15.6   練習問題"],"tags":[]},{"location":"chapter_greedy/exercises/#1_1","level":3,"title":"1.   分割可能ナップサック問題","text":"<p>同じ長さの配列 <code>wgt</code> と <code>val</code> が与えられ、<code>wgt[i] &gt; 0</code>、<code>val[i] &gt;= 0</code>、容量は <code>cap &gt;= 0</code> を満たします。 各種類の品物は一つだけですが、一部分だけを入れることもできます。 得られる価値は、入れた部分の重さがその品物全体の重さに占める割合に応じて計算します。貪欲法を使い、 ナップサックで得られる価値の合計の最大値を実数で返してください。</p> 解くためのヒント <ol> <li>まず各品物の単位重量当たりの価値 val[i] / wgt[i] を計算し、除算の結果は小数部分も保持します</li> <li>単位当たりの価値が高い品物から先に入れます</li> <li>残りの容量が現在の品物の重さより小さければ、ちょうど満杯になる分だけを入れて終了します</li> </ol>","path":["第 15 章   貪欲法","15.6   練習問題"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/","level":1,"title":"15.2   分割可能ナップサック問題","text":"<p>問題</p> <p>\\(n\\) 個の品物があり、\\(i\\) 番目の品物の重さは \\(wgt[i-1]\\)、価値は \\(val[i-1]\\) です。容量 \\(cap\\) のナップサックに入れる品物を選びます。各品物は 1 回だけ選べますが、品物の一部だけを選ぶこともでき、その価値は選んだ重さの割合で計算します。容量の制限内で得られる品物の最大価値を求めてください。例を図 15-3 に示します。</p> <p></p> <p> 図 15-3   分割可能ナップサック問題のデータ例 </p> <p>分割可能ナップサック問題は、全体として 0-1 ナップサック問題によく似ています。状態は現在の品物 \\(i\\) と容量 \\(c\\) で表し、容量の制限内で得られる最大価値を求めます。</p> <p>違いは、品物の一部だけを選べることです。図 15-4 のように、品物を自由に分割し、重さの割合に応じて価値を計算できます。</p> <ol> <li>品物 \\(i\\) の単位重さ当たりの価値は \\(val[i-1] / wgt[i-1]\\) で、これを単位価値と呼びます。</li> <li>品物 \\(i\\) のうち重さ \\(w\\) の部分を入れると、ナップサック内の価値は \\(w \\times val[i-1] / wgt[i-1]\\) 増えます。</li> </ol> <p></p> <p> 図 15-4   品物の単位重さ当たりの価値 </p>","path":["第 15 章   貪欲法","15.2   分割可能ナップサック問題"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#1","level":3,"title":"1.   貪欲戦略を決める","text":"<p>ナップサック内の品物の総価値を最大にすることは、本質的には単位重さ当たりの価値を最大にすることです。ここから、図 15-5 の貪欲戦略を導けます。</p> <ol> <li>品物を単位価値の高い順にソートします。</li> <li>すべての品物を順に調べ、毎回、単位価値が最も高い品物を貪欲に選びます。</li> <li>ナップサックの残り容量が足りなければ、現在の品物の一部を使っていっぱいにします。</li> </ol> <p></p> <p> 図 15-5   分割可能ナップサック問題の貪欲戦略 </p>","path":["第 15 章   貪欲法","15.2   分割可能ナップサック問題"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#2","level":3,"title":"2.   コードの実装","text":"<p>品物を単位価値でソートしやすいよう、品物のクラス <code>Item</code> を作ります。貪欲な選択を繰り返し、ナップサックがいっぱいになったらループを抜け、解を返します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby fractional_knapsack.py<pre><code>class Item:\n \"\"\"品物\"\"\"\n\n def __init__(self, w: int, v: int):\n self.w = w # 品物の重さ\n self.v = v # 品物の価値\n\ndef fractional_knapsack(wgt: list[int], val: list[int], cap: int) -&gt; int:\n \"\"\"分割可能ナップサック問題:貪欲法\"\"\"\n # 重さと価値の 2 つの属性を持つ品物のリストを作成する\n items = [Item(w, v) for w, v in zip(wgt, val)]\n # 単位重さ当たりの価値 item.v / item.w の降順にソートする\n items.sort(key=lambda item: item.v / item.w, reverse=True)\n # 貪欲な選択を繰り返す\n res = 0\n for item in items:\n if item.w &lt;= cap:\n # 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += item.v\n cap -= item.w\n else:\n # 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += (item.v / item.w) * cap\n # 残り容量がないため、ループを終了する\n break\n return res\n</code></pre> fractional_knapsack.cpp<pre><code>/* 品物 */\nclass Item {\n public:\n int w; // 品物の重さ\n int v; // 品物の価値\n\n Item(int w, int v) : w(w), v(v) {\n }\n};\n\n/* 分割可能ナップサック問題:貪欲法 */\ndouble fractionalKnapsack(vector&lt;int&gt; &amp;wgt, vector&lt;int&gt; &amp;val, int cap) {\n // 重さと価値の 2 つの属性を持つ品物のリストを作成する\n vector&lt;Item&gt; items;\n for (int i = 0; i &lt; wgt.size(); i++) {\n items.push_back(Item(wgt[i], val[i]));\n }\n // 単位重さ当たりの価値 item.v / item.w の降順にソートする\n sort(items.begin(), items.end(), [](Item &amp;a, Item &amp;b) { return (double)a.v / a.w &gt; (double)b.v / b.w; });\n // 貪欲な選択を繰り返す\n double res = 0;\n for (auto &amp;item : items) {\n if (item.w &lt;= cap) {\n // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += item.v;\n cap -= item.w;\n } else {\n // 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += (double)item.v / item.w * cap;\n // 残り容量がないため、ループを終了する\n break;\n }\n }\n return res;\n}\n</code></pre> fractional_knapsack.java<pre><code>/* 品物 */\nclass Item {\n int w; // 品物の重さ\n int v; // 品物の価値\n\n public Item(int w, int v) {\n this.w = w;\n this.v = v;\n }\n}\n\n/* 分割可能ナップサック問題:貪欲法 */\ndouble fractionalKnapsack(int[] wgt, int[] val, int cap) {\n // 重さと価値の 2 つの属性を持つ品物のリストを作成する\n Item[] items = new Item[wgt.length];\n for (int i = 0; i &lt; wgt.length; i++) {\n items[i] = new Item(wgt[i], val[i]);\n }\n // 単位重さ当たりの価値 item.v / item.w の降順にソートする\n Arrays.sort(items, Comparator.comparingDouble(item -&gt; -((double) item.v / item.w)));\n // 貪欲な選択を繰り返す\n double res = 0;\n for (Item item : items) {\n if (item.w &lt;= cap) {\n // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += item.v;\n cap -= item.w;\n } else {\n // 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += (double) item.v / item.w * cap;\n // 残り容量がないため、ループを終了する\n break;\n }\n }\n return res;\n}\n</code></pre> fractional_knapsack.cs<pre><code>/* 品物 */\nclass Item(int w, int v) {\n public int w = w; // 品物の重さ\n public int v = v; // 品物の価値\n}\n\n/* 分割可能ナップサック問題:貪欲法 */\ndouble FractionalKnapsack(int[] wgt, int[] val, int cap) {\n // 重さと価値の 2 つの属性を持つ品物のリストを作成する\n Item[] items = new Item[wgt.Length];\n for (int i = 0; i &lt; wgt.Length; i++) {\n items[i] = new Item(wgt[i], val[i]);\n }\n // 単位重さ当たりの価値 item.v / item.w の降順にソートする\n Array.Sort(items, (x, y) =&gt; (y.v / y.w).CompareTo(x.v / x.w));\n // 貪欲な選択を繰り返す\n double res = 0;\n foreach (Item item in items) {\n if (item.w &lt;= cap) {\n // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += item.v;\n cap -= item.w;\n } else {\n // 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += (double)item.v / item.w * cap;\n // 残り容量がないため、ループを終了する\n break;\n }\n }\n return res;\n}\n</code></pre> fractional_knapsack.go<pre><code>/* 品物 */\ntype Item struct {\n w int // 品物の重さ\n v int // 品物の価値\n}\n\n/* 分割可能ナップサック問題:貪欲法 */\nfunc fractionalKnapsack(wgt []int, val []int, cap int) float64 {\n // 重さと価値の 2 つの属性を持つ品物のリストを作成する\n items := make([]Item, len(wgt))\n for i := 0; i &lt; len(wgt); i++ {\n items[i] = Item{wgt[i], val[i]}\n }\n // 単位重さ当たりの価値 item.v / item.w の降順にソートする\n sort.Slice(items, func(i, j int) bool {\n return float64(items[i].v)/float64(items[i].w) &gt; float64(items[j].v)/float64(items[j].w)\n })\n // 貪欲な選択を繰り返す\n res := 0.0\n for _, item := range items {\n if item.w &lt;= cap {\n // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += float64(item.v)\n cap -= item.w\n } else {\n // 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += float64(item.v) / float64(item.w) * float64(cap)\n // 残り容量がないため、ループを終了する\n break\n }\n }\n return res\n}\n</code></pre> fractional_knapsack.swift<pre><code>/* 品物 */\nclass Item {\n var w: Int // 品物の重さ\n var v: Int // 品物の価値\n\n init(w: Int, v: Int) {\n self.w = w\n self.v = v\n }\n}\n\n/* 分割可能ナップサック問題:貪欲法 */\nfunc fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -&gt; Double {\n // 重さと価値の 2 つの属性を持つ品物のリストを作成する\n var items = zip(wgt, val).map { Item(w: $0, v: $1) }\n // 単位重さ当たりの価値 item.v / item.w の降順にソートする\n items.sort { -(Double($0.v) / Double($0.w)) &lt; -(Double($1.v) / Double($1.w)) }\n // 貪欲な選択を繰り返す\n var res = 0.0\n var cap = cap\n for item in items {\n if item.w &lt;= cap {\n // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += Double(item.v)\n cap -= item.w\n } else {\n // 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += Double(item.v) / Double(item.w) * Double(cap)\n // 残り容量がないため、ループを終了する\n break\n }\n }\n return res\n}\n</code></pre> fractional_knapsack.js<pre><code>/* 品物 */\nclass Item {\n constructor(w, v) {\n this.w = w; // 品物の重さ\n this.v = v; // 品物の価値\n }\n}\n\n/* 分割可能ナップサック問題:貪欲法 */\nfunction fractionalKnapsack(wgt, val, cap) {\n // 重さと価値の 2 つの属性を持つ品物のリストを作成する\n const items = wgt.map((w, i) =&gt; new Item(w, val[i]));\n // 単位重さ当たりの価値 item.v / item.w の降順にソートする\n items.sort((a, b) =&gt; b.v / b.w - a.v / a.w);\n // 貪欲な選択を繰り返す\n let res = 0;\n for (const item of items) {\n if (item.w &lt;= cap) {\n // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += item.v;\n cap -= item.w;\n } else {\n // 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += (item.v / item.w) * cap;\n // 残り容量がないため、ループを終了する\n break;\n }\n }\n return res;\n}\n</code></pre> fractional_knapsack.ts<pre><code>/* 品物 */\nclass Item {\n w: number; // 品物の重さ\n v: number; // 品物の価値\n\n constructor(w: number, v: number) {\n this.w = w;\n this.v = v;\n }\n}\n\n/* 分割可能ナップサック問題:貪欲法 */\nfunction fractionalKnapsack(wgt: number[], val: number[], cap: number): number {\n // 重さと価値の 2 つの属性を持つ品物のリストを作成する\n const items: Item[] = wgt.map((w, i) =&gt; new Item(w, val[i]));\n // 単位重さ当たりの価値 item.v / item.w の降順にソートする\n items.sort((a, b) =&gt; b.v / b.w - a.v / a.w);\n // 貪欲な選択を繰り返す\n let res = 0;\n for (const item of items) {\n if (item.w &lt;= cap) {\n // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += item.v;\n cap -= item.w;\n } else {\n // 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += (item.v / item.w) * cap;\n // 残り容量がないため、ループを終了する\n break;\n }\n }\n return res;\n}\n</code></pre> fractional_knapsack.dart<pre><code>/* 品物 */\nclass Item {\n int w; // 品物の重さ\n int v; // 品物の価値\n\n Item(this.w, this.v);\n}\n\n/* 分割可能ナップサック問題:貪欲法 */\ndouble fractionalKnapsack(List&lt;int&gt; wgt, List&lt;int&gt; val, int cap) {\n // 重さと価値の 2 つの属性を持つ品物のリストを作成する\n List&lt;Item&gt; items = List.generate(wgt.length, (i) =&gt; Item(wgt[i], val[i]));\n // 単位重さ当たりの価値 item.v / item.w の降順にソートする\n items.sort((a, b) =&gt; (b.v / b.w).compareTo(a.v / a.w));\n // 貪欲な選択を繰り返す\n double res = 0;\n for (Item item in items) {\n if (item.w &lt;= cap) {\n // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += item.v;\n cap -= item.w;\n } else {\n // 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += item.v / item.w * cap;\n // 残り容量がないため、ループを終了する\n break;\n }\n }\n return res;\n}\n</code></pre> fractional_knapsack.rs<pre><code>/* 品物 */\nstruct Item {\n w: i32, // 品物の重さ\n v: i32, // 品物の価値\n}\n\nimpl Item {\n fn new(w: i32, v: i32) -&gt; Self {\n Self { w, v }\n }\n}\n\n/* 分割可能ナップサック問題:貪欲法 */\nfn fractional_knapsack(wgt: &amp;[i32], val: &amp;[i32], mut cap: i32) -&gt; f64 {\n // 重さと価値の 2 つの属性を持つ品物のリストを作成する\n let mut items = wgt\n .iter()\n .zip(val.iter())\n .map(|(&amp;w, &amp;v)| Item::new(w, v))\n .collect::&lt;Vec&lt;Item&gt;&gt;();\n // 単位重さ当たりの価値 item.v / item.w の降順にソートする\n items.sort_by(|a, b| {\n (b.v as f64 / b.w as f64)\n .partial_cmp(&amp;(a.v as f64 / a.w as f64))\n .unwrap()\n });\n // 貪欲な選択を繰り返す\n let mut res = 0.0;\n for item in &amp;items {\n if item.w &lt;= cap {\n // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += item.v as f64;\n cap -= item.w;\n } else {\n // 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += item.v as f64 / item.w as f64 * cap as f64;\n // 残り容量がないため、ループを終了する\n break;\n }\n }\n res\n}\n</code></pre> fractional_knapsack.c<pre><code>/* 品物 */\ntypedef struct {\n int w; // 品物の重さ\n int v; // 品物の価値\n} Item;\n\n/* 分割可能ナップサック問題:貪欲法 */\nfloat fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) {\n // 重さと価値の 2 つの属性を持つ品物のリストを作成する\n Item *items = malloc(sizeof(Item) * itemCount);\n for (int i = 0; i &lt; itemCount; i++) {\n items[i] = (Item){.w = wgt[i], .v = val[i]};\n }\n // 単位重さ当たりの価値 item.v / item.w の降順にソートする\n qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity);\n // 貪欲な選択を繰り返す\n float res = 0.0;\n for (int i = 0; i &lt; itemCount; i++) {\n if (items[i].w &lt;= cap) {\n // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += items[i].v;\n cap -= items[i].w;\n } else {\n // 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += (float)cap / items[i].w * items[i].v;\n cap = 0;\n break;\n }\n }\n free(items);\n return res;\n}\n</code></pre> fractional_knapsack.kt<pre><code>/* 品物 */\nclass Item(\n val w: Int, // 品物\n val v: Int // 品物の価値\n)\n\n/* 分割可能ナップサック問題:貪欲法 */\nfun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double {\n // 重さと価値の 2 つの属性を持つ品物のリストを作成する\n var cap = c\n val items = arrayOfNulls&lt;Item&gt;(wgt.size)\n for (i in wgt.indices) {\n items[i] = Item(wgt[i], _val[i])\n }\n // 単位重さ当たりの価値 item.v / item.w の降順にソートする\n items.sortBy { item: Item? -&gt; -(item!!.v.toDouble() / item.w) }\n // 貪欲な選択を繰り返す\n var res = 0.0\n for (item in items) {\n if (item!!.w &lt;= cap) {\n // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += item.v\n cap -= item.w\n } else {\n // 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += item.v.toDouble() / item.w * cap\n // 残り容量がないため、ループを終了する\n break\n }\n }\n return res\n}\n</code></pre> fractional_knapsack.rb<pre><code>### 品物 ###\nclass Item\n attr_accessor :w # 品物の重さ\n attr_accessor :v # 品物の価値\n\n def initialize(w, v)\n @w = w\n @v = v\n end\nend\n\n### 分割可能ナップサック問題:貪欲法 ###\ndef fractional_knapsack(wgt, val, cap)\n # 重さと価値の 2 つの属性を持つ品物のリストを作成する\n items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) }\n # 単位重さ当たりの価値 item.v / item.w の降順にソートする\n items.sort! { |a, b| (b.v.to_f / b.w) &lt;=&gt; (a.v.to_f / a.w) }\n # 貪欲な選択を繰り返す\n res = 0\n for item in items\n if item.w &lt;= cap\n # 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる\n res += item.v\n cap -= item.w\n else\n # 残り容量が不足している場合、現在の品物の一部をナップサックに入れる\n res += (item.v.to_f / item.w) * cap\n # 残り容量がないため、ループを終了する\n break\n end\n end\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>組み込みソートの時間計算量は通常 \\(O(n \\log n)\\) です。空間計算量は通常 \\(O(\\log n)\\) または \\(O(n)\\) で、言語ごとの実装に依存します。</p> <p>ソート以外では、最悪の場合、品物のリスト全体を走査するため、時間計算量は \\(O(n)\\) です。ここで \\(n\\) は品物の数です。</p> <p><code>Item</code> オブジェクトのリストを初期化するため、空間計算量は \\(O(n)\\) です。</p>","path":["第 15 章   貪欲法","15.2   分割可能ナップサック問題"],"tags":[]},{"location":"chapter_greedy/fractional_knapsack_problem/#3","level":3,"title":"3.   正しさの証明","text":"<p>背理法を使います。品物 \\(x\\) の単位価値が最も高いとします。あるアルゴリズムで最大価値 <code>res</code> が得られたものの、その解には品物 \\(x\\) が含まれていないと仮定します。</p> <p>ここで、ナップサックから任意の品物を単位重さだけ取り出し、同じ重さの品物 \\(x\\) に置き換えます。\\(x\\) の単位価値が最も高いので、置き換え後の総価値は必ず <code>res</code> より大きくなります。これは <code>res</code> が最適解であることに矛盾するため、最適解には品物 \\(x\\) が含まれていなければなりません。</p> <p>解に含まれるほかの品物についても、同じ矛盾を導けます。つまり、単位価値の高い品物を選ぶ方が常に有利であり、貪欲戦略は有効です。</p> <p>図 15-6 のように、品物の重さを横軸、単位価値を縦軸とすると、分割可能ナップサック問題は「横軸の区間が限られた中で、囲まれる面積を最大にする問題」に置き換えられます。このたとえから、貪欲戦略が有効な理由を幾何学的に理解できます。</p> <p></p> <p> 図 15-6   分割可能ナップサック問題の幾何学的表現 </p>","path":["第 15 章   貪欲法","15.2   分割可能ナップサック問題"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/","level":1,"title":"15.1   貪欲アルゴリズム","text":"<p>貪欲アルゴリズム(greedy algorithm)は、最適化問題を解くためによく使われるアルゴリズムです。各段階で、その時点で最もよいと思われる選択、つまり局所的に最適な判断を貪欲に行い、全体の最適解を目指します。簡潔で効率的なため、さまざまな実際の問題に広く使われています。</p> <p>貪欲アルゴリズムと動的計画法は、どちらも最適化問題でよく使われます。最適部分構造を利用する点などは似ていますが、動作原理は異なります。</p> <ul> <li>動的計画法は、それまでの段階のすべての判断を踏まえて現在の判断を考え、過去の部分問題の解から現在の部分問題の解を組み立てます。</li> <li>貪欲アルゴリズムは過去の判断を振り返らず、前に進みながら貪欲な選択を続け、問題の範囲を縮めて解決に至ります。</li> </ul> <p>まず「硬貨の両替」を例に、貪欲アルゴリズムの仕組みを見てみましょう。「個数制限なしナップサック問題」の節で取り上げたので、覚えているかもしれません。</p> <p>問題</p> <p>\\(n\\) 種類の硬貨があり、\\(i\\) 番目の硬貨の額面は \\(coins[i - 1]\\) です。目標金額は \\(amt\\) で、各種類の硬貨は繰り返し選べます。目標金額を作るために必要な硬貨の最小枚数を求めてください。作れない場合は \\(-1\\) を返します。</p> <p>この問題で使う貪欲戦略を図 15-1 に示します。目標金額に対し、それを超えない範囲で最も近い額面の硬貨を貪欲に選び、目標金額を作るまで繰り返します。</p> <p></p> <p> 図 15-1   硬貨の両替における貪欲戦略 </p> <p>実装は次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby coin_change_greedy.py<pre><code>def coin_change_greedy(coins: list[int], amt: int) -&gt; int:\n \"\"\"硬貨の両替:貪欲法\"\"\"\n # coins リストはソート済みと仮定する\n i = len(coins) - 1\n count = 0\n # 残額がなくなるまで貪欲な選択を繰り返す\n while amt &gt; 0:\n # 残額より小さく、残額に最も近い硬貨を探す\n while i &gt; 0 and coins[i] &gt; amt:\n i -= 1\n # coins[i] を選ぶ\n amt -= coins[i]\n count += 1\n # 実行可能な方法が見つからなかった場合、-1 を返す\n return count if amt == 0 else -1\n</code></pre> coin_change_greedy.cpp<pre><code>/* 硬貨の両替:貪欲法 */\nint coinChangeGreedy(vector&lt;int&gt; &amp;coins, int amt) {\n // coins リストはソート済みと仮定する\n int i = coins.size() - 1;\n int count = 0;\n // 残額がなくなるまで貪欲な選択を繰り返す\n while (amt &gt; 0) {\n // 残額より小さく、残額に最も近い硬貨を探す\n while (i &gt; 0 &amp;&amp; coins[i] &gt; amt) {\n i--;\n }\n // coins[i] を選ぶ\n amt -= coins[i];\n count++;\n }\n // 実行可能な方法が見つからなかった場合、-1 を返す\n return amt == 0 ? count : -1;\n}\n</code></pre> coin_change_greedy.java<pre><code>/* 硬貨の両替:貪欲法 */\nint coinChangeGreedy(int[] coins, int amt) {\n // coins リストはソート済みと仮定する\n int i = coins.length - 1;\n int count = 0;\n // 残額がなくなるまで貪欲な選択を繰り返す\n while (amt &gt; 0) {\n // 残額より小さく、残額に最も近い硬貨を探す\n while (i &gt; 0 &amp;&amp; coins[i] &gt; amt) {\n i--;\n }\n // coins[i] を選ぶ\n amt -= coins[i];\n count++;\n }\n // 実行可能な方法が見つからなかった場合、-1 を返す\n return amt == 0 ? count : -1;\n}\n</code></pre> coin_change_greedy.cs<pre><code>/* 硬貨の両替:貪欲法 */\nint CoinChangeGreedy(int[] coins, int amt) {\n // coins リストはソート済みと仮定する\n int i = coins.Length - 1;\n int count = 0;\n // 残額がなくなるまで貪欲な選択を繰り返す\n while (amt &gt; 0) {\n // 残額より小さく、残額に最も近い硬貨を探す\n while (i &gt; 0 &amp;&amp; coins[i] &gt; amt) {\n i--;\n }\n // coins[i] を選ぶ\n amt -= coins[i];\n count++;\n }\n // 実行可能な方法が見つからなかった場合、-1 を返す\n return amt == 0 ? count : -1;\n}\n</code></pre> coin_change_greedy.go<pre><code>/* 硬貨の両替:貪欲法 */\nfunc coinChangeGreedy(coins []int, amt int) int {\n // coins リストはソート済みと仮定する\n i := len(coins) - 1\n count := 0\n // 残額がなくなるまで貪欲な選択を繰り返す\n for amt &gt; 0 {\n // 残額より小さく、残額に最も近い硬貨を探す\n for i &gt; 0 &amp;&amp; coins[i] &gt; amt {\n i--\n }\n // coins[i] を選ぶ\n amt -= coins[i]\n count++\n }\n // 実行可能な方法が見つからなかった場合、-1 を返す\n if amt != 0 {\n return -1\n }\n return count\n}\n</code></pre> coin_change_greedy.swift<pre><code>/* 硬貨の両替:貪欲法 */\nfunc coinChangeGreedy(coins: [Int], amt: Int) -&gt; Int {\n // coins リストはソート済みと仮定する\n var i = coins.count - 1\n var count = 0\n var amt = amt\n // 残額がなくなるまで貪欲な選択を繰り返す\n while amt &gt; 0 {\n // 残額より小さく、残額に最も近い硬貨を探す\n while i &gt; 0 &amp;&amp; coins[i] &gt; amt {\n i -= 1\n }\n // coins[i] を選ぶ\n amt -= coins[i]\n count += 1\n }\n // 実行可能な方法が見つからなかった場合、-1 を返す\n return amt == 0 ? count : -1\n}\n</code></pre> coin_change_greedy.js<pre><code>/* 硬貨の両替:貪欲法 */\nfunction coinChangeGreedy(coins, amt) {\n // coins 配列はソート済みと仮定する\n let i = coins.length - 1;\n let count = 0;\n // 残額がなくなるまで貪欲な選択を繰り返す\n while (amt &gt; 0) {\n // 残額より小さく、残額に最も近い硬貨を探す\n while (i &gt; 0 &amp;&amp; coins[i] &gt; amt) {\n i--;\n }\n // coins[i] を選ぶ\n amt -= coins[i];\n count++;\n }\n // 実行可能な方法が見つからなかった場合、-1 を返す\n return amt === 0 ? count : -1;\n}\n</code></pre> coin_change_greedy.ts<pre><code>/* 硬貨の両替:貪欲法 */\nfunction coinChangeGreedy(coins: number[], amt: number): number {\n // coins 配列はソート済みと仮定する\n let i = coins.length - 1;\n let count = 0;\n // 残額がなくなるまで貪欲な選択を繰り返す\n while (amt &gt; 0) {\n // 残額より小さく、残額に最も近い硬貨を探す\n while (i &gt; 0 &amp;&amp; coins[i] &gt; amt) {\n i--;\n }\n // coins[i] を選ぶ\n amt -= coins[i];\n count++;\n }\n // 実行可能な方法が見つからなかった場合、-1 を返す\n return amt === 0 ? count : -1;\n}\n</code></pre> coin_change_greedy.dart<pre><code>/* 硬貨の両替:貪欲法 */\nint coinChangeGreedy(List&lt;int&gt; coins, int amt) {\n // coins リストはソート済みと仮定する\n int i = coins.length - 1;\n int count = 0;\n // 残額がなくなるまで貪欲な選択を繰り返す\n while (amt &gt; 0) {\n // 残額より小さく、残額に最も近い硬貨を探す\n while (i &gt; 0 &amp;&amp; coins[i] &gt; amt) {\n i--;\n }\n // coins[i] を選ぶ\n amt -= coins[i];\n count++;\n }\n // 実行可能な方法が見つからなかった場合、-1 を返す\n return amt == 0 ? count : -1;\n}\n</code></pre> coin_change_greedy.rs<pre><code>/* 硬貨の両替:貪欲法 */\nfn coin_change_greedy(coins: &amp;[i32], mut amt: i32) -&gt; i32 {\n // coins リストはソート済みと仮定する\n let mut i = coins.len() - 1;\n let mut count = 0;\n // 残額がなくなるまで貪欲な選択を繰り返す\n while amt &gt; 0 {\n // 残額より小さく、残額に最も近い硬貨を探す\n while i &gt; 0 &amp;&amp; coins[i] &gt; amt {\n i -= 1;\n }\n // coins[i] を選ぶ\n amt -= coins[i];\n count += 1;\n }\n // 実行可能な方法が見つからなかった場合、-1 を返す\n if amt == 0 {\n count\n } else {\n -1\n }\n}\n</code></pre> coin_change_greedy.c<pre><code>/* 硬貨の両替:貪欲法 */\nint coinChangeGreedy(int *coins, int size, int amt) {\n // coins リストはソート済みと仮定する\n int i = size - 1;\n int count = 0;\n // 残額がなくなるまで貪欲な選択を繰り返す\n while (amt &gt; 0) {\n // 残額より小さく、残額に最も近い硬貨を探す\n while (i &gt; 0 &amp;&amp; coins[i] &gt; amt) {\n i--;\n }\n // coins[i] を選ぶ\n amt -= coins[i];\n count++;\n }\n // 実行可能な方法が見つからなかった場合、-1 を返す\n return amt == 0 ? count : -1;\n}\n</code></pre> coin_change_greedy.kt<pre><code>/* 硬貨の両替:貪欲法 */\nfun coinChangeGreedy(coins: IntArray, amt: Int): Int {\n // coins リストはソート済みと仮定する\n var am = amt\n var i = coins.size - 1\n var count = 0\n // 残額がなくなるまで貪欲な選択を繰り返す\n while (am &gt; 0) {\n // 残額より小さく、残額に最も近い硬貨を探す\n while (i &gt; 0 &amp;&amp; coins[i] &gt; am) {\n i--\n }\n // coins[i] を選ぶ\n am -= coins[i]\n count++\n }\n // 実行可能な方法が見つからなかった場合、-1 を返す\n return if (am == 0) count else -1\n}\n</code></pre> coin_change_greedy.rb<pre><code>### 硬貨の両替:貪欲法 ###\ndef coin_change_greedy(coins, amt)\n # coins リストはソート済みと仮定する\n i = coins.length - 1\n count = 0\n # 残額がなくなるまで貪欲な選択を繰り返す\n while amt &gt; 0\n # 残額より小さく、残額に最も近い硬貨を探す\n while i &gt; 0 &amp;&amp; coins[i] &gt; amt\n i -= 1\n end\n # coins[i] を選ぶ\n amt -= coins[i]\n count += 1\n end\n # 実行可能な方法が見つからなかった場合、-1 を返す\n amt == 0 ? count : -1\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>「なんてすっきりしたコードだろう!」と思うかもしれません。貪欲アルゴリズムなら、わずか 10 行ほどで硬貨の両替問題を解けます。</p>","path":["第 15 章   貪欲法","15.1   貪欲アルゴリズム"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1511","level":2,"title":"15.1.1   貪欲アルゴリズムの利点と制約","text":"<p>貪欲アルゴリズムは操作が直接的で実装が簡単なうえ、通常は効率も高くなります。上のコードで最小の額面を \\(\\min(coins)\\) とすると、貪欲な選択は最大 \\(amt / \\min(coins)\\) 回繰り返されるため、時間計算量は \\(O(amt / \\min(coins))\\) です。これは、動的計画法の時間計算量 \\(O(n \\times amt)\\) より一つのオーダー分小さくなっています。</p> <p>ただし、硬貨の額面の組み合わせによっては、貪欲アルゴリズムで最適解を見つけられません。図 15-2 に二つの例を示します。</p> <ul> <li>うまくいく例 \\(coins = [1, 5, 10, 20, 50, 100]\\):この硬貨の組み合わせでは、どのような \\(amt\\) に対しても、貪欲アルゴリズムで最適解を見つけられます。</li> <li>反例 \\(coins = [1, 20, 50]\\):\\(amt = 60\\) とすると、貪欲アルゴリズムが得るのは \\(50 + 1 \\times 10\\) で、硬貨は計 \\(11\\) 枚です。一方、動的計画法なら最適解 \\(20 + 20 + 20\\) が見つかり、\\(3\\) 枚で済みます。</li> <li>反例 \\(coins = [1, 49, 50]\\):\\(amt = 98\\) とすると、貪欲アルゴリズムが得るのは \\(50 + 1 \\times 48\\) で、硬貨は計 \\(49\\) 枚です。一方、動的計画法なら最適解 \\(49 + 49\\) が見つかり、\\(2\\) 枚で済みます。</li> </ul> <p></p> <p> 図 15-2   貪欲アルゴリズムで最適解を見つけられない例 </p> <p>つまり、硬貨の両替問題では、貪欲アルゴリズムで全体の最適解を得られる保証はなく、非常に悪い解を得る可能性もあります。この問題には動的計画法の方が適しています。</p> <p>一般に、貪欲アルゴリズムが適するのは次の二つの場合です。</p> <ol> <li>最適解を得られる保証がある場合:バックトラッキングや動的計画法より効率的であることが多く、貪欲アルゴリズムが最良の選択になりやすい場合です。</li> <li>最適解に近い解を得られる場合:この場合にも貪欲アルゴリズムを使えます。複雑な問題では全体の最適解を探すのが非常に難しく、最適には及ばなくてもよい解を効率的に得られれば、十分に有用です。</li> </ol>","path":["第 15 章   貪欲法","15.1   貪欲アルゴリズム"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1512","level":2,"title":"15.1.2   貪欲アルゴリズムの性質","text":"<p>では、どのような問題が貪欲アルゴリズムに適しているのでしょうか。言い換えると、どのような場合に最適解を得られる保証があるのでしょうか。</p> <p>動的計画法と比べると、貪欲アルゴリズムを使える条件は厳しく、主に次の二つの性質に注目します。</p> <ul> <li>貪欲選択性:局所的に最適な選択が常に全体の最適解につながる場合に限り、貪欲アルゴリズムで最適解を得られることが保証されます。</li> <li>最適部分構造:元の問題の最適解の中に、部分問題の最適解が含まれています。</li> </ul> <p>最適部分構造は「動的計画法」の章で説明したため、ここでは繰り返しません。なお、最適部分構造が明らかでなくても、貪欲アルゴリズムで解ける問題があります。</p> <p>ここでは主に、貪欲選択性をどう判断するかを考えます。定義は簡単に見えますが、多くの問題で貪欲選択性を証明するのは容易ではありません。</p> <p>たとえば硬貨の両替問題では、反例を挙げて貪欲選択性を否定するのは簡単ですが、成り立つことを証明するのは難しくなります。どのような条件を満たす硬貨の組み合わせなら、貪欲アルゴリズムで解けるのでしょうか。直感や具体例から曖昧な答えを出せても、厳密に数学的証明を与えるのは難しいものです。</p> <p>引用</p> <p>ある論文では、硬貨の組み合わせが、任意の金額に対して貪欲アルゴリズムで最適解を求められるかどうかを判定する、時間計算量 \\(O(n^3)\\) のアルゴリズムが示されています。</p> <p>Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234.</p>","path":["第 15 章   貪欲法","15.1   貪欲アルゴリズム"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1513","level":2,"title":"15.1.3   貪欲アルゴリズムで問題を解く手順","text":"<p>貪欲法で問題を解く流れは、大きく次の 3 段階に分けられます。</p> <ol> <li>問題の分析:状態の定義、最適化の目標、制約条件など、問題の性質を整理して理解します。これはバックトラッキングや動的計画法でも行う段階です。</li> <li>貪欲戦略の決定:各段階でどのように貪欲な選択をするかを決めます。その戦略によって問題を少しずつ小さくし、最終的に全体を解決します。</li> <li>正しさの証明:通常は、貪欲選択性と最適部分構造が成り立つことを示す必要があります。帰納法や背理法などの数学的証明が必要になることがあります。</li> </ol> <p>貪欲戦略を決めることが解法の中心ですが、必ずしも容易ではありません。主な理由は次のとおりです。</p> <ul> <li>問題によって貪欲戦略が大きく異なります。少し考えて試せば戦略が見つかる問題も多い一方、複雑な問題では戦略が見えにくいこともあります。そのような場合は、問題を解いた経験やアルゴリズムの力が問われます。</li> <li>一見正しそうでも誤った貪欲戦略があります。自信を持って戦略を立て、コードを書いて提出しても、一部のテストに通らないことがあります。戦略が「一部の場合にしか正しくない」ためです。先ほどの硬貨の両替は、その典型例です。</li> </ul> <p>正しさを保証するには、貪欲戦略を厳密に数学的に証明する必要があり、通常は背理法や数学的帰納法を使います。</p> <p>ただし、正しさの証明自体も難しいことがあります。糸口がつかめない場合は、テストケースを使ってコードをデバッグし、貪欲戦略を少しずつ修正・検証していくことが多いです。</p>","path":["第 15 章   貪欲法","15.1   貪欲アルゴリズム"],"tags":[]},{"location":"chapter_greedy/greedy_algorithm/#1514","level":2,"title":"15.1.4   貪欲アルゴリズムの代表的な問題","text":"<p>貪欲アルゴリズムは、貪欲選択性と最適部分構造を持つ最適化問題でよく使われます。代表的な問題を次に挙げます。</p> <ul> <li>硬貨の両替問題:特定の硬貨の組み合わせでは、貪欲アルゴリズムで常に最適解を得られます。</li> <li>区間スケジューリング問題:それぞれ一定の時間区間で行う複数の作業があり、できるだけ多くを完了したいとします。毎回、終了時刻が最も早い作業を選べば、貪欲アルゴリズムで最適解を得られます。</li> <li>分割可能ナップサック問題:品物の集合と積載可能な重さが与えられ、総重量が制限を超えず、総価値が最大になるよう品物を選びます。毎回、価値と重さの比(価値 / 重さ)が最も高い品物を選ぶと、場合によっては貪欲アルゴリズムで最適解を得られます。</li> <li>株式の売買問題:株価の履歴が与えられ、売買を何度も行えます。ただし、株式を保有している間は、売るまで次の購入はできません。利益を最大にすることが目標です。</li> <li>ハフマン符号化:可逆データ圧縮に使う貪欲アルゴリズムです。出現頻度が最も低い二つのノードを毎回統合してハフマン木を作ると、重み付き経路長(符号長)が最小になります。</li> <li>Dijkstra 法:すべての辺の重みが非負のグラフで、指定した始点からほかの各頂点への最短経路を求める貪欲アルゴリズムです。</li> </ul>","path":["第 15 章   貪欲法","15.1   貪欲アルゴリズム"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/","level":1,"title":"15.3   最大容量問題","text":"<p>問題</p> <p>配列 \\(ht\\) が与えられ、各要素は垂直な仕切り板の高さを表します。任意の 2 枚の仕切り板とその間の空間で、容器を作れます。</p> <p>容器の容量は高さと幅の積(面積)です。高さは低い方の仕切り板で決まり、幅は 2 枚の仕切り板の配列インデックスの差です。</p> <p>容器の容量が最大になるように 2 枚の仕切り板を選び、最大容量を返してください。例を図 15-7 に示します。</p> <p></p> <p> 図 15-7   最大容量問題のデータ例 </p> <p>容器は任意の 2 枚の仕切り板で作るため、この問題の状態は 2 枚のインデックス \\([i, j]\\) で表せます。</p> <p>問題文によると、容量は高さと幅の積であり、高さは低い方の板、幅はインデックスの差で決まります。容量を \\(cap[i, j]\\) とすると、次の式が得られます。</p> \\[ cap[i, j] = \\min(ht[i], ht[j]) \\times (j - i) \\] <p>配列の長さを \\(n\\) とすると、2 枚の組み合わせ(状態の総数)は \\(C_n^2 = \\frac{n(n - 1)}{2}\\) 通りです。最も直接的には、すべての状態を列挙して最大容量を求められます。時間計算量は \\(O(n^2)\\) です。</p>","path":["第 15 章   貪欲法","15.3   最大容量問題"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#1","level":3,"title":"1.   貪欲戦略を決める","text":"<p>この問題には、さらに効率的な解法があります。図 15-8 のように、\\(i &lt; j\\) かつ \\(ht[i] &lt; ht[j]\\) を満たす状態 \\([i, j]\\) を選びます。つまり、\\(i\\) が低い板、\\(j\\) が高い板です。</p> <p></p> <p> 図 15-8   初期状態 </p> <p>図 15-9 のように、高い方の板 \\(j\\) を低い方の板 \\(i\\) に近づけると、容量は必ず小さくなります。</p> <p>板 \\(j\\) を動かすと、幅 \\(j-i\\) は必ず小さくなります。一方、高さは低い方の板で決まるため、変わらない(\\(i\\) が引き続き低い方)か、小さくなる(移動後の \\(j\\) が低い方になる)かのどちらかです。</p> <p></p> <p> 図 15-9   高い方の板を内側に動かした状態 </p> <p>逆に考えると、容量が大きくなる可能性があるのは、低い方の板 \\(i\\) を内側に動かす場合だけです。幅は必ず小さくなりますが、高さは大きくなる可能性があるからです(移動先の板 \\(i\\) がより高いことがあります)。たとえば図 15-10 では、低い方の板を動かすと面積が大きくなります。</p> <p></p> <p> 図 15-10   低い方の板を内側に動かした状態 </p> <p>ここから貪欲戦略が導けます。二つのポインタを容器の両端に置き、毎回、低い方の板に対応するポインタを内側に動かし、二つが出会うまで続けます。</p> <p>図 15-11 に、貪欲戦略の実行手順を示します。</p> <ol> <li>初期状態では、ポインタ \\(i\\) と \\(j\\) を配列の両端に置きます。</li> <li>現在の状態の容量 \\(cap[i, j]\\) を計算し、最大容量を更新します。</li> <li>板 \\(i\\) と板 \\(j\\) の高さを比較し、低い方を一つ内側に動かします。</li> <li>手順 <code>2.</code> と <code>3.</code> を繰り返し、\\(i\\) と \\(j\\) が出会ったら終了します。</li> </ol> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 15-11   最大容量問題を貪欲に解く流れ </p>","path":["第 15 章   貪欲法","15.3   最大容量問題"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#2","level":3,"title":"2.   コードの実装","text":"<p>ループは最大 \\(n\\) 回なので、時間計算量は \\(O(n)\\) です。</p> <p>変数 \\(i\\)、\\(j\\)、\\(res\\) が使う追加の空間は一定なので、空間計算量は \\(O(1)\\) です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby max_capacity.py<pre><code>def max_capacity(ht: list[int]) -&gt; int:\n \"\"\"最大容量:貪欲法\"\"\"\n # i, j を初期化し、配列の両端をそれぞれ指すようにする\n i, j = 0, len(ht) - 1\n # 最大容量の初期値は 0\n res = 0\n # 2 枚の板が出会うまで貪欲な選択を繰り返す\n while i &lt; j:\n # 最大容量を更新する\n cap = min(ht[i], ht[j]) * (j - i)\n res = max(res, cap)\n # 低い方の仕切り板を内側に移動する\n if ht[i] &lt; ht[j]:\n i += 1\n else:\n j -= 1\n return res\n</code></pre> max_capacity.cpp<pre><code>/* 最大容量:貪欲法 */\nint maxCapacity(vector&lt;int&gt; &amp;ht) {\n // i, j を初期化し、配列の両端をそれぞれ指すようにする\n int i = 0, j = ht.size() - 1;\n // 最大容量の初期値は 0\n int res = 0;\n // 2 枚の板が出会うまで貪欲な選択を繰り返す\n while (i &lt; j) {\n // 最大容量を更新する\n int cap = min(ht[i], ht[j]) * (j - i);\n res = max(res, cap);\n // 低い方の仕切り板を内側に移動する\n if (ht[i] &lt; ht[j]) {\n i++;\n } else {\n j--;\n }\n }\n return res;\n}\n</code></pre> max_capacity.java<pre><code>/* 最大容量:貪欲法 */\nint maxCapacity(int[] ht) {\n // i, j を初期化し、配列の両端をそれぞれ指すようにする\n int i = 0, j = ht.length - 1;\n // 最大容量の初期値は 0\n int res = 0;\n // 2 枚の板が出会うまで貪欲な選択を繰り返す\n while (i &lt; j) {\n // 最大容量を更新する\n int cap = Math.min(ht[i], ht[j]) * (j - i);\n res = Math.max(res, cap);\n // 低い方の仕切り板を内側に移動する\n if (ht[i] &lt; ht[j]) {\n i++;\n } else {\n j--;\n }\n }\n return res;\n}\n</code></pre> max_capacity.cs<pre><code>/* 最大容量:貪欲法 */\nint MaxCapacity(int[] ht) {\n // i, j を初期化し、配列の両端をそれぞれ指すようにする\n int i = 0, j = ht.Length - 1;\n // 最大容量の初期値は 0\n int res = 0;\n // 2 枚の板が出会うまで貪欲な選択を繰り返す\n while (i &lt; j) {\n // 最大容量を更新する\n int cap = Math.Min(ht[i], ht[j]) * (j - i);\n res = Math.Max(res, cap);\n // 低い方の仕切り板を内側に移動する\n if (ht[i] &lt; ht[j]) {\n i++;\n } else {\n j--;\n }\n }\n return res;\n}\n</code></pre> max_capacity.go<pre><code>/* 最大容量:貪欲法 */\nfunc maxCapacity(ht []int) int {\n // i, j を初期化し、配列の両端をそれぞれ指すようにする\n i, j := 0, len(ht)-1\n // 最大容量の初期値は 0\n res := 0\n // 2 枚の板が出会うまで貪欲な選択を繰り返す\n for i &lt; j {\n // 最大容量を更新する\n capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i)\n res = int(math.Max(float64(res), float64(capacity)))\n // 低い方の仕切り板を内側に移動する\n if ht[i] &lt; ht[j] {\n i++\n } else {\n j--\n }\n }\n return res\n}\n</code></pre> max_capacity.swift<pre><code>/* 最大容量:貪欲法 */\nfunc maxCapacity(ht: [Int]) -&gt; Int {\n // i, j を初期化し、配列の両端をそれぞれ指すようにする\n var i = ht.startIndex, j = ht.endIndex - 1\n // 最大容量の初期値は 0\n var res = 0\n // 2 枚の板が出会うまで貪欲な選択を繰り返す\n while i &lt; j {\n // 最大容量を更新する\n let cap = min(ht[i], ht[j]) * (j - i)\n res = max(res, cap)\n // 低い方の仕切り板を内側に移動する\n if ht[i] &lt; ht[j] {\n i += 1\n } else {\n j -= 1\n }\n }\n return res\n}\n</code></pre> max_capacity.js<pre><code>/* 最大容量:貪欲法 */\nfunction maxCapacity(ht) {\n // i, j を初期化し、配列の両端をそれぞれ指すようにする\n let i = 0,\n j = ht.length - 1;\n // 最大容量の初期値は 0\n let res = 0;\n // 2 枚の板が出会うまで貪欲な選択を繰り返す\n while (i &lt; j) {\n // 最大容量を更新する\n const cap = Math.min(ht[i], ht[j]) * (j - i);\n res = Math.max(res, cap);\n // 低い方の仕切り板を内側に移動する\n if (ht[i] &lt; ht[j]) {\n i += 1;\n } else {\n j -= 1;\n }\n }\n return res;\n}\n</code></pre> max_capacity.ts<pre><code>/* 最大容量:貪欲法 */\nfunction maxCapacity(ht: number[]): number {\n // i, j を初期化し、配列の両端をそれぞれ指すようにする\n let i = 0,\n j = ht.length - 1;\n // 最大容量の初期値は 0\n let res = 0;\n // 2 枚の板が出会うまで貪欲な選択を繰り返す\n while (i &lt; j) {\n // 最大容量を更新する\n const cap: number = Math.min(ht[i], ht[j]) * (j - i);\n res = Math.max(res, cap);\n // 低い方の仕切り板を内側に移動する\n if (ht[i] &lt; ht[j]) {\n i += 1;\n } else {\n j -= 1;\n }\n }\n return res;\n}\n</code></pre> max_capacity.dart<pre><code>/* 最大容量:貪欲法 */\nint maxCapacity(List&lt;int&gt; ht) {\n // i, j を初期化し、配列の両端をそれぞれ指すようにする\n int i = 0, j = ht.length - 1;\n // 最大容量の初期値は 0\n int res = 0;\n // 2 枚の板が出会うまで貪欲な選択を繰り返す\n while (i &lt; j) {\n // 最大容量を更新する\n int cap = min(ht[i], ht[j]) * (j - i);\n res = max(res, cap);\n // 低い方の仕切り板を内側に移動する\n if (ht[i] &lt; ht[j]) {\n i++;\n } else {\n j--;\n }\n }\n return res;\n}\n</code></pre> max_capacity.rs<pre><code>/* 最大容量:貪欲法 */\nfn max_capacity(ht: &amp;[i32]) -&gt; i32 {\n // i, j を初期化し、配列の両端をそれぞれ指すようにする\n let mut i = 0;\n let mut j = ht.len() - 1;\n // 最大容量の初期値は 0\n let mut res = 0;\n // 2 枚の板が出会うまで貪欲な選択を繰り返す\n while i &lt; j {\n // 最大容量を更新する\n let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32;\n res = std::cmp::max(res, cap);\n // 低い方の仕切り板を内側に移動する\n if ht[i] &lt; ht[j] {\n i += 1;\n } else {\n j -= 1;\n }\n }\n res\n}\n</code></pre> max_capacity.c<pre><code>/* 最大容量:貪欲法 */\nint maxCapacity(int ht[], int htLength) {\n // i, j を初期化し、配列の両端をそれぞれ指すようにする\n int i = 0;\n int j = htLength - 1;\n // 最大容量の初期値は 0\n int res = 0;\n // 2 枚の板が出会うまで貪欲な選択を繰り返す\n while (i &lt; j) {\n // 最大容量を更新する\n int capacity = myMin(ht[i], ht[j]) * (j - i);\n res = myMax(res, capacity);\n // 低い方の仕切り板を内側に移動する\n if (ht[i] &lt; ht[j]) {\n i++;\n } else {\n j--;\n }\n }\n return res;\n}\n</code></pre> max_capacity.kt<pre><code>/* 最大容量:貪欲法 */\nfun maxCapacity(ht: IntArray): Int {\n // i, j を初期化し、配列の両端をそれぞれ指すようにする\n var i = 0\n var j = ht.size - 1\n // 最大容量の初期値は 0\n var res = 0\n // 2 枚の板が出会うまで貪欲な選択を繰り返す\n while (i &lt; j) {\n // 最大容量を更新する\n val cap = min(ht[i], ht[j]) * (j - i)\n res = max(res, cap)\n // 低い方の仕切り板を内側に移動する\n if (ht[i] &lt; ht[j]) {\n i++\n } else {\n j--\n }\n }\n return res\n}\n</code></pre> max_capacity.rb<pre><code>### 最大容量:貪欲法 ###\ndef max_capacity(ht)\n # i, j を初期化し、配列の両端をそれぞれ指すようにする\n i, j = 0, ht.length - 1\n # 最大容量の初期値は 0\n res = 0\n\n # 2 枚の板が出会うまで貪欲な選択を繰り返す\n while i &lt; j\n # 最大容量を更新する\n cap = [ht[i], ht[j]].min * (j - i)\n res = [res, cap].max\n # 低い方の仕切り板を内側に移動する\n if ht[i] &lt; ht[j]\n i += 1\n else\n j -= 1\n end\n end\n\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 15 章   貪欲法","15.3   最大容量問題"],"tags":[]},{"location":"chapter_greedy/max_capacity_problem/#3","level":3,"title":"3.   正しさの証明","text":"<p>貪欲法が総当たりより速いのは、各回の貪欲な選択で一部の状態を「飛ばす」ためです。</p> <p>たとえば状態 \\(cap[i, j]\\) で、\\(i\\) が低い板、\\(j\\) が高い板だとします。低い方の \\(i\\) を貪欲に一つ内側へ動かすと、図 15-12 の状態が「飛ばされます」。つまり、その後はこれらの状態の容量を確認できなくなります。</p> \\[ cap[i, i+1], cap[i, i+2], \\dots, cap[i, j-2], cap[i, j-1] \\] <p></p> <p> 図 15-12   低い方の板を動かすことで飛ばされる状態 </p> <p>飛ばされる状態は、実際には高い方の板 \\(j\\) を内側へ動かして得られるすべての状態です。高い方の板を内側へ動かすと容量が必ず小さくなることは、すでに示しました。したがって、飛ばす状態が最適解になることはなく、飛ばしても最適解を見逃すことはありません。</p> <p>以上から、低い方の板を動かす操作は「安全」であり、貪欲戦略は有効です。</p>","path":["第 15 章   貪欲法","15.3   最大容量問題"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/","level":1,"title":"15.4   整数を分割して積を最大にする問題","text":"<p>問題</p> <p>正の整数 \\(n\\) を、少なくとも二つの正の整数の和に分けます。分割後のすべての整数の積の最大値を求めてください。図 15-13 に例を示します。</p> <p></p> <p> 図 15-13   整数を分割して積を最大にする問題の定義 </p> <p>\\(n\\) を \\(m\\) 個の整数に分け、それぞれを積の因数とします。\\(i\\) 番目の因数を \\(n_i\\) とすると、次の式が成り立ちます。</p> \\[ n = \\sum_{i=1}^{m}n_i \\] <p>この問題の目標は、すべての整数の因数の積を最大にすることです。すなわち、</p> \\[ \\max(\\prod_{i=1}^{m}n_i) \\] <p>考えるべきことは、分割数 \\(m\\) をいくつにし、それぞれの \\(n_i\\) をいくつにするかです。</p>","path":["第 15 章   貪欲法","15.4   整数を分割して積を最大にする問題"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#1","level":3,"title":"1.   貪欲戦略を決める","text":"<p>経験上、二つの整数の積は、その和より大きくなることがよくあります。\\(n\\) から因数 \\(2\\) を切り出すと、積は \\(2(n-2)\\) です。これを \\(n\\) と比較します。</p> \\[ \\begin{aligned} 2(n-2) &amp; \\geq n \\newline 2n - n - 4 &amp; \\geq 0 \\newline n &amp; \\geq 4 \\end{aligned} \\] <p>図 15-14 のように、\\(n \\geq 4\\) なら \\(2\\) を切り出すことで積が大きくなります。つまり、\\(4\\) 以上の整数は分割すべきだとわかります。</p> <p>貪欲戦略 1:分割結果に \\(\\geq 4\\) の因数が含まれるなら、それをさらに分割します。最終的に残る因数は、\\(1\\)、\\(2\\)、\\(3\\) の 3 種類だけにします。</p> <p></p> <p> 図 15-14   分割によって積が大きくなる </p> <p>次に、どの因数が最もよいかを考えます。\\(1\\)、\\(2\\)、\\(3\\) の中では、明らかに \\(1\\) が最も不利です。常に \\(1 \\times (n-1) &lt; n\\) なので、\\(1\\) を切り出すと、かえって積が小さくなるためです。</p> <p>図 15-15 のように、\\(n = 6\\) では \\(3 \\times 3 &gt; 2 \\times 2 \\times 2\\) です。つまり、\\(2\\) より \\(3\\) に分ける方が有利です。</p> <p>貪欲戦略 2:分割結果の \\(2\\) は、多くても二つにします。三つの \\(2\\) は常に二つの \\(3\\) に置き換えられ、その方が積が大きくなるためです。</p> <p></p> <p> 図 15-15   最適な分割の因数 </p> <p>以上から、次の貪欲戦略が導けます。</p> <ol> <li>入力された整数 \\(n\\) から因数 \\(3\\) を繰り返し切り出し、余りが \\(0\\)、\\(1\\)、\\(2\\) のいずれかになるまで続けます。</li> <li>余りが \\(0\\) なら、\\(n\\) は \\(3\\) の倍数なので、何もしません。</li> <li>余りが \\(2\\) なら、それ以上分割せずに残します。</li> <li>余りが \\(1\\) なら、\\(2 \\times 2 &gt; 1 \\times 3\\) なので、最後の \\(3\\) と余りの \\(1\\) を、二つの \\(2\\) に置き換えます。</li> </ol>","path":["第 15 章   貪欲法","15.4   整数を分割して積を最大にする問題"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#2","level":3,"title":"2.   コードの実装","text":"<p>図 15-16 のように、ループで整数を分割する必要はありません。切り捨ての整数除算で \\(3\\) の個数 \\(a\\) を求め、剰余演算で余り \\(b\\) を求めると、次の関係が成り立ちます。</p> \\[ n = 3 a + b \\] <p>境界の場合 \\(n \\leq 3\\) では、必ず \\(1\\) を一つ切り出す必要があり、積は \\(1 \\times (n - 1)\\) になります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby max_product_cutting.py<pre><code>def max_product_cutting(n: int) -&gt; int:\n \"\"\"分割後の積の最大化:貪欲法\"\"\"\n # n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if n &lt;= 3:\n return 1 * (n - 1)\n # 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n a, b = n // 3, n % 3\n if b == 1:\n # 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return int(math.pow(3, a - 1)) * 2 * 2\n if b == 2:\n # 余りが 2 の場合、何もしない\n return int(math.pow(3, a)) * 2\n # 余りが 0 の場合、何もしない\n return int(math.pow(3, a))\n</code></pre> max_product_cutting.cpp<pre><code>/* 分割後の積の最大化:貪欲法 */\nint maxProductCutting(int n) {\n // n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if (n &lt;= 3) {\n return 1 * (n - 1);\n }\n // 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n int a = n / 3;\n int b = n % 3;\n if (b == 1) {\n // 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return (int)pow(3, a - 1) * 2 * 2;\n }\n if (b == 2) {\n // 余りが 2 の場合、何もしない\n return (int)pow(3, a) * 2;\n }\n // 余りが 0 の場合、何もしない\n return (int)pow(3, a);\n}\n</code></pre> max_product_cutting.java<pre><code>/* 分割後の積の最大化:貪欲法 */\nint maxProductCutting(int n) {\n // n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if (n &lt;= 3) {\n return 1 * (n - 1);\n }\n // 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n int a = n / 3;\n int b = n % 3;\n if (b == 1) {\n // 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return (int) Math.pow(3, a - 1) * 2 * 2;\n }\n if (b == 2) {\n // 余りが 2 の場合、何もしない\n return (int) Math.pow(3, a) * 2;\n }\n // 余りが 0 の場合、何もしない\n return (int) Math.pow(3, a);\n}\n</code></pre> max_product_cutting.cs<pre><code>/* 分割後の積の最大化:貪欲法 */\nint MaxProductCutting(int n) {\n // n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if (n &lt;= 3) {\n return 1 * (n - 1);\n }\n // 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n int a = n / 3;\n int b = n % 3;\n if (b == 1) {\n // 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return (int)Math.Pow(3, a - 1) * 2 * 2;\n }\n if (b == 2) {\n // 余りが 2 の場合、何もしない\n return (int)Math.Pow(3, a) * 2;\n }\n // 余りが 0 の場合、何もしない\n return (int)Math.Pow(3, a);\n}\n</code></pre> max_product_cutting.go<pre><code>/* 分割後の積の最大化:貪欲法 */\nfunc maxProductCutting(n int) int {\n // n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if n &lt;= 3 {\n return 1 * (n - 1)\n }\n // 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n a := n / 3\n b := n % 3\n if b == 1 {\n // 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return int(math.Pow(3, float64(a-1))) * 2 * 2\n }\n if b == 2 {\n // 余りが 2 の場合、何もしない\n return int(math.Pow(3, float64(a))) * 2\n }\n // 余りが 0 の場合、何もしない\n return int(math.Pow(3, float64(a)))\n}\n</code></pre> max_product_cutting.swift<pre><code>/* 分割後の積の最大化:貪欲法 */\nfunc maxProductCutting(n: Int) -&gt; Int {\n // n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if n &lt;= 3 {\n return 1 * (n - 1)\n }\n // 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n let a = n / 3\n let b = n % 3\n if b == 1 {\n // 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return pow(3, a - 1) * 2 * 2\n }\n if b == 2 {\n // 余りが 2 の場合、何もしない\n return pow(3, a) * 2\n }\n // 余りが 0 の場合、何もしない\n return pow(3, a)\n}\n</code></pre> max_product_cutting.js<pre><code>/* 分割後の積の最大化:貪欲法 */\nfunction maxProductCutting(n) {\n // n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if (n &lt;= 3) {\n return 1 * (n - 1);\n }\n // 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n let a = Math.floor(n / 3);\n let b = n % 3;\n if (b === 1) {\n // 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return Math.pow(3, a - 1) * 2 * 2;\n }\n if (b === 2) {\n // 余りが 2 の場合、何もしない\n return Math.pow(3, a) * 2;\n }\n // 余りが 0 の場合、何もしない\n return Math.pow(3, a);\n}\n</code></pre> max_product_cutting.ts<pre><code>/* 分割後の積の最大化:貪欲法 */\nfunction maxProductCutting(n: number): number {\n // n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if (n &lt;= 3) {\n return 1 * (n - 1);\n }\n // 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n let a: number = Math.floor(n / 3);\n let b: number = n % 3;\n if (b === 1) {\n // 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return Math.pow(3, a - 1) * 2 * 2;\n }\n if (b === 2) {\n // 余りが 2 の場合、何もしない\n return Math.pow(3, a) * 2;\n }\n // 余りが 0 の場合、何もしない\n return Math.pow(3, a);\n}\n</code></pre> max_product_cutting.dart<pre><code>/* 分割後の積の最大化:貪欲法 */\nint maxProductCutting(int n) {\n // n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if (n &lt;= 3) {\n return 1 * (n - 1);\n }\n // 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n int a = n ~/ 3;\n int b = n % 3;\n if (b == 1) {\n // 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return (pow(3, a - 1) * 2 * 2).toInt();\n }\n if (b == 2) {\n // 余りが 2 の場合、何もしない\n return (pow(3, a) * 2).toInt();\n }\n // 余りが 0 の場合、何もしない\n return pow(3, a).toInt();\n}\n</code></pre> max_product_cutting.rs<pre><code>/* 分割後の積の最大化:貪欲法 */\nfn max_product_cutting(n: i32) -&gt; i32 {\n // n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if n &lt;= 3 {\n return 1 * (n - 1);\n }\n // 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n let a = n / 3;\n let b = n % 3;\n if b == 1 {\n // 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n 3_i32.pow(a as u32 - 1) * 2 * 2\n } else if b == 2 {\n // 余りが 2 の場合、何もしない\n 3_i32.pow(a as u32) * 2\n } else {\n // 余りが 0 の場合、何もしない\n 3_i32.pow(a as u32)\n }\n}\n</code></pre> max_product_cutting.c<pre><code>/* 分割後の積の最大化:貪欲法 */\nint maxProductCutting(int n) {\n // n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if (n &lt;= 3) {\n return 1 * (n - 1);\n }\n // 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n int a = n / 3;\n int b = n % 3;\n if (b == 1) {\n // 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return pow(3, a - 1) * 2 * 2;\n }\n if (b == 2) {\n // 余りが 2 の場合、何もしない\n return pow(3, a) * 2;\n }\n // 余りが 0 の場合、何もしない\n return pow(3, a);\n}\n</code></pre> max_product_cutting.kt<pre><code>/* 分割後の積の最大化:貪欲法 */\nfun maxProductCutting(n: Int): Int {\n // n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n if (n &lt;= 3) {\n return 1 * (n - 1)\n }\n // 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n val a = n / 3\n val b = n % 3\n if (b == 1) {\n // 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return 3.0.pow((a - 1)).toInt() * 2 * 2\n }\n if (b == 2) {\n // 余りが 2 の場合、何もしない\n return 3.0.pow(a).toInt() * 2 * 2\n }\n // 余りが 0 の場合、何もしない\n return 3.0.pow(a).toInt()\n}\n</code></pre> max_product_cutting.rb<pre><code>### 分割後の積の最大化:貪欲法 ###\ndef max_product_cutting(n)\n # n &lt;= 3 の場合、必ず 1 を 1 つ切り出す必要がある\n return 1 * (n - 1) if n &lt;= 3\n # 貪欲に 3 を切り出す。a は 3 の個数、b は余り\n a, b = n / 3, n % 3\n # 余りが 1 の場合、1 * 3 の組を 2 * 2 に変換する\n return (3.pow(a - 1) * 2 * 2).to_i if b == 1\n # 余りが 2 の場合、何もしない\n return (3.pow(a) * 2).to_i if b == 2\n # 余りが 0 の場合、何もしない\n 3.pow(a).to_i\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p></p> <p> 図 15-16   分割後の最大の積の計算方法 </p> <p>時間計算量は、プログラミング言語の累乗演算の実装によって決まります。Python を例にすると、よく使われる累乗の計算方法には三つあります。</p> <ul> <li>演算子 <code>**</code> と関数 <code>pow()</code> の時間計算量は、どちらも \\(O(\\log⁡ a)\\) です。</li> <li>関数 <code>math.pow()</code> は内部で C 標準ライブラリの <code>pow()</code> を呼び出します。浮動小数点の累乗を計算し、時間計算量は \\(O(1)\\) です。</li> </ul> <p>変数 \\(a\\) と \\(b\\) が使う追加の空間は一定なので、空間計算量は \\(O(1)\\) です。</p>","path":["第 15 章   貪欲法","15.4   整数を分割して積を最大にする問題"],"tags":[]},{"location":"chapter_greedy/max_product_cutting_problem/#3","level":3,"title":"3.   正しさの証明","text":"<p>背理法を使い、\\(n \\geq 4\\) の場合だけを考えます。</p> <ol> <li>すべての因数は \\(\\leq 3\\):最適な分割に \\(\\geq 4\\) の因数 \\(x\\) が含まれると仮定します。これをさらに \\(2(x-2)\\) に分けると、積を大きくする(または等しく保つ)ことができます。これは仮定に矛盾します。</li> <li>分割結果に \\(1\\) は含まれない:最適な分割に因数 \\(1\\) が含まれると仮定します。それを別の因数に足してまとめれば、積を大きくできます。これは仮定に矛盾します。</li> <li>分割結果の \\(2\\) は多くても二つ:最適な分割に三つの \\(2\\) が含まれると仮定します。これを二つの \\(3\\) に置き換えると、積が大きくなります。これは仮定に矛盾します。</li> </ol>","path":["第 15 章   貪欲法","15.4   整数を分割して積を最大にする問題"],"tags":[]},{"location":"chapter_greedy/summary/","level":1,"title":"15.5   まとめ","text":"","path":["第 15 章   貪欲法","15.5   まとめ"],"tags":[]},{"location":"chapter_greedy/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>貪欲法は通常、最適化問題の解決に用いられます。各意思決定段階で局所的に最適な選択を行い、大域的な最適解を得ることを目指します。</li> <li>貪欲法は貪欲な選択を繰り返し、各反復で問題をより小さな部分問題に変換して、問題が解けるまで続けます。</li> <li>貪欲法は実装が簡単なだけでなく、解を求める効率も高いです。動的計画法と比べて、通常は時間計算量が小さいです。</li> <li>硬貨の両替問題では、硬貨の組み合わせによっては貪欲法で最適解を必ず見つけられますが、そうでない組み合わせでは、非常に悪い解になることもあります。</li> <li>貪欲法で解くのに適した問題には、貪欲選択性と最適部分構造という二つの性質があります。貪欲選択性は、貪欲な戦略が有効であることを表します。</li> <li>複雑な問題では、貪欲選択性の証明は容易ではありません。一方、硬貨の両替問題のように、成り立たないことを示すほうが比較的容易です。</li> <li>貪欲法による問題解決は、主に問題の分析、貪欲な戦略の決定、正しさの証明という三つの手順に分かれます。戦略の決定が中心となり、正しさの証明が難所になることが多いです。</li> <li>分割可能ナップサック問題は、0-1 ナップサック問題と異なり品物の一部を選べるため、貪欲法で解けます。貪欲な戦略の正しさは背理法で証明できます。</li> <li>最大容量問題は全探索で解くと時間計算量が \\(O(n^2)\\) となります。貪欲な戦略を設計し、各反復で低い方の仕切り板を内側へ動かすことで、時間計算量を \\(O(n)\\) に改善できます。</li> <li>分割後の積を最大化する問題では、\\(\\geq 4\\) の整数はさらに分割すべきであり、最適な分割因子は \\(3\\) であるという二つの貪欲な戦略を順に導きました。コードには累乗の計算が含まれるため、時間計算量はその実装方法に依存し、通常は \\(O(1)\\) または \\(O(\\log n)\\) となります。</li> </ul>","path":["第 15 章   貪欲法","15.5   まとめ"],"tags":[]},{"location":"chapter_hashing/","level":1,"title":"第 6 章   ハッシュテーブル","text":"<p>概要</p> <p>コンピュータの世界では、ハッシュテーブルは賢い司書のような存在です。</p> <p>請求記号の計算方法を知っているため、目的の本を素早く見つけられます。</p>","path":["第 6 章   ハッシュテーブル"],"tags":[]},{"location":"chapter_hashing/#_1","level":2,"title":"この章の内容","text":"<ul> <li>6.1   ハッシュテーブル</li> <li>6.2   ハッシュ衝突</li> <li>6.3   ハッシュアルゴリズム</li> <li>6.4   まとめ</li> <li>6.5   練習問題</li> </ul>","path":["第 6 章   ハッシュテーブル"],"tags":[]},{"location":"chapter_hashing/exercises/","level":1,"title":"6.5   練習問題","text":"","path":["第 6 章   ハッシュテーブル","6.5   練習問題"],"tags":[]},{"location":"chapter_hashing/exercises/#651","level":2,"title":"6.5.1   知識の確認","text":"","path":["第 6 章   ハッシュテーブル","6.5   練習問題"],"tags":[]},{"location":"chapter_hashing/exercises/#1","level":3,"title":"1.   ハッシュ衝突が起きた後の探索","text":"<p>バケット数 5、ハッシュ関数 \\(h(x)=x \\bmod 5\\) のハッシュテーブルがあります。衝突した要素は、そのバケットのリストに順に格納します。 <code>[1, 6, 11, 7]</code> を順に挿入します。</p> <ol> <li>0~4 番のバケットの内容を書いてください。</li> <li>6 を探すとき、まずどのバケットに入り、どの要素を順に調べますか。</li> <li>問い 1 のバケットの内容を見ると、後から挿入した要素が先に挿入した要素を上書きしていますか。この衝突の処理方法に基づいて理由を説明してください。</li> </ol> 解答例 <ol> <li> <p>\\(1\\bmod5=6\\bmod5=11\\bmod5=1\\)、\\(7\\bmod5=2\\) なので、各バケットは次のとおりです。</p> <pre><code>0: []\n1: [1, 6, 11]\n2: [7]\n3: []\n4: []\n</code></pre> </li> <li> <p>6 を探すときは、まず 1 番のバケットに入り、1、6 の順に比較します。二回目の比較で見つかります。</p> </li> <li> <p>ハッシュ値が同じでも、同じバケットに入るというだけで、要素が等しいわけではありません。チェイン法では衝突した要素をすべてバケット内に保存し、 照会時に一つずつ比較するため、1、6、11 が互いを上書きすることはありません。</p> </li> </ol>","path":["第 6 章   ハッシュテーブル","6.5   練習問題"],"tags":[]},{"location":"chapter_hashing/exercises/#2","level":3,"title":"2.   ハッシュテーブルの拡張後、要素はどこへ行くか","text":"<p>チェイン法を使うハッシュテーブルに、もともと 5 個のバケットがあり、ハッシュ関数は \\(h(x)=x\\bmod5\\) です。 キー <code>[1, 6, 11]</code> は、すべて 1 番のバケットに入っています。</p> <p>バケット数を 7 に拡張し、ハッシュ関数も \\(h(x)=x\\bmod7\\) に変えます。</p> <ol> <li>1、6、11 の新しいバケット番号をそれぞれ計算してください。</li> <li>拡張後は、どのバケットに要素がありますか。</li> <li>拡張時に、元の 1 番のバケットのリストを、新しい 1 番のバケットへそのままコピーできますか。問い 1、2 の結果を使って理由を説明してください。</li> </ol> 解答例 <ol> <li> <p>新しいバケット番号は、それぞれ次のとおりです。</p> <ul> <li>\\(1\\bmod7=1\\)</li> <li>\\(6\\bmod7=6\\)</li> <li>\\(11\\bmod7=4\\)</li> </ul> </li> <li> <p>1 番のバケットに 1、4 番に 11、6 番に 6 が入ります。三つのキーは、同じバケットに集中しなくなります。</p> </li> <li> <p>そのままコピーすることはできません。バケット番号は「キーをバケット数で割った余り」で決まります。バケット数が 5 から 7 に変わると、同じキーでも番号が変わることがあるため、 各キーの位置を計算し直す必要があります。古い 1 番のバケットをそのままコピーすると、その後、新しい式で 6 と 11 を探す際に、 それぞれ 6 番と 4 番のバケットへ進むため、見つけられません。</p> </li> </ol>","path":["第 6 章   ハッシュテーブル","6.5   練習問題"],"tags":[]},{"location":"chapter_hashing/exercises/#3-6-11","level":3,"title":"3.   6 を削除しても 11 を見つけられるか","text":"<p>インデックス <code>0~4</code> の 5 か所に格納できるハッシュテーブルがあり、ハッシュ関数は \\(h(x)=x\\bmod5\\) です。 衝突した場合は、ハッシュ関数で求めたインデックスから右へ進み、最初の空き位置を探します。</p> <p><code>[1, 6, 11]</code> を順に挿入します。</p> <ol> <li>三つの数は、最終的にそれぞれどのインデックスに置かれますか。</li> <li>11 を探すとき、どのインデックスを順に調べますか。</li> <li>6 を削除するとき、その位置を単に「一度も使われていない空き位置」に変え、探索では空き位置に出会うと停止する場合、 再び 11 を探すと何が起きますか。その探索結果は正しいですか。問題があれば、どうすれば避けられますか。</li> </ol> 解答例 <ol> <li> <p>1 はインデックス 1 に置かれます。6 もインデックス 1 に対応しますが、衝突するためインデックス 2 に置かれます。 11 もインデックス 1 から始め、使用中のインデックス 1、2 を順に飛ばして、最終的にインデックス 3 に置かれます。</p> </li> <li> <p>11 を探すときは、インデックス <code>1、2、3</code> を順に調べ、インデックス 3 で見つけます。</p> </li> <li> <p>インデックス 2 を「一度も使われていない」空き位置に変えると、11 の探索はインデックス 1 を調べた後、インデックス 2 で停止し、 11 は存在しないと誤って判断します。削除時には「削除済み」の印を残す必要があります。 探索ではその印に出会っても次のインデックスへ進みます(インデックス 4 を越えると 0 に戻ります)。一方、その後の挿入では、この位置を再利用できます。</p> </li> </ol>","path":["第 6 章   ハッシュテーブル","6.5   練習問題"],"tags":[]},{"location":"chapter_hashing/exercises/#652","level":2,"title":"6.5.2   プログラミング演習","text":"","path":["第 6 章   ハッシュテーブル","6.5   練習問題"],"tags":[]},{"location":"chapter_hashing/exercises/#1_1","level":3,"title":"1.   二つの文字列の文字構成を比べる","text":"<p>英小文字だけからなる二つの文字列 <code>s</code> と <code>t</code> が与えられます。 <code>s</code> の文字の位置は自由に入れ替えられますが、文字の追加、削除、置換はできません。</p> <p>入れ替えによって <code>t</code> にできるかを判定し、できれば <code>true</code>、できなければ <code>false</code> を返してください。 各文字の出現回数をハッシュテーブルに記録し、文字をソートせずに解いてください。</p> 解くためのヒント <ol> <li>二つの文字列の長さが違えば、文字の構成も必ず違います</li> <li>ハッシュテーブルに各文字の個数を記録します。s を走査するときは、対応する個数を 1 増やします</li> <li>t を走査するときは、対応する個数を 1 減らします。最後にすべての個数が 0 であれば、文字の構成が同じです</li> </ol> <p>LeetCode</p>","path":["第 6 章   ハッシュテーブル","6.5   練習問題"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/","level":1,"title":"6.3   ハッシュアルゴリズム","text":"<p>前の二節では、ハッシュテーブルの仕組みと衝突への対処方法を紹介しました。ただし、オープンアドレス法もチェイン法も、衝突時に正しく動作することを保証するだけで、衝突の発生自体を減らすことはできません。</p> <p>衝突が頻繁すぎると、性能は急激に悪化します。図 6-8 のチェイン法では、理想的にはペアが各バケットへ均等に分布し、最高の照会効率を得られます。最悪の場合はすべて同じバケットに入り、時間計算量が \\(O(n)\\) に悪化します。</p> <p></p> <p> 図 6-8   ハッシュ衝突の最良と最悪の場合 </p> <p>ペアの分布を決めるのはハッシュ関数です。計算手順を思い出すと、まずハッシュ値を求め、配列の長さで剰余を取ります。</p> <pre><code>index = hash(key) % capacity\n</code></pre> <p>この式で容量 <code>capacity</code> が固定なら、ハッシュアルゴリズム <code>hash()</code> が出力値を決め、ひいてはハッシュテーブル内のペアの分布を決めます。</p> <p>したがって、衝突の確率を減らすには、<code>hash()</code> の設計に注目すべきです。</p>","path":["第 6 章   ハッシュテーブル","6.3   ハッシュアルゴリズム"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#631","level":2,"title":"6.3.1   ハッシュアルゴリズムの目標","text":"<p>速く安定したハッシュテーブルを実現するため、ハッシュアルゴリズムには次の特徴が必要です。</p> <ul> <li>決定性:同じ入力には常に同じ出力を返す必要があります。これにより、ハッシュテーブルの信頼性を確保できます。</li> <li>高い効率:ハッシュ値を十分速く計算できる必要があります。計算負荷が小さいほど、実用性が高まります。</li> <li>均等な分布:ペアがテーブル全体に均等に分布する必要があります。均等なほど、衝突の確率が低くなります。</li> </ul> <p>ハッシュアルゴリズムは、ハッシュテーブル以外の分野でも広く使われています。</p> <ul> <li>パスワードの保存:安全性を守るため、通常はパスワードを平文で直接保存せず、ハッシュ値を保存します。入力されたパスワードのハッシュ値を計算し、保存済みの値と比較します。一致すれば、正しいパスワードと見なします。</li> <li>データの完全性の確認:送信側はデータのハッシュ値を計算して一緒に送り、受信側は受け取ったデータのハッシュ値を再計算して、届いた値と比較できます。一致すれば、データは完全であると見なします。</li> </ul> <p>暗号分野では、ハッシュ値から元のパスワードを推測するなどの逆解析を防ぐため、より高い安全性が必要です。</p> <ul> <li>一方向性:ハッシュ値から入力データに関する情報を逆算できないことです。</li> <li>衝突耐性:異なる二つの入力で同じハッシュ値になるものを見つけることが、極めて困難であることです。</li> <li>雪崩効果:入力のわずかな変化が、出力の大きく予測できない変化を引き起こすことです。</li> </ul> <p>均等な分布と衝突耐性は、独立した概念です。分布が均等でも、衝突耐性があるとは限りません。例えば、ランダムな <code>key</code> に対する <code>key % 100</code> は均等な出力を生みます。しかし単純すぎるため、下二桁が等しい <code>key</code> はすべて同じ出力になります。そのため、ハッシュ値から使える <code>key</code> を容易に逆算し、パスワードを破れます。</p>","path":["第 6 章   ハッシュテーブル","6.3   ハッシュアルゴリズム"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#632","level":2,"title":"6.3.2   ハッシュアルゴリズムの設計","text":"<p>設計には多くの要因を考慮する必要があり、複雑な問題です。ただし、要件が厳しくない場面では、単純なアルゴリズムも設計できます。</p> <ul> <li>加算ハッシュ:入力の各文字の ASCII コードを足し、その合計をハッシュ値にします。</li> <li>乗算ハッシュ:乗算による非相関性を利用し、毎回定数を掛けながら各文字の ASCII コードをハッシュ値に累積します。</li> <li>排他的論理和ハッシュ:入力の各要素を XOR 演算で一つのハッシュ値に累積します。</li> <li>回転ハッシュ:各文字の ASCII コードを累積する前に、毎回ハッシュ値のビットを回転させます。</li> </ul> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby simple_hash.py<pre><code>def add_hash(key: str) -&gt; int:\n \"\"\"加算ハッシュ\"\"\"\n hash = 0\n modulus = 1000000007\n for c in key:\n hash += ord(c)\n return hash % modulus\n\ndef mul_hash(key: str) -&gt; int:\n \"\"\"乗算ハッシュ\"\"\"\n hash = 0\n modulus = 1000000007\n for c in key:\n hash = 31 * hash + ord(c)\n return hash % modulus\n\ndef xor_hash(key: str) -&gt; int:\n \"\"\"排他的論理和ハッシュ\"\"\"\n hash = 0\n modulus = 1000000007\n for c in key:\n hash ^= ord(c)\n return hash % modulus\n\ndef rot_hash(key: str) -&gt; int:\n \"\"\"回転ハッシュ\"\"\"\n hash = 0\n modulus = 1000000007\n for c in key:\n hash = (hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ ord(c)\n return hash % modulus\n</code></pre> simple_hash.cpp<pre><code>/* 加算ハッシュ */\nint addHash(string key) {\n long long hash = 0;\n const int MODULUS = 1000000007;\n for (unsigned char c : key) {\n hash = (hash + (int)c) % MODULUS;\n }\n return (int)hash;\n}\n\n/* 乗算ハッシュ */\nint mulHash(string key) {\n long long hash = 0;\n const int MODULUS = 1000000007;\n for (unsigned char c : key) {\n hash = (31 * hash + (int)c) % MODULUS;\n }\n return (int)hash;\n}\n\n/* 排他的論理和ハッシュ */\nint xorHash(string key) {\n int hash = 0;\n const int MODULUS = 1000000007;\n for (unsigned char c : key) {\n hash ^= (int)c;\n }\n return hash &amp; MODULUS;\n}\n\n/* 回転ハッシュ */\nint rotHash(string key) {\n long long hash = 0;\n const int MODULUS = 1000000007;\n for (unsigned char c : key) {\n hash = ((hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ (int)c) % MODULUS;\n }\n return (int)hash;\n}\n</code></pre> simple_hash.java<pre><code>/* 加算ハッシュ */\nint addHash(String key) {\n long hash = 0;\n final int MODULUS = 1000000007;\n for (char c : key.toCharArray()) {\n hash = (hash + (int) c) % MODULUS;\n }\n return (int) hash;\n}\n\n/* 乗算ハッシュ */\nint mulHash(String key) {\n long hash = 0;\n final int MODULUS = 1000000007;\n for (char c : key.toCharArray()) {\n hash = (31 * hash + (int) c) % MODULUS;\n }\n return (int) hash;\n}\n\n/* 排他的論理和ハッシュ */\nint xorHash(String key) {\n int hash = 0;\n final int MODULUS = 1000000007;\n for (char c : key.toCharArray()) {\n hash ^= (int) c;\n }\n return hash &amp; MODULUS;\n}\n\n/* 回転ハッシュ */\nint rotHash(String key) {\n long hash = 0;\n final int MODULUS = 1000000007;\n for (char c : key.toCharArray()) {\n hash = ((hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ (int) c) % MODULUS;\n }\n return (int) hash;\n}\n</code></pre> simple_hash.cs<pre><code>/* 加算ハッシュ */\nint AddHash(string key) {\n long hash = 0;\n const int MODULUS = 1000000007;\n foreach (char c in key) {\n hash = (hash + c) % MODULUS;\n }\n return (int)hash;\n}\n\n/* 乗算ハッシュ */\nint MulHash(string key) {\n long hash = 0;\n const int MODULUS = 1000000007;\n foreach (char c in key) {\n hash = (31 * hash + c) % MODULUS;\n }\n return (int)hash;\n}\n\n/* 排他的論理和ハッシュ */\nint XorHash(string key) {\n int hash = 0;\n const int MODULUS = 1000000007;\n foreach (char c in key) {\n hash ^= c;\n }\n return hash &amp; MODULUS;\n}\n\n/* 回転ハッシュ */\nint RotHash(string key) {\n long hash = 0;\n const int MODULUS = 1000000007;\n foreach (char c in key) {\n hash = ((hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ c) % MODULUS;\n }\n return (int)hash;\n}\n</code></pre> simple_hash.go<pre><code>/* 加算ハッシュ */\nfunc addHash(key string) int {\n var hash int64\n var modulus int64\n\n modulus = 1000000007\n for _, b := range []byte(key) {\n hash = (hash + int64(b)) % modulus\n }\n return int(hash)\n}\n\n/* 乗算ハッシュ */\nfunc mulHash(key string) int {\n var hash int64\n var modulus int64\n\n modulus = 1000000007\n for _, b := range []byte(key) {\n hash = (31*hash + int64(b)) % modulus\n }\n return int(hash)\n}\n\n/* 排他的論理和ハッシュ */\nfunc xorHash(key string) int {\n hash := 0\n modulus := 1000000007\n for _, b := range []byte(key) {\n fmt.Println(int(b))\n hash ^= int(b)\n hash = (31*hash + int(b)) % modulus\n }\n return hash &amp; modulus\n}\n\n/* 回転ハッシュ */\nfunc rotHash(key string) int {\n var hash int64\n var modulus int64\n\n modulus = 1000000007\n for _, b := range []byte(key) {\n hash = ((hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ int64(b)) % modulus\n }\n return int(hash)\n}\n</code></pre> simple_hash.swift<pre><code>/* 加算ハッシュ */\nfunc addHash(key: String) -&gt; Int {\n var hash = 0\n let MODULUS = 1_000_000_007\n for c in key {\n for scalar in c.unicodeScalars {\n hash = (hash + Int(scalar.value)) % MODULUS\n }\n }\n return hash\n}\n\n/* 乗算ハッシュ */\nfunc mulHash(key: String) -&gt; Int {\n var hash = 0\n let MODULUS = 1_000_000_007\n for c in key {\n for scalar in c.unicodeScalars {\n hash = (31 * hash + Int(scalar.value)) % MODULUS\n }\n }\n return hash\n}\n\n/* 排他的論理和ハッシュ */\nfunc xorHash(key: String) -&gt; Int {\n var hash = 0\n let MODULUS = 1_000_000_007\n for c in key {\n for scalar in c.unicodeScalars {\n hash ^= Int(scalar.value)\n }\n }\n return hash &amp; MODULUS\n}\n\n/* 回転ハッシュ */\nfunc rotHash(key: String) -&gt; Int {\n var hash = 0\n let MODULUS = 1_000_000_007\n for c in key {\n for scalar in c.unicodeScalars {\n hash = ((hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ Int(scalar.value)) % MODULUS\n }\n }\n return hash\n}\n</code></pre> simple_hash.js<pre><code>/* 加算ハッシュ */\nfunction addHash(key) {\n let hash = 0;\n const MODULUS = 1000000007;\n for (const c of key) {\n hash = (hash + c.charCodeAt(0)) % MODULUS;\n }\n return hash;\n}\n\n/* 乗算ハッシュ */\nfunction mulHash(key) {\n let hash = 0;\n const MODULUS = 1000000007;\n for (const c of key) {\n hash = (31 * hash + c.charCodeAt(0)) % MODULUS;\n }\n return hash;\n}\n\n/* 排他的論理和ハッシュ */\nfunction xorHash(key) {\n let hash = 0;\n const MODULUS = 1000000007;\n for (const c of key) {\n hash ^= c.charCodeAt(0);\n }\n return hash % MODULUS;\n}\n\n/* 回転ハッシュ */\nfunction rotHash(key) {\n let hash = 0;\n const MODULUS = 1000000007;\n for (const c of key) {\n hash = ((hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ c.charCodeAt(0)) % MODULUS;\n }\n return hash;\n}\n</code></pre> simple_hash.ts<pre><code>/* 加算ハッシュ */\nfunction addHash(key: string): number {\n let hash = 0;\n const MODULUS = 1000000007;\n for (const c of key) {\n hash = (hash + c.charCodeAt(0)) % MODULUS;\n }\n return hash;\n}\n\n/* 乗算ハッシュ */\nfunction mulHash(key: string): number {\n let hash = 0;\n const MODULUS = 1000000007;\n for (const c of key) {\n hash = (31 * hash + c.charCodeAt(0)) % MODULUS;\n }\n return hash;\n}\n\n/* 排他的論理和ハッシュ */\nfunction xorHash(key: string): number {\n let hash = 0;\n const MODULUS = 1000000007;\n for (const c of key) {\n hash ^= c.charCodeAt(0);\n }\n return hash % MODULUS;\n}\n\n/* 回転ハッシュ */\nfunction rotHash(key: string): number {\n let hash = 0;\n const MODULUS = 1000000007;\n for (const c of key) {\n hash = ((hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ c.charCodeAt(0)) % MODULUS;\n }\n return hash;\n}\n</code></pre> simple_hash.dart<pre><code>/* 加算ハッシュ */\nint addHash(String key) {\n int hash = 0;\n final int MODULUS = 1000000007;\n for (int i = 0; i &lt; key.length; i++) {\n hash = (hash + key.codeUnitAt(i)) % MODULUS;\n }\n return hash;\n}\n\n/* 乗算ハッシュ */\nint mulHash(String key) {\n int hash = 0;\n final int MODULUS = 1000000007;\n for (int i = 0; i &lt; key.length; i++) {\n hash = (31 * hash + key.codeUnitAt(i)) % MODULUS;\n }\n return hash;\n}\n\n/* 排他的論理和ハッシュ */\nint xorHash(String key) {\n int hash = 0;\n final int MODULUS = 1000000007;\n for (int i = 0; i &lt; key.length; i++) {\n hash ^= key.codeUnitAt(i);\n }\n return hash &amp; MODULUS;\n}\n\n/* 回転ハッシュ */\nint rotHash(String key) {\n int hash = 0;\n final int MODULUS = 1000000007;\n for (int i = 0; i &lt; key.length; i++) {\n hash = ((hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ key.codeUnitAt(i)) % MODULUS;\n }\n return hash;\n}\n</code></pre> simple_hash.rs<pre><code>/* 加算ハッシュ */\nfn add_hash(key: &amp;str) -&gt; i32 {\n let mut hash = 0_i64;\n const MODULUS: i64 = 1000000007;\n\n for c in key.chars() {\n hash = (hash + c as i64) % MODULUS;\n }\n\n hash as i32\n}\n\n/* 乗算ハッシュ */\nfn mul_hash(key: &amp;str) -&gt; i32 {\n let mut hash = 0_i64;\n const MODULUS: i64 = 1000000007;\n\n for c in key.chars() {\n hash = (31 * hash + c as i64) % MODULUS;\n }\n\n hash as i32\n}\n\n/* 排他的論理和ハッシュ */\nfn xor_hash(key: &amp;str) -&gt; i32 {\n let mut hash = 0_i64;\n const MODULUS: i64 = 1000000007;\n\n for c in key.chars() {\n hash ^= c as i64;\n }\n\n (hash &amp; MODULUS) as i32\n}\n\n/* 回転ハッシュ */\nfn rot_hash(key: &amp;str) -&gt; i32 {\n let mut hash = 0_i64;\n const MODULUS: i64 = 1000000007;\n\n for c in key.chars() {\n hash = ((hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ c as i64) % MODULUS;\n }\n\n hash as i32\n}\n</code></pre> simple_hash.c<pre><code>/* 加算ハッシュ */\nint addHash(char *key) {\n long long hash = 0;\n const int MODULUS = 1000000007;\n for (int i = 0; i &lt; strlen(key); i++) {\n hash = (hash + (unsigned char)key[i]) % MODULUS;\n }\n return (int)hash;\n}\n\n/* 乗算ハッシュ */\nint mulHash(char *key) {\n long long hash = 0;\n const int MODULUS = 1000000007;\n for (int i = 0; i &lt; strlen(key); i++) {\n hash = (31 * hash + (unsigned char)key[i]) % MODULUS;\n }\n return (int)hash;\n}\n\n/* 排他的論理和ハッシュ */\nint xorHash(char *key) {\n int hash = 0;\n const int MODULUS = 1000000007;\n\n for (int i = 0; i &lt; strlen(key); i++) {\n hash ^= (unsigned char)key[i];\n }\n return hash &amp; MODULUS;\n}\n\n/* 回転ハッシュ */\nint rotHash(char *key) {\n long long hash = 0;\n const int MODULUS = 1000000007;\n for (int i = 0; i &lt; strlen(key); i++) {\n hash = ((hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ (unsigned char)key[i]) % MODULUS;\n }\n\n return (int)hash;\n}\n</code></pre> simple_hash.kt<pre><code>/* 加算ハッシュ */\nfun addHash(key: String): Int {\n var hash = 0L\n val MODULUS = 1000000007\n for (c in key.toCharArray()) {\n hash = (hash + c.code) % MODULUS\n }\n return hash.toInt()\n}\n\n/* 乗算ハッシュ */\nfun mulHash(key: String): Int {\n var hash = 0L\n val MODULUS = 1000000007\n for (c in key.toCharArray()) {\n hash = (31 * hash + c.code) % MODULUS\n }\n return hash.toInt()\n}\n\n/* 排他的論理和ハッシュ */\nfun xorHash(key: String): Int {\n var hash = 0\n val MODULUS = 1000000007\n for (c in key.toCharArray()) {\n hash = hash xor c.code\n }\n return hash and MODULUS\n}\n\n/* 回転ハッシュ */\nfun rotHash(key: String): Int {\n var hash = 0L\n val MODULUS = 1000000007\n for (c in key.toCharArray()) {\n hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS\n }\n return hash.toInt()\n}\n</code></pre> simple_hash.rb<pre><code>### 加算ハッシュ ###\ndef add_hash(key)\n hash = 0\n modulus = 1_000_000_007\n\n key.each_char { |c| hash += c.ord }\n\n hash % modulus\nend\n\n### 乗算ハッシュ ###\ndef mul_hash(key)\n hash = 0\n modulus = 1_000_000_007\n\n key.each_char { |c| hash = 31 * hash + c.ord }\n\n hash % modulus\nend\n\n### 排他的論理和ハッシュ ###\ndef xor_hash(key)\n hash = 0\n modulus = 1_000_000_007\n\n key.each_char { |c| hash ^= c.ord }\n\n hash % modulus\nend\n\n### 回転ハッシュ ###\ndef rot_hash(key)\n hash = 0\n modulus = 1_000_000_007\n\n key.each_char { |c| hash = (hash &lt;&lt; 4) ^ (hash &gt;&gt; 28) ^ c.ord }\n\n hash % modulus\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>どの方法も最後に大きな素数 \\(1000000007\\) で剰余を取り、値を適切な範囲に収めています。なぜ素数を法とすることを強調するのでしょうか。合成数を法とすると、どのような欠点があるのでしょうか。興味深い問いです。</p> <p>先に結論を述べると、大きな素数を法とすることで、ハッシュ値の分布をできるだけ均等にできます。素数はほかの数と公約数を持たないため、剰余による周期的なパターンを減らし、衝突を避けられるからです。</p> <p>例えば、合成数 \\(9\\) を法に選ぶと、\\(3\\) で割り切れるため、\\(3\\) で割り切れる <code>key</code> はすべて \\(0\\)、\\(3\\)、\\(6\\) の三つに写されます。</p> \\[ \\begin{aligned} \\text{modulus} &amp; = 9 \\newline \\text{key} &amp; = \\{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \\dots \\} \\newline \\text{hash} &amp; = \\{ 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6,\\dots \\} \\end{aligned} \\] <p>入力 <code>key</code> がちょうどこのような等差数列なら、ハッシュ値が集中し、衝突が増えます。<code>modulus</code> を素数 \\(13\\) に置き換えると、<code>key</code> と <code>modulus</code> の間に公約数がないため、出力の均等性は大幅に向上します。</p> \\[ \\begin{aligned} \\text{modulus} &amp; = 13 \\newline \\text{key} &amp; = \\{ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, \\dots \\} \\newline \\text{hash} &amp; = \\{ 0, 3, 6, 9, 12, 2, 5, 8, 11, 1, 4, 7, \\dots \\} \\end{aligned} \\] <p><code>key</code> がランダムかつ一様に分布すると保証できるなら、法は素数でも合成数でもよく、どちらも均等なハッシュ値を出力します。一方、<code>key</code> の分布に何らかの周期性があると、合成数ではクラスタリングが起きやすくなります。</p> <p>したがって、通常は法に素数を選び、できれば十分大きくすることで、周期的なパターンをできるだけ取り除き、アルゴリズムの頑健性を高めます。</p>","path":["第 6 章   ハッシュテーブル","6.3   ハッシュアルゴリズム"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#633","level":2,"title":"6.3.3   主なハッシュアルゴリズム","text":"<p>ここまでの単純なアルゴリズムは脆弱で、設計目標には遠く及びません。例えば、加算と XOR は交換法則を満たすため、同じ文字を異なる順序で並べた文字列を区別できません。これは衝突を増やし、安全性の問題も引き起こす可能性があります。</p> <p>実際には、MD5、SHA-1、SHA-2、SHA-3 などの標準的なアルゴリズムをよく使います。任意の長さの入力を、固定長のハッシュ値へ写せます。</p> <p>この約一世紀、ハッシュアルゴリズムは改良と最適化を重ねてきました。性能向上に取り組む研究者がいる一方、安全性の問題を探す研究者やハッカーもいます。実用でよく使われるものを表 6-2 に示します。</p> <ul> <li>MD5 と SHA-1 への攻撃は何度も成功しているため、各種のセキュリティ用途では使用されなくなっています。</li> <li>SHA-2 系列の SHA-256 は最も安全なハッシュアルゴリズムの一つで、攻撃の成功例がまだないため、各種のセキュリティ用途やプロトコルでよく使われています。</li> <li>SHA-3 は SHA-2 より実装の負担が小さく、計算効率が高いものの、現時点での普及度は SHA-2 系列に及びません。</li> </ul> <p> 表 6-2   主なハッシュアルゴリズム </p> MD5 SHA-1 SHA-2 SHA-3 登場年 1992 1995 2002 2008 出力長 128 bit 160 bit 256/512 bit 224/256/384/512 bit ハッシュ衝突 比較的多い 比較的多い 非常に少ない 非常に少ない 安全性 低い、攻撃の成功例あり 低い、攻撃の成功例あり 高い 高い 用途 使用中止、データの完全性の確認には引き続き使用 使用中止 暗号資産の取引検証、電子署名など SHA-2 の代替に使用可能","path":["第 6 章   ハッシュテーブル","6.3   ハッシュアルゴリズム"],"tags":[]},{"location":"chapter_hashing/hash_algorithm/#634","level":2,"title":"6.3.4   データ構造のハッシュ値","text":"<p>ハッシュテーブルの <code>key</code> には、整数、小数、文字列などを使えます。言語は通常、これらの型に組み込みのハッシュアルゴリズムを用意し、バケットのインデックスの計算に使います。Python では、<code>hash()</code> でさまざまな型のハッシュ値を求められます。</p> <ul> <li>整数と真偽値のハッシュ値は、その値自体です。</li> <li>浮動小数点数と文字列の計算はやや複雑なので、興味があれば調べてみてください。</li> <li>タプルは各要素をハッシュ化し、それらを組み合わせて一つのハッシュ値にします。</li> <li>オブジェクトはメモリアドレスに基づいてハッシュ値を生成します。ハッシュメソッドをオーバーライドすれば、内容に基づく生成も可能です。</li> </ul> <p>ヒント</p> <p>組み込みのハッシュ値計算関数の定義や方法は、言語ごとに異なります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby built_in_hash.py<pre><code>num = 3\nhash_num = hash(num)\n# 整数 3 のハッシュ値は 3\n\nbol = True\nhash_bol = hash(bol)\n# 真偽値 True のハッシュ値は 1\n\ndec = 3.14159\nhash_dec = hash(dec)\n# 小数 3.14159 のハッシュ値は 326484311674566659\n\nstr = \"Hello 算法\"\nhash_str = hash(str)\n# 文字列「Hello 算法」のハッシュ値は 4617003410720528961\n\ntup = (12836, \"小哈\")\nhash_tup = hash(tup)\n# タプル (12836, '小哈') のハッシュ値は 1029005403108185979\n\nobj = ListNode(0)\nhash_obj = hash(obj)\n# ノードオブジェクト &lt;ListNode object at 0x1058fd810&gt; のハッシュ値は 274267521\n</code></pre> built_in_hash.cpp<pre><code>int num = 3;\nsize_t hashNum = hash&lt;int&gt;()(num);\n// 整数 3 のハッシュ値は 3\n\nbool bol = true;\nsize_t hashBol = hash&lt;bool&gt;()(bol);\n// 真偽値 1 のハッシュ値は 1\n\ndouble dec = 3.14159;\nsize_t hashDec = hash&lt;double&gt;()(dec);\n// 小数 3.14159 のハッシュ値は 4614256650576692846\n\nstring str = \"Hello 算法\";\nsize_t hashStr = hash&lt;string&gt;()(str);\n// 文字列「Hello 算法」のハッシュ値は 15466937326284535026\n\n// C++ の組み込み std::hash() は基本データ型のハッシュ値計算のみを提供する\n// 配列やオブジェクトのハッシュ値計算は自分で実装する\n</code></pre> built_in_hash.java<pre><code>int num = 3;\nint hashNum = Integer.hashCode(num);\n// 整数 3 のハッシュ値は 3\n\nboolean bol = true;\nint hashBol = Boolean.hashCode(bol);\n// 真偽値 true のハッシュ値は 1231\n\ndouble dec = 3.14159;\nint hashDec = Double.hashCode(dec);\n// 小数 3.14159 のハッシュ値は -1340954729\n\nString str = \"Hello 算法\";\nint hashStr = str.hashCode();\n// 文字列「Hello 算法」のハッシュ値は -727081396\n\nObject[] arr = { 12836, \"小哈\" };\nint hashTup = Arrays.hashCode(arr);\n// 配列 [12836, 小哈] のハッシュ値は 1151158\n\nListNode obj = new ListNode(0);\nint hashObj = obj.hashCode();\n// ノードオブジェクト utils.ListNode@7dc5e7b4 のハッシュ値は 2110121908\n</code></pre> built_in_hash.cs<pre><code>int num = 3;\nint hashNum = num.GetHashCode();\n// 整数 3 のハッシュ値は 3;\n\nbool bol = true;\nint hashBol = bol.GetHashCode();\n// 真偽値 true のハッシュ値は 1;\n\ndouble dec = 3.14159;\nint hashDec = dec.GetHashCode();\n// 小数 3.14159 のハッシュ値は -1340954729;\n\nstring str = \"Hello 算法\";\nint hashStr = str.GetHashCode();\n// 文字列「Hello 算法」のハッシュ値は -586107568;\n\nobject[] arr = [12836, \"小哈\"];\nint hashTup = arr.GetHashCode();\n// 配列 [12836, 小哈] のハッシュ値は 42931033;\n\nListNode obj = new(0);\nint hashObj = obj.GetHashCode();\n// ノードオブジェクト 0 のハッシュ値は 39053774;\n</code></pre> built_in_hash.go<pre><code>// Go には組み込みの hash code 関数がない\n</code></pre> built_in_hash.swift<pre><code>let num = 3\nlet hashNum = num.hashValue\n// 整数 3 のハッシュ値は 9047044699613009734\n\nlet bol = true\nlet hashBol = bol.hashValue\n// 真偽値 true のハッシュ値は -4431640247352757451\n\nlet dec = 3.14159\nlet hashDec = dec.hashValue\n// 小数 3.14159 のハッシュ値は -2465384235396674631\n\nlet str = \"Hello 算法\"\nlet hashStr = str.hashValue\n// 文字列「Hello 算法」のハッシュ値は -7850626797806988787\n\nlet arr = [AnyHashable(12836), AnyHashable(\"小哈\")]\nlet hashTup = arr.hashValue\n// 配列 [AnyHashable(12836), AnyHashable(\"小哈\")] のハッシュ値は -2308633508154532996\n\nlet obj = ListNode(x: 0)\nlet hashObj = obj.hashValue\n// ノードオブジェクト utils.ListNode のハッシュ値は -2434780518035996159\n</code></pre> built_in_hash.js<pre><code>// JavaScript には組み込みの hash code 関数がない\n</code></pre> built_in_hash.ts<pre><code>// TypeScript には組み込みの hash code 関数がない\n</code></pre> built_in_hash.dart<pre><code>int num = 3;\nint hashNum = num.hashCode;\n// 整数 3 のハッシュ値は 34803\n\nbool bol = true;\nint hashBol = bol.hashCode;\n// 真偽値 true のハッシュ値は 1231\n\ndouble dec = 3.14159;\nint hashDec = dec.hashCode;\n// 小数 3.14159 のハッシュ値は 2570631074981783\n\nString str = \"Hello 算法\";\nint hashStr = str.hashCode;\n// 文字列「Hello 算法」のハッシュ値は 468167534\n\nList arr = [12836, \"小哈\"];\nint hashArr = arr.hashCode;\n// 配列 [12836, 小哈] のハッシュ値は 976512528\n\nListNode obj = new ListNode(0);\nint hashObj = obj.hashCode;\n// ノードオブジェクト Instance of 'ListNode' のハッシュ値は 1033450432\n</code></pre> built_in_hash.rs<pre><code>use std::collections::hash_map::DefaultHasher;\nuse std::hash::{Hash, Hasher};\n\nlet num = 3;\nlet mut num_hasher = DefaultHasher::new();\nnum.hash(&amp;mut num_hasher);\nlet hash_num = num_hasher.finish();\n// 整数 3 のハッシュ値は 568126464209439262\n\nlet bol = true;\nlet mut bol_hasher = DefaultHasher::new();\nbol.hash(&amp;mut bol_hasher);\nlet hash_bol = bol_hasher.finish();\n// 真偽値 true のハッシュ値は 4952851536318644461\n\nlet dec: f32 = 3.14159;\nlet mut dec_hasher = DefaultHasher::new();\ndec.to_bits().hash(&amp;mut dec_hasher);\nlet hash_dec = dec_hasher.finish();\n// 小数 3.14159 のハッシュ値は 2566941990314602357\n\nlet str = \"Hello 算法\";\nlet mut str_hasher = DefaultHasher::new();\nstr.hash(&amp;mut str_hasher);\nlet hash_str = str_hasher.finish();\n// 文字列「Hello 算法」のハッシュ値は 16092673739211250988\n\nlet arr = (&amp;12836, &amp;\"小哈\");\nlet mut tup_hasher = DefaultHasher::new();\narr.hash(&amp;mut tup_hasher);\nlet hash_tup = tup_hasher.finish();\n// タプル (12836, \"小哈\") のハッシュ値は 1885128010422702749\n\nlet node = ListNode::new(42);\nlet mut hasher = DefaultHasher::new();\nnode.borrow().val.hash(&amp;mut hasher);\nlet hash = hasher.finish();\n// ノードオブジェクト RefCell { value: ListNode { val: 42, next: None } } のハッシュ値は 15387811073369036852\n</code></pre> built_in_hash.c<pre><code>// C には組み込みの hash code 関数がない\n</code></pre> built_in_hash.kt<pre><code>val num = 3\nval hashNum = num.hashCode()\n// 整数 3 のハッシュ値は 3\n\nval bol = true\nval hashBol = bol.hashCode()\n// 真偽値 true のハッシュ値は 1231\n\nval dec = 3.14159\nval hashDec = dec.hashCode()\n// 小数 3.14159 のハッシュ値は -1340954729\n\nval str = \"Hello 算法\"\nval hashStr = str.hashCode()\n// 文字列「Hello 算法」のハッシュ値は -727081396\n\nval arr = arrayOf&lt;Any&gt;(12836, \"小哈\")\nval hashTup = arr.hashCode()\n// 配列 [12836, 小哈] のハッシュ値は 189568618\n\nval obj = ListNode(0)\nval hashObj = obj.hashCode()\n// ノードオブジェクト utils.ListNode@1d81eb93 のハッシュ値は 495053715\n</code></pre> built_in_hash.rb<pre><code>num = 3\nhash_num = num.hash\n# 整数 3 のハッシュ値は -4385856518450339636\n\nbol = true\nhash_bol = bol.hash\n# 真偽値 true のハッシュ値は -1617938112149317027\n\ndec = 3.14159\nhash_dec = dec.hash\n# 小数 3.14159 のハッシュ値は -1479186995943067893\n\nstr = \"Hello 算法\"\nhash_str = str.hash\n# 文字列「Hello 算法」のハッシュ値は -4075943250025831763\n\ntup = [12836, '小哈']\nhash_tup = tup.hash\n# タプル (12836, '小哈') のハッシュ値は 1999544809202288822\n\nobj = ListNode.new(0)\nhash_obj = obj.hash\n# ノードオブジェクト #&lt;ListNode:0x000078133140ab70&gt; のハッシュ値は 4302940560806366381\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>多くの言語では、不変オブジェクトだけがハッシュテーブルの <code>key</code> になれます。リスト(動的配列)を <code>key</code> にすると、内容の変化とともにハッシュ値も変わり、元の <code>value</code> を照会できなくなります。</p> <p>連結リストのノードなど、自分で定義したオブジェクトは、メンバー変数が可変でもハッシュ化できます。通常、ハッシュ値がメモリアドレスに基づくためです。内容が変わってもアドレスが変わらなければ、ハッシュ値は変わりません。</p> <p>異なるコンソールで実行すると、出力されるハッシュ値が異なることに気付くかもしれません。Python インタープリタは起動のたびに、文字列のハッシュ関数へランダムなソルト(salt)を加えるためです。これにより HashDoS 攻撃を効果的に防ぎ、安全性を高めます。</p>","path":["第 6 章   ハッシュテーブル","6.3   ハッシュアルゴリズム"],"tags":[]},{"location":"chapter_hashing/hash_collision/","level":1,"title":"6.2   ハッシュ衝突","text":"<p>前節で述べたように、通常、ハッシュ関数の入力空間は出力空間よりはるかに大きいため、理論上、ハッシュ衝突は避けられません。例えば、入力がすべての整数で、出力の範囲が配列の容量なら、複数の整数が同じバケットのインデックスに写されることは必然です。</p> <p>ハッシュ衝突は誤った照会結果を生み、ハッシュテーブルの利用に重大な影響を与えます。衝突するたびに、解消するまで容量を拡張すれば、この問題を解決できます。単純で強引ながら有効ですが、多くのデータの移動とハッシュ値の計算が必要なため、効率が低すぎます。効率を高めるには、次の戦略を使えます。</p> <ol> <li>データ構造を改良して、ハッシュ衝突が起きても正しく動作できるようにします。</li> <li>必要な場合、つまり衝突が多くなった場合だけ容量を拡張します。</li> </ol> <p>主な構造の改良方法には、「チェイン法」と「オープンアドレス法」があります。</p>","path":["第 6 章   ハッシュテーブル","6.2   ハッシュ衝突"],"tags":[]},{"location":"chapter_hashing/hash_collision/#621","level":2,"title":"6.2.1   チェイン法","text":"<p>元のハッシュテーブルでは、各バケットに一つのペアしか格納できません。チェイン法(separate chaining)では、単一の要素を連結リストに置き換え、キーと値のペアをノードとして、衝突したペアをすべて同じリストに格納します。図 6-5 にその例を示します。</p> <p></p> <p> 図 6-5   チェイン法のハッシュテーブル </p> <p>チェイン法では、操作が次のように変わります。</p> <ul> <li>要素の照会:<code>key</code> を入力し、ハッシュ関数でバケットのインデックスを求め、リストの先頭ノードにアクセスします。その後、リストを走査して <code>key</code> を比較し、目的のペアを探します。</li> <li>要素の追加:ハッシュ関数でリストの先頭ノードにアクセスし、ノード、つまりキーと値のペアをリストに追加します。</li> <li>要素の削除:ハッシュ関数の結果に基づいてリストの先頭にアクセスし、走査して目的のノードを見つけ、削除します。</li> </ul> <p>チェイン法には、次の制約があります。</p> <ul> <li>使用領域が増える:連結リストはノードのポインタを含むため、配列よりメモリを多く使います。</li> <li>照会の効率が下がる:目的の要素を探すために、リストを線形に走査する必要があります。</li> </ul> <p>以下は単純な実装です。次の二点に注意してください。</p> <ul> <li>コードを簡単にするため、連結リストの代わりにリスト(動的配列)を使います。この場合、ハッシュテーブルの配列は複数のバケットを持ち、各バケットが一つのリストになります。</li> <li>容量拡張の処理も含めています。負荷率が \\(\\frac{2}{3}\\) を超えると、容量を元の \\(2\\) 倍にします。</li> </ul> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map_chaining.py<pre><code>class HashMapChaining:\n \"\"\"チェイン法のハッシュテーブル\"\"\"\n\n def __init__(self):\n \"\"\"コンストラクタ\"\"\"\n self.size = 0 # キーと値のペアの数\n self.capacity = 4 # ハッシュテーブルの容量\n self.load_thres = 2.0 / 3.0 # 容量の拡張を開始する負荷率のしきい値\n self.extend_ratio = 2 # 容量の拡張倍率\n self.buckets = [[] for _ in range(self.capacity)] # バケット配列\n\n def hash_func(self, key: int) -&gt; int:\n \"\"\"ハッシュ関数\"\"\"\n return key % self.capacity\n\n def load_factor(self) -&gt; float:\n \"\"\"負荷率\"\"\"\n return self.size / self.capacity\n\n def get(self, key: int) -&gt; str | None:\n \"\"\"取得操作\"\"\"\n index = self.hash_func(key)\n bucket = self.buckets[index]\n # バケットを走査し、key が見つかったら対応する val を返す\n for pair in bucket:\n if pair.key == key:\n return pair.val\n # key が見つからなかった場合、None を返す\n return None\n\n def put(self, key: int, val: str):\n \"\"\"追加操作\"\"\"\n # 負荷率がしきい値を超えた場合、容量を拡張する\n if self.load_factor() &gt; self.load_thres:\n self.extend()\n index = self.hash_func(key)\n bucket = self.buckets[index]\n # バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n for pair in bucket:\n if pair.key == key:\n pair.val = val\n return\n # key が存在しない場合、キーと値のペアを末尾に追加する\n pair = Pair(key, val)\n bucket.append(pair)\n self.size += 1\n\n def remove(self, key: int):\n \"\"\"削除操作\"\"\"\n index = self.hash_func(key)\n bucket = self.buckets[index]\n # バケットを走査し、キーと値のペアを削除する\n for pair in bucket:\n if pair.key == key:\n bucket.remove(pair)\n self.size -= 1\n break\n\n def extend(self):\n \"\"\"ハッシュテーブルの容量を拡張する\"\"\"\n # 元のハッシュテーブルを一時保存する\n buckets = self.buckets\n # 拡張後の新しいハッシュテーブルを初期化する\n self.capacity *= self.extend_ratio\n self.buckets = [[] for _ in range(self.capacity)]\n self.size = 0\n # キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for bucket in buckets:\n for pair in bucket:\n self.put(pair.key, pair.val)\n\n def print(self):\n \"\"\"ハッシュテーブルを表示する\"\"\"\n for bucket in self.buckets:\n res = []\n for pair in bucket:\n res.append(str(pair.key) + \" -&gt; \" + pair.val)\n print(res)\n</code></pre> hash_map_chaining.cpp<pre><code>/* チェイン法のハッシュテーブル */\nclass HashMapChaining {\n private:\n int size; // キーと値のペアの数\n int capacity; // ハッシュテーブルの容量\n double loadThres; // 容量の拡張を開始する負荷率のしきい値\n int extendRatio; // 容量の拡張倍率\n vector&lt;vector&lt;Pair *&gt;&gt; buckets; // バケット配列\n\n public:\n /* コンストラクタ */\n HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) {\n buckets.resize(capacity);\n }\n\n /* デストラクタ */\n ~HashMapChaining() {\n for (auto &amp;bucket : buckets) {\n for (Pair *pair : bucket) {\n // メモリを解放する\n delete pair;\n }\n }\n }\n\n /* ハッシュ関数 */\n int hashFunc(int key) {\n return key % capacity;\n }\n\n /* 負荷率 */\n double loadFactor() {\n return (double)size / (double)capacity;\n }\n\n /* 取得操作 */\n string get(int key) {\n int index = hashFunc(key);\n // バケットを走査し、key が見つかったら対応する val を返す\n for (Pair *pair : buckets[index]) {\n if (pair-&gt;key == key) {\n return pair-&gt;val;\n }\n }\n // key が見つからなかった場合、空文字列を返す\n return \"\";\n }\n\n /* 追加操作 */\n void put(int key, string val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (loadFactor() &gt; loadThres) {\n extend();\n }\n int index = hashFunc(key);\n // バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n for (Pair *pair : buckets[index]) {\n if (pair-&gt;key == key) {\n pair-&gt;val = val;\n return;\n }\n }\n // key が存在しない場合、キーと値のペアを末尾に追加する\n buckets[index].push_back(new Pair(key, val));\n size++;\n }\n\n /* 削除操作 */\n void remove(int key) {\n int index = hashFunc(key);\n auto &amp;bucket = buckets[index];\n // バケットを走査し、キーと値のペアを削除する\n for (int i = 0; i &lt; bucket.size(); i++) {\n if (bucket[i]-&gt;key == key) {\n Pair *tmp = bucket[i];\n bucket.erase(bucket.begin() + i); // キーと値のペアを削除する\n delete tmp; // メモリを解放する\n size--;\n return;\n }\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n void extend() {\n // 元のハッシュテーブルを一時保存する\n vector&lt;vector&lt;Pair *&gt;&gt; bucketsTmp = buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n capacity *= extendRatio;\n buckets.clear();\n buckets.resize(capacity);\n size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (auto &amp;bucket : bucketsTmp) {\n for (Pair *pair : bucket) {\n put(pair-&gt;key, pair-&gt;val);\n // メモリを解放する\n delete pair;\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n void print() {\n for (auto &amp;bucket : buckets) {\n cout &lt;&lt; \"[\";\n for (Pair *pair : bucket) {\n cout &lt;&lt; pair-&gt;key &lt;&lt; \" -&gt; \" &lt;&lt; pair-&gt;val &lt;&lt; \", \";\n }\n cout &lt;&lt; \"]\\n\";\n }\n }\n};\n</code></pre> hash_map_chaining.java<pre><code>/* チェイン法のハッシュテーブル */\nclass HashMapChaining {\n int size; // キーと値のペアの数\n int capacity; // ハッシュテーブルの容量\n double loadThres; // 容量の拡張を開始する負荷率のしきい値\n int extendRatio; // 容量の拡張倍率\n List&lt;List&lt;Pair&gt;&gt; buckets; // バケット配列\n\n /* コンストラクタ */\n public HashMapChaining() {\n size = 0;\n capacity = 4;\n loadThres = 2.0 / 3.0;\n extendRatio = 2;\n buckets = new ArrayList&lt;&gt;(capacity);\n for (int i = 0; i &lt; capacity; i++) {\n buckets.add(new ArrayList&lt;&gt;());\n }\n }\n\n /* ハッシュ関数 */\n int hashFunc(int key) {\n return key % capacity;\n }\n\n /* 負荷率 */\n double loadFactor() {\n return (double) size / capacity;\n }\n\n /* 取得操作 */\n String get(int key) {\n int index = hashFunc(key);\n List&lt;Pair&gt; bucket = buckets.get(index);\n // バケットを走査し、key が見つかったら対応する val を返す\n for (Pair pair : bucket) {\n if (pair.key == key) {\n return pair.val;\n }\n }\n // key が見つからなかった場合、null を返す\n return null;\n }\n\n /* 追加操作 */\n void put(int key, String val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (loadFactor() &gt; loadThres) {\n extend();\n }\n int index = hashFunc(key);\n List&lt;Pair&gt; bucket = buckets.get(index);\n // バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n for (Pair pair : bucket) {\n if (pair.key == key) {\n pair.val = val;\n return;\n }\n }\n // key が存在しない場合、キーと値のペアを末尾に追加する\n Pair pair = new Pair(key, val);\n bucket.add(pair);\n size++;\n }\n\n /* 削除操作 */\n void remove(int key) {\n int index = hashFunc(key);\n List&lt;Pair&gt; bucket = buckets.get(index);\n // バケットを走査し、キーと値のペアを削除する\n for (Pair pair : bucket) {\n if (pair.key == key) {\n bucket.remove(pair);\n size--;\n break;\n }\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n void extend() {\n // 元のハッシュテーブルを一時保存する\n List&lt;List&lt;Pair&gt;&gt; bucketsTmp = buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n capacity *= extendRatio;\n buckets = new ArrayList&lt;&gt;(capacity);\n for (int i = 0; i &lt; capacity; i++) {\n buckets.add(new ArrayList&lt;&gt;());\n }\n size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (List&lt;Pair&gt; bucket : bucketsTmp) {\n for (Pair pair : bucket) {\n put(pair.key, pair.val);\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n void print() {\n for (List&lt;Pair&gt; bucket : buckets) {\n List&lt;String&gt; res = new ArrayList&lt;&gt;();\n for (Pair pair : bucket) {\n res.add(pair.key + \" -&gt; \" + pair.val);\n }\n System.out.println(res);\n }\n }\n}\n</code></pre> hash_map_chaining.cs<pre><code>/* チェイン法のハッシュテーブル */\nclass HashMapChaining {\n int size; // キーと値のペアの数\n int capacity; // ハッシュテーブルの容量\n double loadThres; // 容量の拡張を開始する負荷率のしきい値\n int extendRatio; // 容量の拡張倍率\n List&lt;List&lt;Pair&gt;&gt; buckets; // バケット配列\n\n /* コンストラクタ */\n public HashMapChaining() {\n size = 0;\n capacity = 4;\n loadThres = 2.0 / 3.0;\n extendRatio = 2;\n buckets = new List&lt;List&lt;Pair&gt;&gt;(capacity);\n for (int i = 0; i &lt; capacity; i++) {\n buckets.Add([]);\n }\n }\n\n /* ハッシュ関数 */\n int HashFunc(int key) {\n return key % capacity;\n }\n\n /* 負荷率 */\n double LoadFactor() {\n return (double)size / capacity;\n }\n\n /* 取得操作 */\n public string? Get(int key) {\n int index = HashFunc(key);\n // バケットを走査し、key が見つかったら対応する val を返す\n foreach (Pair pair in buckets[index]) {\n if (pair.key == key) {\n return pair.val;\n }\n }\n // key が見つからなかった場合、null を返す\n return null;\n }\n\n /* 追加操作 */\n public void Put(int key, string val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (LoadFactor() &gt; loadThres) {\n Extend();\n }\n int index = HashFunc(key);\n // バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n foreach (Pair pair in buckets[index]) {\n if (pair.key == key) {\n pair.val = val;\n return;\n }\n }\n // key が存在しない場合、キーと値のペアを末尾に追加する\n buckets[index].Add(new Pair(key, val));\n size++;\n }\n\n /* 削除操作 */\n public void Remove(int key) {\n int index = HashFunc(key);\n // バケットを走査し、キーと値のペアを削除する\n foreach (Pair pair in buckets[index].ToList()) {\n if (pair.key == key) {\n buckets[index].Remove(pair);\n size--;\n break;\n }\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n void Extend() {\n // 元のハッシュテーブルを一時保存する\n List&lt;List&lt;Pair&gt;&gt; bucketsTmp = buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n capacity *= extendRatio;\n buckets = new List&lt;List&lt;Pair&gt;&gt;(capacity);\n for (int i = 0; i &lt; capacity; i++) {\n buckets.Add([]);\n }\n size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n foreach (List&lt;Pair&gt; bucket in bucketsTmp) {\n foreach (Pair pair in bucket) {\n Put(pair.key, pair.val);\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n public void Print() {\n foreach (List&lt;Pair&gt; bucket in buckets) {\n List&lt;string&gt; res = [];\n foreach (Pair pair in bucket) {\n res.Add(pair.key + \" -&gt; \" + pair.val);\n }\n foreach (string kv in res) {\n Console.WriteLine(kv);\n }\n }\n }\n}\n</code></pre> hash_map_chaining.go<pre><code>/* チェイン法のハッシュテーブル */\ntype hashMapChaining struct {\n size int // キーと値のペアの数\n capacity int // ハッシュテーブルの容量\n loadThres float64 // 容量の拡張を開始する負荷率のしきい値\n extendRatio int // 容量の拡張倍率\n buckets [][]pair // バケット配列\n}\n\n/* コンストラクタ */\nfunc newHashMapChaining() *hashMapChaining {\n buckets := make([][]pair, 4)\n for i := 0; i &lt; 4; i++ {\n buckets[i] = make([]pair, 0)\n }\n return &amp;hashMapChaining{\n size: 0,\n capacity: 4,\n loadThres: 2.0 / 3.0,\n extendRatio: 2,\n buckets: buckets,\n }\n}\n\n/* ハッシュ関数 */\nfunc (m *hashMapChaining) hashFunc(key int) int {\n return key % m.capacity\n}\n\n/* 負荷率 */\nfunc (m *hashMapChaining) loadFactor() float64 {\n return float64(m.size) / float64(m.capacity)\n}\n\n/* 取得操作 */\nfunc (m *hashMapChaining) get(key int) string {\n idx := m.hashFunc(key)\n bucket := m.buckets[idx]\n // バケットを走査し、key が見つかったら対応する val を返す\n for _, p := range bucket {\n if p.key == key {\n return p.val\n }\n }\n // key が見つからなかった場合、空文字列を返す\n return \"\"\n}\n\n/* 追加操作 */\nfunc (m *hashMapChaining) put(key int, val string) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if m.loadFactor() &gt; m.loadThres {\n m.extend()\n }\n idx := m.hashFunc(key)\n // バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n for i := range m.buckets[idx] {\n if m.buckets[idx][i].key == key {\n m.buckets[idx][i].val = val\n return\n }\n }\n // key が存在しない場合、キーと値のペアを末尾に追加する\n p := pair{\n key: key,\n val: val,\n }\n m.buckets[idx] = append(m.buckets[idx], p)\n m.size += 1\n}\n\n/* 削除操作 */\nfunc (m *hashMapChaining) remove(key int) {\n idx := m.hashFunc(key)\n // バケットを走査し、キーと値のペアを削除する\n for i, p := range m.buckets[idx] {\n if p.key == key {\n // スライスから削除する\n m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...)\n m.size -= 1\n break\n }\n }\n}\n\n/* ハッシュテーブルの容量を拡張する */\nfunc (m *hashMapChaining) extend() {\n // 元のハッシュテーブルを一時保存する\n tmpBuckets := make([][]pair, len(m.buckets))\n for i := 0; i &lt; len(m.buckets); i++ {\n tmpBuckets[i] = make([]pair, len(m.buckets[i]))\n copy(tmpBuckets[i], m.buckets[i])\n }\n // 拡張後の新しいハッシュテーブルを初期化する\n m.capacity *= m.extendRatio\n m.buckets = make([][]pair, m.capacity)\n for i := 0; i &lt; m.capacity; i++ {\n m.buckets[i] = make([]pair, 0)\n }\n m.size = 0\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for _, bucket := range tmpBuckets {\n for _, p := range bucket {\n m.put(p.key, p.val)\n }\n }\n}\n\n/* ハッシュテーブルを表示する */\nfunc (m *hashMapChaining) print() {\n var builder strings.Builder\n\n for _, bucket := range m.buckets {\n builder.WriteString(\"[\")\n for _, p := range bucket {\n builder.WriteString(strconv.Itoa(p.key) + \" -&gt; \" + p.val + \" \")\n }\n builder.WriteString(\"]\")\n fmt.Println(builder.String())\n builder.Reset()\n }\n}\n</code></pre> hash_map_chaining.swift<pre><code>/* チェイン法のハッシュテーブル */\nclass HashMapChaining {\n var size: Int // キーと値のペアの数\n var capacity: Int // ハッシュテーブルの容量\n var loadThres: Double // 容量の拡張を開始する負荷率のしきい値\n var extendRatio: Int // 容量の拡張倍率\n var buckets: [[Pair]] // バケット配列\n\n /* コンストラクタ */\n init() {\n size = 0\n capacity = 4\n loadThres = 2.0 / 3.0\n extendRatio = 2\n buckets = Array(repeating: [], count: capacity)\n }\n\n /* ハッシュ関数 */\n func hashFunc(key: Int) -&gt; Int {\n key % capacity\n }\n\n /* 負荷率 */\n func loadFactor() -&gt; Double {\n Double(size) / Double(capacity)\n }\n\n /* 取得操作 */\n func get(key: Int) -&gt; String? {\n let index = hashFunc(key: key)\n let bucket = buckets[index]\n // バケットを走査し、key が見つかったら対応する val を返す\n for pair in bucket {\n if pair.key == key {\n return pair.val\n }\n }\n // key が見つからなかった場合、nil を返す\n return nil\n }\n\n /* 追加操作 */\n func put(key: Int, val: String) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if loadFactor() &gt; loadThres {\n extend()\n }\n let index = hashFunc(key: key)\n let bucket = buckets[index]\n // バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n for pair in bucket {\n if pair.key == key {\n pair.val = val\n return\n }\n }\n // key が存在しない場合、キーと値のペアを末尾に追加する\n let pair = Pair(key: key, val: val)\n buckets[index].append(pair)\n size += 1\n }\n\n /* 削除操作 */\n func remove(key: Int) {\n let index = hashFunc(key: key)\n let bucket = buckets[index]\n // バケットを走査し、キーと値のペアを削除する\n for (pairIndex, pair) in bucket.enumerated() {\n if pair.key == key {\n buckets[index].remove(at: pairIndex)\n size -= 1\n break\n }\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n func extend() {\n // 元のハッシュテーブルを一時保存する\n let bucketsTmp = buckets\n // 拡張後の新しいハッシュテーブルを初期化する\n capacity *= extendRatio\n buckets = Array(repeating: [], count: capacity)\n size = 0\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for bucket in bucketsTmp {\n for pair in bucket {\n put(key: pair.key, val: pair.val)\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n func print() {\n for bucket in buckets {\n let res = bucket.map { \"\\($0.key) -&gt; \\($0.val)\" }\n Swift.print(res)\n }\n }\n}\n</code></pre> hash_map_chaining.js<pre><code>/* チェイン法のハッシュテーブル */\nclass HashMapChaining {\n #size; // キーと値のペアの数\n #capacity; // ハッシュテーブルの容量\n #loadThres; // 容量の拡張を開始する負荷率のしきい値\n #extendRatio; // 容量の拡張倍率\n #buckets; // バケット配列\n\n /* コンストラクタ */\n constructor() {\n this.#size = 0;\n this.#capacity = 4;\n this.#loadThres = 2.0 / 3.0;\n this.#extendRatio = 2;\n this.#buckets = new Array(this.#capacity).fill(null).map((x) =&gt; []);\n }\n\n /* ハッシュ関数 */\n #hashFunc(key) {\n return key % this.#capacity;\n }\n\n /* 負荷率 */\n #loadFactor() {\n return this.#size / this.#capacity;\n }\n\n /* 取得操作 */\n get(key) {\n const index = this.#hashFunc(key);\n const bucket = this.#buckets[index];\n // バケットを走査し、key が見つかったら対応する val を返す\n for (const pair of bucket) {\n if (pair.key === key) {\n return pair.val;\n }\n }\n // key が見つからなかった場合、null を返す\n return null;\n }\n\n /* 追加操作 */\n put(key, val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (this.#loadFactor() &gt; this.#loadThres) {\n this.#extend();\n }\n const index = this.#hashFunc(key);\n const bucket = this.#buckets[index];\n // バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n for (const pair of bucket) {\n if (pair.key === key) {\n pair.val = val;\n return;\n }\n }\n // key が存在しない場合、キーと値のペアを末尾に追加する\n const pair = new Pair(key, val);\n bucket.push(pair);\n this.#size++;\n }\n\n /* 削除操作 */\n remove(key) {\n const index = this.#hashFunc(key);\n let bucket = this.#buckets[index];\n // バケットを走査し、キーと値のペアを削除する\n for (let i = 0; i &lt; bucket.length; i++) {\n if (bucket[i].key === key) {\n bucket.splice(i, 1);\n this.#size--;\n break;\n }\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n #extend() {\n // 元のハッシュテーブルを一時保存する\n const bucketsTmp = this.#buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n this.#capacity *= this.#extendRatio;\n this.#buckets = new Array(this.#capacity).fill(null).map((x) =&gt; []);\n this.#size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (const bucket of bucketsTmp) {\n for (const pair of bucket) {\n this.put(pair.key, pair.val);\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n print() {\n for (const bucket of this.#buckets) {\n let res = [];\n for (const pair of bucket) {\n res.push(pair.key + ' -&gt; ' + pair.val);\n }\n console.log(res);\n }\n }\n}\n</code></pre> hash_map_chaining.ts<pre><code>/* チェイン法のハッシュテーブル */\nclass HashMapChaining {\n #size: number; // キーと値のペアの数\n #capacity: number; // ハッシュテーブルの容量\n #loadThres: number; // 容量の拡張を開始する負荷率のしきい値\n #extendRatio: number; // 容量の拡張倍率\n #buckets: Pair[][]; // バケット配列\n\n /* コンストラクタ */\n constructor() {\n this.#size = 0;\n this.#capacity = 4;\n this.#loadThres = 2.0 / 3.0;\n this.#extendRatio = 2;\n this.#buckets = new Array(this.#capacity).fill(null).map((x) =&gt; []);\n }\n\n /* ハッシュ関数 */\n #hashFunc(key: number): number {\n return key % this.#capacity;\n }\n\n /* 負荷率 */\n #loadFactor(): number {\n return this.#size / this.#capacity;\n }\n\n /* 取得操作 */\n get(key: number): string | null {\n const index = this.#hashFunc(key);\n const bucket = this.#buckets[index];\n // バケットを走査し、key が見つかったら対応する val を返す\n for (const pair of bucket) {\n if (pair.key === key) {\n return pair.val;\n }\n }\n // key が見つからなかった場合、null を返す\n return null;\n }\n\n /* 追加操作 */\n put(key: number, val: string): void {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (this.#loadFactor() &gt; this.#loadThres) {\n this.#extend();\n }\n const index = this.#hashFunc(key);\n const bucket = this.#buckets[index];\n // バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n for (const pair of bucket) {\n if (pair.key === key) {\n pair.val = val;\n return;\n }\n }\n // key が存在しない場合、キーと値のペアを末尾に追加する\n const pair = new Pair(key, val);\n bucket.push(pair);\n this.#size++;\n }\n\n /* 削除操作 */\n remove(key: number): void {\n const index = this.#hashFunc(key);\n let bucket = this.#buckets[index];\n // バケットを走査し、キーと値のペアを削除する\n for (let i = 0; i &lt; bucket.length; i++) {\n if (bucket[i].key === key) {\n bucket.splice(i, 1);\n this.#size--;\n break;\n }\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n #extend(): void {\n // 元のハッシュテーブルを一時保存する\n const bucketsTmp = this.#buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n this.#capacity *= this.#extendRatio;\n this.#buckets = new Array(this.#capacity).fill(null).map((x) =&gt; []);\n this.#size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (const bucket of bucketsTmp) {\n for (const pair of bucket) {\n this.put(pair.key, pair.val);\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n print(): void {\n for (const bucket of this.#buckets) {\n let res = [];\n for (const pair of bucket) {\n res.push(pair.key + ' -&gt; ' + pair.val);\n }\n console.log(res);\n }\n }\n}\n</code></pre> hash_map_chaining.dart<pre><code>/* チェイン法のハッシュテーブル */\nclass HashMapChaining {\n late int size; // キーと値のペアの数\n late int capacity; // ハッシュテーブルの容量\n late double loadThres; // 容量の拡張を開始する負荷率のしきい値\n late int extendRatio; // 容量の拡張倍率\n late List&lt;List&lt;Pair&gt;&gt; buckets; // バケット配列\n\n /* コンストラクタ */\n HashMapChaining() {\n size = 0;\n capacity = 4;\n loadThres = 2.0 / 3.0;\n extendRatio = 2;\n buckets = List.generate(capacity, (_) =&gt; []);\n }\n\n /* ハッシュ関数 */\n int hashFunc(int key) {\n return key % capacity;\n }\n\n /* 負荷率 */\n double loadFactor() {\n return size / capacity;\n }\n\n /* 取得操作 */\n String? get(int key) {\n int index = hashFunc(key);\n List&lt;Pair&gt; bucket = buckets[index];\n // バケットを走査し、key が見つかったら対応する val を返す\n for (Pair pair in bucket) {\n if (pair.key == key) {\n return pair.val;\n }\n }\n // key が見つからなかった場合、null を返す\n return null;\n }\n\n /* 追加操作 */\n void put(int key, String val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (loadFactor() &gt; loadThres) {\n extend();\n }\n int index = hashFunc(key);\n List&lt;Pair&gt; bucket = buckets[index];\n // バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n for (Pair pair in bucket) {\n if (pair.key == key) {\n pair.val = val;\n return;\n }\n }\n // key が存在しない場合、キーと値のペアを末尾に追加する\n Pair pair = Pair(key, val);\n bucket.add(pair);\n size++;\n }\n\n /* 削除操作 */\n void remove(int key) {\n int index = hashFunc(key);\n List&lt;Pair&gt; bucket = buckets[index];\n // バケットを走査し、キーと値のペアを削除する\n for (Pair pair in bucket) {\n if (pair.key == key) {\n bucket.remove(pair);\n size--;\n break;\n }\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n void extend() {\n // 元のハッシュテーブルを一時保存する\n List&lt;List&lt;Pair&gt;&gt; bucketsTmp = buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n capacity *= extendRatio;\n buckets = List.generate(capacity, (_) =&gt; []);\n size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (List&lt;Pair&gt; bucket in bucketsTmp) {\n for (Pair pair in bucket) {\n put(pair.key, pair.val);\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n void printHashMap() {\n for (List&lt;Pair&gt; bucket in buckets) {\n List&lt;String&gt; res = [];\n for (Pair pair in bucket) {\n res.add(\"${pair.key} -&gt; ${pair.val}\");\n }\n print(res);\n }\n }\n}\n</code></pre> hash_map_chaining.rs<pre><code>/* チェイン法のハッシュテーブル */\nstruct HashMapChaining {\n size: usize,\n capacity: usize,\n load_thres: f32,\n extend_ratio: usize,\n buckets: Vec&lt;Vec&lt;Pair&gt;&gt;,\n}\n\nimpl HashMapChaining {\n /* コンストラクタ */\n fn new() -&gt; Self {\n Self {\n size: 0,\n capacity: 4,\n load_thres: 2.0 / 3.0,\n extend_ratio: 2,\n buckets: vec![vec![]; 4],\n }\n }\n\n /* ハッシュ関数 */\n fn hash_func(&amp;self, key: i32) -&gt; usize {\n key as usize % self.capacity\n }\n\n /* 負荷率 */\n fn load_factor(&amp;self) -&gt; f32 {\n self.size as f32 / self.capacity as f32\n }\n\n /* 削除操作 */\n fn remove(&amp;mut self, key: i32) -&gt; Option&lt;String&gt; {\n let index = self.hash_func(key);\n\n // バケットを走査し、キーと値のペアを削除する\n for (i, p) in self.buckets[index].iter_mut().enumerate() {\n if p.key == key {\n let pair = self.buckets[index].remove(i);\n self.size -= 1;\n return Some(pair.val);\n }\n }\n\n // key が見つからなかった場合、None を返す\n None\n }\n\n /* ハッシュテーブルの容量を拡張する */\n fn extend(&amp;mut self) {\n // 元のハッシュテーブルを一時保存する\n let buckets_tmp = std::mem::take(&amp;mut self.buckets);\n\n // 拡張後の新しいハッシュテーブルを初期化する\n self.capacity *= self.extend_ratio;\n self.buckets = vec![Vec::new(); self.capacity as usize];\n self.size = 0;\n\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for bucket in buckets_tmp {\n for pair in bucket {\n self.put(pair.key, pair.val);\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n fn print(&amp;self) {\n for bucket in &amp;self.buckets {\n let mut res = Vec::new();\n for pair in bucket {\n res.push(format!(\"{} -&gt; {}\", pair.key, pair.val));\n }\n println!(\"{:?}\", res);\n }\n }\n\n /* 追加操作 */\n fn put(&amp;mut self, key: i32, val: String) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if self.load_factor() &gt; self.load_thres {\n self.extend();\n }\n\n let index = self.hash_func(key);\n\n // バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n for pair in self.buckets[index].iter_mut() {\n if pair.key == key {\n pair.val = val;\n return;\n }\n }\n\n // key が存在しない場合、キーと値のペアを末尾に追加する\n let pair = Pair { key, val };\n self.buckets[index].push(pair);\n self.size += 1;\n }\n\n /* 取得操作 */\n fn get(&amp;self, key: i32) -&gt; Option&lt;&amp;str&gt; {\n let index = self.hash_func(key);\n\n // バケットを走査し、key が見つかったら対応する val を返す\n for pair in self.buckets[index].iter() {\n if pair.key == key {\n return Some(&amp;pair.val);\n }\n }\n\n // key が見つからなかった場合、None を返す\n None\n }\n}\n</code></pre> hash_map_chaining.c<pre><code>/* 連結リストのノード */\ntypedef struct Node {\n Pair *pair;\n struct Node *next;\n} Node;\n\n/* チェイン法のハッシュテーブル */\ntypedef struct {\n int size; // キーと値のペアの数\n int capacity; // ハッシュテーブルの容量\n double loadThres; // 容量の拡張を開始する負荷率のしきい値\n int extendRatio; // 容量の拡張倍率\n Node **buckets; // バケット配列\n} HashMapChaining;\n\n/* コンストラクタ */\nHashMapChaining *newHashMapChaining() {\n HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining));\n hashMap-&gt;size = 0;\n hashMap-&gt;capacity = 4;\n hashMap-&gt;loadThres = 2.0 / 3.0;\n hashMap-&gt;extendRatio = 2;\n hashMap-&gt;buckets = (Node **)malloc(hashMap-&gt;capacity * sizeof(Node *));\n for (int i = 0; i &lt; hashMap-&gt;capacity; i++) {\n hashMap-&gt;buckets[i] = NULL;\n }\n return hashMap;\n}\n\n/* デストラクタ */\nvoid delHashMapChaining(HashMapChaining *hashMap) {\n for (int i = 0; i &lt; hashMap-&gt;capacity; i++) {\n Node *cur = hashMap-&gt;buckets[i];\n while (cur) {\n Node *tmp = cur;\n cur = cur-&gt;next;\n free(tmp-&gt;pair);\n free(tmp);\n }\n }\n free(hashMap-&gt;buckets);\n free(hashMap);\n}\n\n/* ハッシュ関数 */\nint hashFunc(HashMapChaining *hashMap, int key) {\n return key % hashMap-&gt;capacity;\n}\n\n/* 負荷率 */\ndouble loadFactor(HashMapChaining *hashMap) {\n return (double)hashMap-&gt;size / (double)hashMap-&gt;capacity;\n}\n\n/* 取得操作 */\nchar *get(HashMapChaining *hashMap, int key) {\n int index = hashFunc(hashMap, key);\n // バケットを走査し、key が見つかったら対応する val を返す\n Node *cur = hashMap-&gt;buckets[index];\n while (cur) {\n if (cur-&gt;pair-&gt;key == key) {\n return cur-&gt;pair-&gt;val;\n }\n cur = cur-&gt;next;\n }\n return \"\"; // key が見つからなかった場合、空文字列を返す\n}\n\n/* 追加操作 */\nvoid put(HashMapChaining *hashMap, int key, const char *val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (loadFactor(hashMap) &gt; hashMap-&gt;loadThres) {\n extend(hashMap);\n }\n int index = hashFunc(hashMap, key);\n // バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n Node *cur = hashMap-&gt;buckets[index];\n while (cur) {\n if (cur-&gt;pair-&gt;key == key) {\n strcpy(cur-&gt;pair-&gt;val, val); // 指定された key が見つかったら、対応する val を更新して戻る\n return;\n }\n cur = cur-&gt;next;\n }\n // key が存在しない場合、キーと値のペアを連結リストの先頭に追加する\n Pair *newPair = (Pair *)malloc(sizeof(Pair));\n newPair-&gt;key = key;\n strcpy(newPair-&gt;val, val);\n Node *newNode = (Node *)malloc(sizeof(Node));\n newNode-&gt;pair = newPair;\n newNode-&gt;next = hashMap-&gt;buckets[index];\n hashMap-&gt;buckets[index] = newNode;\n hashMap-&gt;size++;\n}\n\n/* ハッシュテーブルの容量を拡張する */\nvoid extend(HashMapChaining *hashMap) {\n // 元のハッシュテーブルを一時保存する\n int oldCapacity = hashMap-&gt;capacity;\n Node **oldBuckets = hashMap-&gt;buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n hashMap-&gt;capacity *= hashMap-&gt;extendRatio;\n hashMap-&gt;buckets = (Node **)malloc(hashMap-&gt;capacity * sizeof(Node *));\n for (int i = 0; i &lt; hashMap-&gt;capacity; i++) {\n hashMap-&gt;buckets[i] = NULL;\n }\n hashMap-&gt;size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (int i = 0; i &lt; oldCapacity; i++) {\n Node *cur = oldBuckets[i];\n while (cur) {\n put(hashMap, cur-&gt;pair-&gt;key, cur-&gt;pair-&gt;val);\n Node *temp = cur;\n cur = cur-&gt;next;\n // メモリを解放する\n free(temp-&gt;pair);\n free(temp);\n }\n }\n\n free(oldBuckets);\n}\n\n/* 削除操作 */\nvoid removeItem(HashMapChaining *hashMap, int key) {\n int index = hashFunc(hashMap, key);\n Node *cur = hashMap-&gt;buckets[index];\n Node *pre = NULL;\n while (cur) {\n if (cur-&gt;pair-&gt;key == key) {\n // キーと値のペアを削除する\n if (pre) {\n pre-&gt;next = cur-&gt;next;\n } else {\n hashMap-&gt;buckets[index] = cur-&gt;next;\n }\n // メモリを解放する\n free(cur-&gt;pair);\n free(cur);\n hashMap-&gt;size--;\n return;\n }\n pre = cur;\n cur = cur-&gt;next;\n }\n}\n\n/* ハッシュテーブルを表示する */\nvoid print(HashMapChaining *hashMap) {\n for (int i = 0; i &lt; hashMap-&gt;capacity; i++) {\n Node *cur = hashMap-&gt;buckets[i];\n printf(\"[\");\n while (cur) {\n printf(\"%d -&gt; %s, \", cur-&gt;pair-&gt;key, cur-&gt;pair-&gt;val);\n cur = cur-&gt;next;\n }\n printf(\"]\\n\");\n }\n}\n</code></pre> hash_map_chaining.kt<pre><code>/* チェイン法のハッシュテーブル */\nclass HashMapChaining {\n var size: Int // キーと値のペアの数\n var capacity: Int // ハッシュテーブルの容量\n val loadThres: Double // 容量の拡張を開始する負荷率のしきい値\n val extendRatio: Int // 容量の拡張倍率\n var buckets: MutableList&lt;MutableList&lt;Pair&gt;&gt; // バケット配列\n\n /* コンストラクタ */\n init {\n size = 0\n capacity = 4\n loadThres = 2.0 / 3.0\n extendRatio = 2\n buckets = mutableListOf()\n for (i in 0..&lt;capacity) {\n buckets.add(mutableListOf())\n }\n }\n\n /* ハッシュ関数 */\n fun hashFunc(key: Int): Int {\n return key % capacity\n }\n\n /* 負荷率 */\n fun loadFactor(): Double {\n return (size / capacity).toDouble()\n }\n\n /* 取得操作 */\n fun get(key: Int): String? {\n val index = hashFunc(key)\n val bucket = buckets[index]\n // バケットを走査し、key が見つかったら対応する val を返す\n for (pair in bucket) {\n if (pair.key == key) return pair._val\n }\n // key が見つからなかった場合、null を返す\n return null\n }\n\n /* 追加操作 */\n fun put(key: Int, _val: String) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (loadFactor() &gt; loadThres) {\n extend()\n }\n val index = hashFunc(key)\n val bucket = buckets[index]\n // バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n for (pair in bucket) {\n if (pair.key == key) {\n pair._val = _val\n return\n }\n }\n // key が存在しない場合、キーと値のペアを末尾に追加する\n val pair = Pair(key, _val)\n bucket.add(pair)\n size++\n }\n\n /* 削除操作 */\n fun remove(key: Int) {\n val index = hashFunc(key)\n val bucket = buckets[index]\n // バケットを走査し、キーと値のペアを削除する\n for (pair in bucket) {\n if (pair.key == key) {\n bucket.remove(pair)\n size--\n break\n }\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n fun extend() {\n // 元のハッシュテーブルを一時保存する\n val bucketsTmp = buckets\n // 拡張後の新しいハッシュテーブルを初期化する\n capacity *= extendRatio\n // mutablelist のサイズは固定ではない\n buckets = mutableListOf()\n for (i in 0..&lt;capacity) {\n buckets.add(mutableListOf())\n }\n size = 0\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (bucket in bucketsTmp) {\n for (pair in bucket) {\n put(pair.key, pair._val)\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n fun print() {\n for (bucket in buckets) {\n val res = mutableListOf&lt;String&gt;()\n for (pair in bucket) {\n val k = pair.key\n val v = pair._val\n res.add(\"$k -&gt; $v\")\n }\n println(res)\n }\n }\n}\n</code></pre> hash_map_chaining.rb<pre><code>### チェイン法のハッシュテーブル ###\nclass HashMapChaining\n ### コンストラクタ ###\n def initialize\n @size = 0 # キーと値のペアの数\n @capacity = 4 # ハッシュテーブルの容量\n @load_thres = 2.0 / 3.0 # 容量の拡張を開始する負荷率のしきい値\n @extend_ratio = 2 # 容量の拡張倍率\n @buckets = Array.new(@capacity) { [] } # バケット配列\n end\n\n ### ハッシュ関数 ###\n def hash_func(key)\n key % @capacity\n end\n\n ### 負荷率 ###\n def load_factor\n @size / @capacity\n end\n\n ### 取得操作 ###\n def get(key)\n index = hash_func(key)\n bucket = @buckets[index]\n # バケットを走査し、key が見つかったら対応する val を返す\n for pair in bucket\n return pair.val if pair.key == key\n end\n # key が見つからなかった場合、nil を返す\n nil\n end\n\n ### 追加操作 ###\n def put(key, val)\n # 負荷率がしきい値を超えた場合、容量を拡張する\n extend if load_factor &gt; @load_thres\n index = hash_func(key)\n bucket = @buckets[index]\n # バケットを走査し、指定された key が見つかったら、対応する val を更新して戻る\n for pair in bucket\n if pair.key == key\n pair.val = val\n return\n end\n end\n # key が存在しない場合、キーと値のペアを末尾に追加する\n pair = Pair.new(key, val)\n bucket &lt;&lt; pair\n @size += 1\n end\n\n ### 削除操作 ###\n def remove(key)\n index = hash_func(key)\n bucket = @buckets[index]\n # バケットを走査し、キーと値のペアを削除する\n for pair in bucket\n if pair.key == key\n bucket.delete(pair)\n @size -= 1\n break\n end\n end\n end\n\n ### ハッシュテーブルの容量を拡張する ###\n def extend\n # 元のハッシュテーブルを一時保存する\n buckets = @buckets\n # 拡張後の新しいハッシュテーブルを初期化する\n @capacity *= @extend_ratio\n @buckets = Array.new(@capacity) { [] }\n @size = 0\n # キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for bucket in buckets\n for pair in bucket\n put(pair.key, pair.val)\n end\n end\n end\n\n ### ハッシュテーブルを表示する ###\n def print\n for bucket in @buckets\n res = []\n for pair in bucket\n res &lt;&lt; \"#{pair.key} -&gt; #{pair.val}\"\n end\n pp res\n end\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>連結リストが長い場合、\\(O(n)\\) の照会は効率が悪くなります。その場合、リストを AVL 木や赤黒木へ変換すれば、照会の時間計算量を \\(O(\\log n)\\) に改善できます。</p>","path":["第 6 章   ハッシュテーブル","6.2   ハッシュ衝突"],"tags":[]},{"location":"chapter_hashing/hash_collision/#622","level":2,"title":"6.2.2   オープンアドレス法","text":"<p>オープンアドレス法(open addressing)は、追加のデータ構造を導入せず、複数回の探索で衝突に対処します。主な方法には線形探索法、平方探索法、複数のハッシュ関数を使う方法などがあります。</p> <p>以下では線形探索法を例に、その仕組みを説明します。</p>","path":["第 6 章   ハッシュテーブル","6.2   ハッシュ衝突"],"tags":[]},{"location":"chapter_hashing/hash_collision/#1","level":3,"title":"1.   線形探索法","text":"<p>線形探索法は、一定の間隔で線形に探します。通常のハッシュテーブルとは操作が異なります。</p> <ul> <li>要素の挿入:ハッシュ関数でバケットのインデックスを求め、すでに要素があれば、衝突位置から後ろへ一定間隔(通常は \\(1\\))で走査し、空のバケットを見つけて挿入します。</li> <li>要素の探索:衝突したら同じ間隔で後ろへ線形に走査し、目的の要素を見つけて <code>value</code> を返します。空のバケットに出会ったら、その要素は存在しないので <code>None</code> を返します。</li> </ul> <p>図 6-6 は、オープンアドレス法(線形探索法)におけるペアの分布です。このハッシュ関数では、下二桁が等しい <code>key</code> は同じバケットに写されます。線形探索によって、そのバケットと後続のバケットへ順に格納されます。</p> <p></p> <p> 図 6-6   オープンアドレス法(線形探索法)でのキーと値のペアの分布 </p> <p>ただし、線形探索法では「クラスタリング」が起きやすくなります。連続して使用中の場所が長くなるほど、そこで衝突する確率が高まり、さらに塊が成長するという悪循環が生じます。その結果、追加、削除、照会、更新の効率が悪化します。</p> <p>オープンアドレス法では、要素を直接削除できないことにも注意が必要です。削除すると配列内に空のバケット <code>None</code> ができ、照会時の線形探索はそこで終了してしまいます。その先の要素に到達できなくなり、存在しないと誤判定する可能性があります。図 6-7 に示します。</p> <p></p> <p> 図 6-7   オープンアドレス法で削除すると起きる照会の問題 </p> <p>この問題には、遅延削除(lazy deletion)を使えます。要素を直接取り除かず、定数 <code>TOMBSTONE</code> でそのバケットに印を付けます。<code>None</code> と <code>TOMBSTONE</code> はどちらも空のバケットを表し、新しいペアを置けます。ただし、<code>TOMBSTONE</code> の先にはペアがある可能性があるため、線形探索を続ける必要があります。</p> <p>一方、遅延削除は性能の悪化を早める可能性があります。削除のたびに印が増え、<code>TOMBSTONE</code> が多くなると、目的の要素までいくつも飛び越える必要が生じ、探索時間が長くなるためです。</p> <p>そこで、線形探索で最初に出会った <code>TOMBSTONE</code> のインデックスを記録し、見つかった目的の要素と位置を交換します。照会や追加のたびに、要素が理想の位置、つまり探索開始位置に近いバケットへ移るため、照会効率を改善できます。</p> <p>以下は、遅延削除を備えたオープンアドレス法(線形探索法)の実装です。領域を十分に活用するため、ハッシュテーブルを循環配列と見なし、末尾を越えたら先頭に戻って走査を続けます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map_open_addressing.py<pre><code>class HashMapOpenAddressing:\n \"\"\"オープンアドレス法のハッシュテーブル\"\"\"\n\n def __init__(self):\n \"\"\"コンストラクタ\"\"\"\n self.size = 0 # キーと値のペアの数\n self.capacity = 4 # ハッシュテーブルの容量\n self.load_thres = 2.0 / 3.0 # 容量の拡張を開始する負荷率のしきい値\n self.extend_ratio = 2 # 容量の拡張倍率\n self.buckets: list[Pair | None] = [None] * self.capacity # バケット配列\n self.TOMBSTONE = Pair(-1, \"-1\") # 削除マーカー\n\n def hash_func(self, key: int) -&gt; int:\n \"\"\"ハッシュ関数\"\"\"\n return key % self.capacity\n\n def load_factor(self) -&gt; float:\n \"\"\"負荷率\"\"\"\n return self.size / self.capacity\n\n def find_bucket(self, key: int) -&gt; int:\n \"\"\"key に対応するバケットのインデックスを探索する\"\"\"\n index = self.hash_func(key)\n first_tombstone = -1\n # 線形探索法で探索し、空のバケットに到達したら終了する\n while self.buckets[index] is not None:\n # key が見つかった場合、対応するバケットのインデックスを返す\n if self.buckets[index].key == key:\n # それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if first_tombstone != -1:\n self.buckets[first_tombstone] = self.buckets[index]\n self.buckets[index] = self.TOMBSTONE\n return first_tombstone # 移動後のバケットのインデックスを返す\n return index # バケットのインデックスを返す\n # 最初に見つかった削除マーカーを記録する\n if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE:\n first_tombstone = index\n # バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % self.capacity\n # key が存在しない場合、追加位置のインデックスを返す\n return index if first_tombstone == -1 else first_tombstone\n\n def get(self, key: int) -&gt; str:\n \"\"\"取得操作\"\"\"\n # key に対応するバケットのインデックスを探索する\n index = self.find_bucket(key)\n # キーと値のペアが見つかった場合、対応する val を返す\n if self.buckets[index] not in [None, self.TOMBSTONE]:\n return self.buckets[index].val\n # キーと値のペアが存在しない場合、None を返す\n return None\n\n def put(self, key: int, val: str):\n \"\"\"追加操作\"\"\"\n # 負荷率がしきい値を超えた場合、容量を拡張する\n if self.load_factor() &gt; self.load_thres:\n self.extend()\n # key に対応するバケットのインデックスを探索する\n index = self.find_bucket(key)\n # キーと値のペアが見つかった場合、val を上書きして戻る\n if self.buckets[index] not in [None, self.TOMBSTONE]:\n self.buckets[index].val = val\n return\n # キーと値のペアが存在しない場合、そのペアを追加する\n self.buckets[index] = Pair(key, val)\n self.size += 1\n\n def remove(self, key: int):\n \"\"\"削除操作\"\"\"\n # key に対応するバケットのインデックスを探索する\n index = self.find_bucket(key)\n # キーと値のペアが見つかった場合、削除マーカーで上書きする\n if self.buckets[index] not in [None, self.TOMBSTONE]:\n self.buckets[index] = self.TOMBSTONE\n self.size -= 1\n\n def extend(self):\n \"\"\"ハッシュテーブルの容量を拡張する\"\"\"\n # 元のハッシュテーブルを一時保存する\n buckets_tmp = self.buckets\n # 拡張後の新しいハッシュテーブルを初期化する\n self.capacity *= self.extend_ratio\n self.buckets = [None] * self.capacity\n self.size = 0\n # キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for pair in buckets_tmp:\n if pair not in [None, self.TOMBSTONE]:\n self.put(pair.key, pair.val)\n\n def print(self):\n \"\"\"ハッシュテーブルを表示する\"\"\"\n for pair in self.buckets:\n if pair is None:\n print(\"None\")\n elif pair is self.TOMBSTONE:\n print(\"TOMBSTONE\")\n else:\n print(pair.key, \"-&gt;\", pair.val)\n</code></pre> hash_map_open_addressing.cpp<pre><code>/* オープンアドレス法のハッシュテーブル */\nclass HashMapOpenAddressing {\n private:\n int size; // キーと値のペアの数\n int capacity = 4; // ハッシュテーブルの容量\n const double loadThres = 2.0 / 3.0; // 容量の拡張を開始する負荷率のしきい値\n const int extendRatio = 2; // 容量の拡張倍率\n vector&lt;Pair *&gt; buckets; // バケット配列\n Pair *TOMBSTONE = new Pair(-1, \"-1\"); // 削除マーカー\n\n public:\n /* コンストラクタ */\n HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) {\n }\n\n /* デストラクタ */\n ~HashMapOpenAddressing() {\n for (Pair *pair : buckets) {\n if (pair != nullptr &amp;&amp; pair != TOMBSTONE) {\n delete pair;\n }\n }\n delete TOMBSTONE;\n }\n\n /* ハッシュ関数 */\n int hashFunc(int key) {\n return key % capacity;\n }\n\n /* 負荷率 */\n double loadFactor() {\n return (double)size / capacity;\n }\n\n /* key に対応するバケットのインデックスを探索する */\n int findBucket(int key) {\n int index = hashFunc(key);\n int firstTombstone = -1;\n // 線形探索法で探索し、空のバケットに到達したら終了する\n while (buckets[index] != nullptr) {\n // key が見つかった場合、対応するバケットのインデックスを返す\n if (buckets[index]-&gt;key == key) {\n // それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if (firstTombstone != -1) {\n buckets[firstTombstone] = buckets[index];\n buckets[index] = TOMBSTONE;\n return firstTombstone; // 移動後のバケットのインデックスを返す\n }\n return index; // バケットのインデックスを返す\n }\n // 最初に見つかった削除マーカーを記録する\n if (firstTombstone == -1 &amp;&amp; buckets[index] == TOMBSTONE) {\n firstTombstone = index;\n }\n // バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % capacity;\n }\n // key が存在しない場合、追加位置のインデックスを返す\n return firstTombstone == -1 ? index : firstTombstone;\n }\n\n /* 取得操作 */\n string get(int key) {\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(key);\n // キーと値のペアが見つかった場合、対応する val を返す\n if (buckets[index] != nullptr &amp;&amp; buckets[index] != TOMBSTONE) {\n return buckets[index]-&gt;val;\n }\n // キーと値のペアが存在しない場合、空文字列を返す\n return \"\";\n }\n\n /* 追加操作 */\n void put(int key, string val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (loadFactor() &gt; loadThres) {\n extend();\n }\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(key);\n // キーと値のペアが見つかった場合、val を上書きして戻る\n if (buckets[index] != nullptr &amp;&amp; buckets[index] != TOMBSTONE) {\n buckets[index]-&gt;val = val;\n return;\n }\n // キーと値のペアが存在しない場合、そのペアを追加する\n buckets[index] = new Pair(key, val);\n size++;\n }\n\n /* 削除操作 */\n void remove(int key) {\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(key);\n // キーと値のペアが見つかった場合、削除マーカーで上書きする\n if (buckets[index] != nullptr &amp;&amp; buckets[index] != TOMBSTONE) {\n delete buckets[index];\n buckets[index] = TOMBSTONE;\n size--;\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n void extend() {\n // 元のハッシュテーブルを一時保存する\n vector&lt;Pair *&gt; bucketsTmp = buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n capacity *= extendRatio;\n buckets = vector&lt;Pair *&gt;(capacity, nullptr);\n size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (Pair *pair : bucketsTmp) {\n if (pair != nullptr &amp;&amp; pair != TOMBSTONE) {\n put(pair-&gt;key, pair-&gt;val);\n delete pair;\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n void print() {\n for (Pair *pair : buckets) {\n if (pair == nullptr) {\n cout &lt;&lt; \"nullptr\" &lt;&lt; endl;\n } else if (pair == TOMBSTONE) {\n cout &lt;&lt; \"TOMBSTONE\" &lt;&lt; endl;\n } else {\n cout &lt;&lt; pair-&gt;key &lt;&lt; \" -&gt; \" &lt;&lt; pair-&gt;val &lt;&lt; endl;\n }\n }\n }\n};\n</code></pre> hash_map_open_addressing.java<pre><code>/* オープンアドレス法のハッシュテーブル */\nclass HashMapOpenAddressing {\n private int size; // キーと値のペアの数\n private int capacity = 4; // ハッシュテーブルの容量\n private final double loadThres = 2.0 / 3.0; // 容量の拡張を開始する負荷率のしきい値\n private final int extendRatio = 2; // 容量の拡張倍率\n private Pair[] buckets; // バケット配列\n private final Pair TOMBSTONE = new Pair(-1, \"-1\"); // 削除マーカー\n\n /* コンストラクタ */\n public HashMapOpenAddressing() {\n size = 0;\n buckets = new Pair[capacity];\n }\n\n /* ハッシュ関数 */\n private int hashFunc(int key) {\n return key % capacity;\n }\n\n /* 負荷率 */\n private double loadFactor() {\n return (double) size / capacity;\n }\n\n /* key に対応するバケットのインデックスを探索する */\n private int findBucket(int key) {\n int index = hashFunc(key);\n int firstTombstone = -1;\n // 線形探索法で探索し、空のバケットに到達したら終了する\n while (buckets[index] != null) {\n // key が見つかった場合、対応するバケットのインデックスを返す\n if (buckets[index].key == key) {\n // それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if (firstTombstone != -1) {\n buckets[firstTombstone] = buckets[index];\n buckets[index] = TOMBSTONE;\n return firstTombstone; // 移動後のバケットのインデックスを返す\n }\n return index; // バケットのインデックスを返す\n }\n // 最初に見つかった削除マーカーを記録する\n if (firstTombstone == -1 &amp;&amp; buckets[index] == TOMBSTONE) {\n firstTombstone = index;\n }\n // バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % capacity;\n }\n // key が存在しない場合、追加位置のインデックスを返す\n return firstTombstone == -1 ? index : firstTombstone;\n }\n\n /* 取得操作 */\n public String get(int key) {\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(key);\n // キーと値のペアが見つかった場合、対応する val を返す\n if (buckets[index] != null &amp;&amp; buckets[index] != TOMBSTONE) {\n return buckets[index].val;\n }\n // キーと値のペアが存在しない場合、null を返す\n return null;\n }\n\n /* 追加操作 */\n public void put(int key, String val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (loadFactor() &gt; loadThres) {\n extend();\n }\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(key);\n // キーと値のペアが見つかった場合、val を上書きして戻る\n if (buckets[index] != null &amp;&amp; buckets[index] != TOMBSTONE) {\n buckets[index].val = val;\n return;\n }\n // キーと値のペアが存在しない場合、そのペアを追加する\n buckets[index] = new Pair(key, val);\n size++;\n }\n\n /* 削除操作 */\n public void remove(int key) {\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(key);\n // キーと値のペアが見つかった場合、削除マーカーで上書きする\n if (buckets[index] != null &amp;&amp; buckets[index] != TOMBSTONE) {\n buckets[index] = TOMBSTONE;\n size--;\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n private void extend() {\n // 元のハッシュテーブルを一時保存する\n Pair[] bucketsTmp = buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n capacity *= extendRatio;\n buckets = new Pair[capacity];\n size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (Pair pair : bucketsTmp) {\n if (pair != null &amp;&amp; pair != TOMBSTONE) {\n put(pair.key, pair.val);\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n public void print() {\n for (Pair pair : buckets) {\n if (pair == null) {\n System.out.println(\"null\");\n } else if (pair == TOMBSTONE) {\n System.out.println(\"TOMBSTONE\");\n } else {\n System.out.println(pair.key + \" -&gt; \" + pair.val);\n }\n }\n }\n}\n</code></pre> hash_map_open_addressing.cs<pre><code>/* オープンアドレス法のハッシュテーブル */\nclass HashMapOpenAddressing {\n int size; // キーと値のペアの数\n int capacity = 4; // ハッシュテーブルの容量\n double loadThres = 2.0 / 3.0; // 容量の拡張を開始する負荷率のしきい値\n int extendRatio = 2; // 容量の拡張倍率\n Pair[] buckets; // バケット配列\n Pair TOMBSTONE = new(-1, \"-1\"); // 削除マーカー\n\n /* コンストラクタ */\n public HashMapOpenAddressing() {\n size = 0;\n buckets = new Pair[capacity];\n }\n\n /* ハッシュ関数 */\n int HashFunc(int key) {\n return key % capacity;\n }\n\n /* 負荷率 */\n double LoadFactor() {\n return (double)size / capacity;\n }\n\n /* key に対応するバケットのインデックスを探索する */\n int FindBucket(int key) {\n int index = HashFunc(key);\n int firstTombstone = -1;\n // 線形探索法で探索し、空のバケットに到達したら終了する\n while (buckets[index] != null) {\n // key が見つかった場合、対応するバケットのインデックスを返す\n if (buckets[index].key == key) {\n // それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if (firstTombstone != -1) {\n buckets[firstTombstone] = buckets[index];\n buckets[index] = TOMBSTONE;\n return firstTombstone; // 移動後のバケットのインデックスを返す\n }\n return index; // バケットのインデックスを返す\n }\n // 最初に見つかった削除マーカーを記録する\n if (firstTombstone == -1 &amp;&amp; buckets[index] == TOMBSTONE) {\n firstTombstone = index;\n }\n // バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % capacity;\n }\n // key が存在しない場合、追加位置のインデックスを返す\n return firstTombstone == -1 ? index : firstTombstone;\n }\n\n /* 取得操作 */\n public string? Get(int key) {\n // key に対応するバケットのインデックスを探索する\n int index = FindBucket(key);\n // キーと値のペアが見つかった場合、対応する val を返す\n if (buckets[index] != null &amp;&amp; buckets[index] != TOMBSTONE) {\n return buckets[index].val;\n }\n // キーと値のペアが存在しない場合、null を返す\n return null;\n }\n\n /* 追加操作 */\n public void Put(int key, string val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (LoadFactor() &gt; loadThres) {\n Extend();\n }\n // key に対応するバケットのインデックスを探索する\n int index = FindBucket(key);\n // キーと値のペアが見つかった場合、val を上書きして戻る\n if (buckets[index] != null &amp;&amp; buckets[index] != TOMBSTONE) {\n buckets[index].val = val;\n return;\n }\n // キーと値のペアが存在しない場合、そのペアを追加する\n buckets[index] = new Pair(key, val);\n size++;\n }\n\n /* 削除操作 */\n public void Remove(int key) {\n // key に対応するバケットのインデックスを探索する\n int index = FindBucket(key);\n // キーと値のペアが見つかった場合、削除マーカーで上書きする\n if (buckets[index] != null &amp;&amp; buckets[index] != TOMBSTONE) {\n buckets[index] = TOMBSTONE;\n size--;\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n void Extend() {\n // 元のハッシュテーブルを一時保存する\n Pair[] bucketsTmp = buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n capacity *= extendRatio;\n buckets = new Pair[capacity];\n size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n foreach (Pair pair in bucketsTmp) {\n if (pair != null &amp;&amp; pair != TOMBSTONE) {\n Put(pair.key, pair.val);\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n public void Print() {\n foreach (Pair pair in buckets) {\n if (pair == null) {\n Console.WriteLine(\"null\");\n } else if (pair == TOMBSTONE) {\n Console.WriteLine(\"TOMBSTONE\");\n } else {\n Console.WriteLine(pair.key + \" -&gt; \" + pair.val);\n }\n }\n }\n}\n</code></pre> hash_map_open_addressing.go<pre><code>/* オープンアドレス法のハッシュテーブル */\ntype hashMapOpenAddressing struct {\n size int // キーと値のペアの数\n capacity int // ハッシュテーブルの容量\n loadThres float64 // 容量の拡張を開始する負荷率のしきい値\n extendRatio int // 容量の拡張倍率\n buckets []*pair // バケット配列\n TOMBSTONE *pair // 削除マーカー\n}\n\n/* コンストラクタ */\nfunc newHashMapOpenAddressing() *hashMapOpenAddressing {\n return &amp;hashMapOpenAddressing{\n size: 0,\n capacity: 4,\n loadThres: 2.0 / 3.0,\n extendRatio: 2,\n buckets: make([]*pair, 4),\n TOMBSTONE: &amp;pair{-1, \"-1\"},\n }\n}\n\n/* ハッシュ関数 */\nfunc (h *hashMapOpenAddressing) hashFunc(key int) int {\n return key % h.capacity // キーからハッシュ値を計算する\n}\n\n/* 負荷率 */\nfunc (h *hashMapOpenAddressing) loadFactor() float64 {\n return float64(h.size) / float64(h.capacity) // 現在の負荷率を計算する\n}\n\n/* key に対応するバケットのインデックスを探索する */\nfunc (h *hashMapOpenAddressing) findBucket(key int) int {\n index := h.hashFunc(key) // 初期インデックスを取得する\n firstTombstone := -1 // 最初に見つかった TOMBSTONE の位置を記録する\n for h.buckets[index] != nil {\n if h.buckets[index].key == key {\n if firstTombstone != -1 {\n // それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n h.buckets[firstTombstone] = h.buckets[index]\n h.buckets[index] = h.TOMBSTONE\n return firstTombstone // 移動後のバケットのインデックスを返す\n }\n return index // 見つかったインデックスを返す\n }\n if firstTombstone == -1 &amp;&amp; h.buckets[index] == h.TOMBSTONE {\n firstTombstone = index // 最初に見つかった削除マーカーの位置を記録する\n }\n index = (index + 1) % h.capacity // 線形探索法で探索し、末尾を越えたら先頭に戻る\n }\n // key が存在しない場合、追加位置のインデックスを返す\n if firstTombstone != -1 {\n return firstTombstone\n }\n return index\n}\n\n/* 取得操作 */\nfunc (h *hashMapOpenAddressing) get(key int) string {\n index := h.findBucket(key) // key に対応するバケットのインデックスを探索する\n if h.buckets[index] != nil &amp;&amp; h.buckets[index] != h.TOMBSTONE {\n return h.buckets[index].val // キーと値のペアが見つかった場合、対応する val を返す\n }\n return \"\" // キーと値のペアが存在しない場合、\"\" を返す\n}\n\n/* 追加操作 */\nfunc (h *hashMapOpenAddressing) put(key int, val string) {\n if h.loadFactor() &gt; h.loadThres {\n h.extend() // 負荷率がしきい値を超えた場合、容量を拡張する\n }\n index := h.findBucket(key) // key に対応するバケットのインデックスを探索する\n if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE {\n h.buckets[index] = &amp;pair{key, val} // キーと値のペアが存在しない場合、そのペアを追加する\n h.size++\n } else {\n h.buckets[index].val = val // キーと値のペアが見つかった場合、val を上書きする\n }\n}\n\n/* 削除操作 */\nfunc (h *hashMapOpenAddressing) remove(key int) {\n index := h.findBucket(key) // key に対応するバケットのインデックスを探索する\n if h.buckets[index] != nil &amp;&amp; h.buckets[index] != h.TOMBSTONE {\n h.buckets[index] = h.TOMBSTONE // キーと値のペアが見つかった場合、削除マーカーで上書きする\n h.size--\n }\n}\n\n/* ハッシュテーブルの容量を拡張する */\nfunc (h *hashMapOpenAddressing) extend() {\n oldBuckets := h.buckets // 元のハッシュテーブルを一時保存する\n h.capacity *= h.extendRatio // 容量を更新する\n h.buckets = make([]*pair, h.capacity) // 拡張後の新しいハッシュテーブルを初期化する\n h.size = 0 // サイズをリセットする\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for _, pair := range oldBuckets {\n if pair != nil &amp;&amp; pair != h.TOMBSTONE {\n h.put(pair.key, pair.val)\n }\n }\n}\n\n/* ハッシュテーブルを表示する */\nfunc (h *hashMapOpenAddressing) print() {\n for _, pair := range h.buckets {\n if pair == nil {\n fmt.Println(\"nil\")\n } else if pair == h.TOMBSTONE {\n fmt.Println(\"TOMBSTONE\")\n } else {\n fmt.Printf(\"%d -&gt; %s\\n\", pair.key, pair.val)\n }\n }\n}\n</code></pre> hash_map_open_addressing.swift<pre><code>/* オープンアドレス法のハッシュテーブル */\nclass HashMapOpenAddressing {\n var size: Int // キーと値のペアの数\n var capacity: Int // ハッシュテーブルの容量\n var loadThres: Double // 容量の拡張を開始する負荷率のしきい値\n var extendRatio: Int // 容量の拡張倍率\n var buckets: [Pair?] // バケット配列\n var TOMBSTONE: Pair // 削除マーカー\n\n /* コンストラクタ */\n init() {\n size = 0\n capacity = 4\n loadThres = 2.0 / 3.0\n extendRatio = 2\n buckets = Array(repeating: nil, count: capacity)\n TOMBSTONE = Pair(key: -1, val: \"-1\")\n }\n\n /* ハッシュ関数 */\n func hashFunc(key: Int) -&gt; Int {\n key % capacity\n }\n\n /* 負荷率 */\n func loadFactor() -&gt; Double {\n Double(size) / Double(capacity)\n }\n\n /* key に対応するバケットのインデックスを探索する */\n func findBucket(key: Int) -&gt; Int {\n var index = hashFunc(key: key)\n var firstTombstone = -1\n // 線形探索法で探索し、空のバケットに到達したら終了する\n while buckets[index] != nil {\n // key が見つかった場合、対応するバケットのインデックスを返す\n if buckets[index]!.key == key {\n // それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if firstTombstone != -1 {\n buckets[firstTombstone] = buckets[index]\n buckets[index] = TOMBSTONE\n return firstTombstone // 移動後のバケットのインデックスを返す\n }\n return index // バケットのインデックスを返す\n }\n // 最初に見つかった削除マーカーを記録する\n if firstTombstone == -1 &amp;&amp; buckets[index] == TOMBSTONE {\n firstTombstone = index\n }\n // バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % capacity\n }\n // key が存在しない場合、追加位置のインデックスを返す\n return firstTombstone == -1 ? index : firstTombstone\n }\n\n /* 取得操作 */\n func get(key: Int) -&gt; String? {\n // key に対応するバケットのインデックスを探索する\n let index = findBucket(key: key)\n // キーと値のペアが見つかった場合、対応する val を返す\n if buckets[index] != nil, buckets[index] != TOMBSTONE {\n return buckets[index]!.val\n }\n // キーと値のペアが存在しない場合、null を返す\n return nil\n }\n\n /* 追加操作 */\n func put(key: Int, val: String) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if loadFactor() &gt; loadThres {\n extend()\n }\n // key に対応するバケットのインデックスを探索する\n let index = findBucket(key: key)\n // キーと値のペアが見つかった場合、val を上書きして戻る\n if buckets[index] != nil, buckets[index] != TOMBSTONE {\n buckets[index]!.val = val\n return\n }\n // キーと値のペアが存在しない場合、そのペアを追加する\n buckets[index] = Pair(key: key, val: val)\n size += 1\n }\n\n /* 削除操作 */\n func remove(key: Int) {\n // key に対応するバケットのインデックスを探索する\n let index = findBucket(key: key)\n // キーと値のペアが見つかった場合、削除マーカーで上書きする\n if buckets[index] != nil, buckets[index] != TOMBSTONE {\n buckets[index] = TOMBSTONE\n size -= 1\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n func extend() {\n // 元のハッシュテーブルを一時保存する\n let bucketsTmp = buckets\n // 拡張後の新しいハッシュテーブルを初期化する\n capacity *= extendRatio\n buckets = Array(repeating: nil, count: capacity)\n size = 0\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for pair in bucketsTmp {\n if let pair, pair != TOMBSTONE {\n put(key: pair.key, val: pair.val)\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n func print() {\n for pair in buckets {\n if pair == nil {\n Swift.print(\"null\")\n } else if pair == TOMBSTONE {\n Swift.print(\"TOMBSTONE\")\n } else {\n Swift.print(\"\\(pair!.key) -&gt; \\(pair!.val)\")\n }\n }\n }\n}\n</code></pre> hash_map_open_addressing.js<pre><code>/* オープンアドレス法のハッシュテーブル */\nclass HashMapOpenAddressing {\n #size; // キーと値のペアの数\n #capacity; // ハッシュテーブルの容量\n #loadThres; // 容量の拡張を開始する負荷率のしきい値\n #extendRatio; // 容量の拡張倍率\n #buckets; // バケット配列\n #TOMBSTONE; // 削除マーカー\n\n /* コンストラクタ */\n constructor() {\n this.#size = 0; // キーと値のペアの数\n this.#capacity = 4; // ハッシュテーブルの容量\n this.#loadThres = 2.0 / 3.0; // 容量の拡張を開始する負荷率のしきい値\n this.#extendRatio = 2; // 容量の拡張倍率\n this.#buckets = Array(this.#capacity).fill(null); // バケット配列\n this.#TOMBSTONE = new Pair(-1, '-1'); // 削除マーカー\n }\n\n /* ハッシュ関数 */\n #hashFunc(key) {\n return key % this.#capacity;\n }\n\n /* 負荷率 */\n #loadFactor() {\n return this.#size / this.#capacity;\n }\n\n /* key に対応するバケットのインデックスを探索する */\n #findBucket(key) {\n let index = this.#hashFunc(key);\n let firstTombstone = -1;\n // 線形探索法で探索し、空のバケットに到達したら終了する\n while (this.#buckets[index] !== null) {\n // key が見つかった場合、対応するバケットのインデックスを返す\n if (this.#buckets[index].key === key) {\n // それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if (firstTombstone !== -1) {\n this.#buckets[firstTombstone] = this.#buckets[index];\n this.#buckets[index] = this.#TOMBSTONE;\n return firstTombstone; // 移動後のバケットのインデックスを返す\n }\n return index; // バケットのインデックスを返す\n }\n // 最初に見つかった削除マーカーを記録する\n if (\n firstTombstone === -1 &amp;&amp;\n this.#buckets[index] === this.#TOMBSTONE\n ) {\n firstTombstone = index;\n }\n // バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % this.#capacity;\n }\n // key が存在しない場合、追加位置のインデックスを返す\n return firstTombstone === -1 ? index : firstTombstone;\n }\n\n /* 取得操作 */\n get(key) {\n // key に対応するバケットのインデックスを探索する\n const index = this.#findBucket(key);\n // キーと値のペアが見つかった場合、対応する val を返す\n if (\n this.#buckets[index] !== null &amp;&amp;\n this.#buckets[index] !== this.#TOMBSTONE\n ) {\n return this.#buckets[index].val;\n }\n // キーと値のペアが存在しない場合、null を返す\n return null;\n }\n\n /* 追加操作 */\n put(key, val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (this.#loadFactor() &gt; this.#loadThres) {\n this.#extend();\n }\n // key に対応するバケットのインデックスを探索する\n const index = this.#findBucket(key);\n // キーと値のペアが見つかった場合、val を上書きして戻る\n if (\n this.#buckets[index] !== null &amp;&amp;\n this.#buckets[index] !== this.#TOMBSTONE\n ) {\n this.#buckets[index].val = val;\n return;\n }\n // キーと値のペアが存在しない場合、そのペアを追加する\n this.#buckets[index] = new Pair(key, val);\n this.#size++;\n }\n\n /* 削除操作 */\n remove(key) {\n // key に対応するバケットのインデックスを探索する\n const index = this.#findBucket(key);\n // キーと値のペアが見つかった場合、削除マーカーで上書きする\n if (\n this.#buckets[index] !== null &amp;&amp;\n this.#buckets[index] !== this.#TOMBSTONE\n ) {\n this.#buckets[index] = this.#TOMBSTONE;\n this.#size--;\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n #extend() {\n // 元のハッシュテーブルを一時保存する\n const bucketsTmp = this.#buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n this.#capacity *= this.#extendRatio;\n this.#buckets = Array(this.#capacity).fill(null);\n this.#size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (const pair of bucketsTmp) {\n if (pair !== null &amp;&amp; pair !== this.#TOMBSTONE) {\n this.put(pair.key, pair.val);\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n print() {\n for (const pair of this.#buckets) {\n if (pair === null) {\n console.log('null');\n } else if (pair === this.#TOMBSTONE) {\n console.log('TOMBSTONE');\n } else {\n console.log(pair.key + ' -&gt; ' + pair.val);\n }\n }\n }\n}\n</code></pre> hash_map_open_addressing.ts<pre><code>/* オープンアドレス法のハッシュテーブル */\nclass HashMapOpenAddressing {\n private size: number; // キーと値のペアの数\n private capacity: number; // ハッシュテーブルの容量\n private loadThres: number; // 容量の拡張を開始する負荷率のしきい値\n private extendRatio: number; // 容量の拡張倍率\n private buckets: Array&lt;Pair | null&gt;; // バケット配列\n private TOMBSTONE: Pair; // 削除マーカー\n\n /* コンストラクタ */\n constructor() {\n this.size = 0; // キーと値のペアの数\n this.capacity = 4; // ハッシュテーブルの容量\n this.loadThres = 2.0 / 3.0; // 容量の拡張を開始する負荷率のしきい値\n this.extendRatio = 2; // 容量の拡張倍率\n this.buckets = Array(this.capacity).fill(null); // バケット配列\n this.TOMBSTONE = new Pair(-1, '-1'); // 削除マーカー\n }\n\n /* ハッシュ関数 */\n private hashFunc(key: number): number {\n return key % this.capacity;\n }\n\n /* 負荷率 */\n private loadFactor(): number {\n return this.size / this.capacity;\n }\n\n /* key に対応するバケットのインデックスを探索する */\n private findBucket(key: number): number {\n let index = this.hashFunc(key);\n let firstTombstone = -1;\n // 線形探索法で探索し、空のバケットに到達したら終了する\n while (this.buckets[index] !== null) {\n // key が見つかった場合、対応するバケットのインデックスを返す\n if (this.buckets[index]!.key === key) {\n // それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if (firstTombstone !== -1) {\n this.buckets[firstTombstone] = this.buckets[index];\n this.buckets[index] = this.TOMBSTONE;\n return firstTombstone; // 移動後のバケットのインデックスを返す\n }\n return index; // バケットのインデックスを返す\n }\n // 最初に見つかった削除マーカーを記録する\n if (\n firstTombstone === -1 &amp;&amp;\n this.buckets[index] === this.TOMBSTONE\n ) {\n firstTombstone = index;\n }\n // バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % this.capacity;\n }\n // key が存在しない場合、追加位置のインデックスを返す\n return firstTombstone === -1 ? index : firstTombstone;\n }\n\n /* 取得操作 */\n get(key: number): string | null {\n // key に対応するバケットのインデックスを探索する\n const index = this.findBucket(key);\n // キーと値のペアが見つかった場合、対応する val を返す\n if (\n this.buckets[index] !== null &amp;&amp;\n this.buckets[index] !== this.TOMBSTONE\n ) {\n return this.buckets[index]!.val;\n }\n // キーと値のペアが存在しない場合、null を返す\n return null;\n }\n\n /* 追加操作 */\n put(key: number, val: string): void {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (this.loadFactor() &gt; this.loadThres) {\n this.extend();\n }\n // key に対応するバケットのインデックスを探索する\n const index = this.findBucket(key);\n // キーと値のペアが見つかった場合、val を上書きして戻る\n if (\n this.buckets[index] !== null &amp;&amp;\n this.buckets[index] !== this.TOMBSTONE\n ) {\n this.buckets[index]!.val = val;\n return;\n }\n // キーと値のペアが存在しない場合、そのペアを追加する\n this.buckets[index] = new Pair(key, val);\n this.size++;\n }\n\n /* 削除操作 */\n remove(key: number): void {\n // key に対応するバケットのインデックスを探索する\n const index = this.findBucket(key);\n // キーと値のペアが見つかった場合、削除マーカーで上書きする\n if (\n this.buckets[index] !== null &amp;&amp;\n this.buckets[index] !== this.TOMBSTONE\n ) {\n this.buckets[index] = this.TOMBSTONE;\n this.size--;\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n private extend(): void {\n // 元のハッシュテーブルを一時保存する\n const bucketsTmp = this.buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n this.capacity *= this.extendRatio;\n this.buckets = Array(this.capacity).fill(null);\n this.size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (const pair of bucketsTmp) {\n if (pair !== null &amp;&amp; pair !== this.TOMBSTONE) {\n this.put(pair.key, pair.val);\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n print(): void {\n for (const pair of this.buckets) {\n if (pair === null) {\n console.log('null');\n } else if (pair === this.TOMBSTONE) {\n console.log('TOMBSTONE');\n } else {\n console.log(pair.key + ' -&gt; ' + pair.val);\n }\n }\n }\n}\n</code></pre> hash_map_open_addressing.dart<pre><code>/* オープンアドレス法のハッシュテーブル */\nclass HashMapOpenAddressing {\n late int _size; // キーと値のペアの数\n int _capacity = 4; // ハッシュテーブルの容量\n double _loadThres = 2.0 / 3.0; // 容量の拡張を開始する負荷率のしきい値\n int _extendRatio = 2; // 容量の拡張倍率\n late List&lt;Pair?&gt; _buckets; // バケット配列\n Pair _TOMBSTONE = Pair(-1, \"-1\"); // 削除マーカー\n\n /* コンストラクタ */\n HashMapOpenAddressing() {\n _size = 0;\n _buckets = List.generate(_capacity, (index) =&gt; null);\n }\n\n /* ハッシュ関数 */\n int hashFunc(int key) {\n return key % _capacity;\n }\n\n /* 負荷率 */\n double loadFactor() {\n return _size / _capacity;\n }\n\n /* key に対応するバケットのインデックスを探索する */\n int findBucket(int key) {\n int index = hashFunc(key);\n int firstTombstone = -1;\n // 線形探索法で探索し、空のバケットに到達したら終了する\n while (_buckets[index] != null) {\n // key が見つかった場合、対応するバケットのインデックスを返す\n if (_buckets[index]!.key == key) {\n // それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if (firstTombstone != -1) {\n _buckets[firstTombstone] = _buckets[index];\n _buckets[index] = _TOMBSTONE;\n return firstTombstone; // 移動後のバケットのインデックスを返す\n }\n return index; // バケットのインデックスを返す\n }\n // 最初に見つかった削除マーカーを記録する\n if (firstTombstone == -1 &amp;&amp; _buckets[index] == _TOMBSTONE) {\n firstTombstone = index;\n }\n // バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % _capacity;\n }\n // key が存在しない場合、追加位置のインデックスを返す\n return firstTombstone == -1 ? index : firstTombstone;\n }\n\n /* 取得操作 */\n String? get(int key) {\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(key);\n // キーと値のペアが見つかった場合、対応する val を返す\n if (_buckets[index] != null &amp;&amp; _buckets[index] != _TOMBSTONE) {\n return _buckets[index]!.val;\n }\n // キーと値のペアが存在しない場合、null を返す\n return null;\n }\n\n /* 追加操作 */\n void put(int key, String val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (loadFactor() &gt; _loadThres) {\n extend();\n }\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(key);\n // キーと値のペアが見つかった場合、val を上書きして戻る\n if (_buckets[index] != null &amp;&amp; _buckets[index] != _TOMBSTONE) {\n _buckets[index]!.val = val;\n return;\n }\n // キーと値のペアが存在しない場合、そのペアを追加する\n _buckets[index] = new Pair(key, val);\n _size++;\n }\n\n /* 削除操作 */\n void remove(int key) {\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(key);\n // キーと値のペアが見つかった場合、削除マーカーで上書きする\n if (_buckets[index] != null &amp;&amp; _buckets[index] != _TOMBSTONE) {\n _buckets[index] = _TOMBSTONE;\n _size--;\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n void extend() {\n // 元のハッシュテーブルを一時保存する\n List&lt;Pair?&gt; bucketsTmp = _buckets;\n // 拡張後の新しいハッシュテーブルを初期化する\n _capacity *= _extendRatio;\n _buckets = List.generate(_capacity, (index) =&gt; null);\n _size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (Pair? pair in bucketsTmp) {\n if (pair != null &amp;&amp; pair != _TOMBSTONE) {\n put(pair.key, pair.val);\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n void printHashMap() {\n for (Pair? pair in _buckets) {\n if (pair == null) {\n print(\"null\");\n } else if (pair == _TOMBSTONE) {\n print(\"TOMBSTONE\");\n } else {\n print(\"${pair.key} -&gt; ${pair.val}\");\n }\n }\n }\n}\n</code></pre> hash_map_open_addressing.rs<pre><code>/* オープンアドレス法のハッシュテーブル */\nstruct HashMapOpenAddressing {\n size: usize, // キーと値のペアの数\n capacity: usize, // ハッシュテーブルの容量\n load_thres: f64, // 容量の拡張を開始する負荷率のしきい値\n extend_ratio: usize, // 容量の拡張倍率\n buckets: Vec&lt;Option&lt;Pair&gt;&gt;, // バケット配列\n TOMBSTONE: Option&lt;Pair&gt;, // 削除マーカー\n}\n\nimpl HashMapOpenAddressing {\n /* コンストラクタ */\n fn new() -&gt; Self {\n Self {\n size: 0,\n capacity: 4,\n load_thres: 2.0 / 3.0,\n extend_ratio: 2,\n buckets: vec![None; 4],\n TOMBSTONE: Some(Pair {\n key: -1,\n val: \"-1\".to_string(),\n }),\n }\n }\n\n /* ハッシュ関数 */\n fn hash_func(&amp;self, key: i32) -&gt; usize {\n (key % self.capacity as i32) as usize\n }\n\n /* 負荷率 */\n fn load_factor(&amp;self) -&gt; f64 {\n self.size as f64 / self.capacity as f64\n }\n\n /* key に対応するバケットのインデックスを探索する */\n fn find_bucket(&amp;mut self, key: i32) -&gt; usize {\n let mut index = self.hash_func(key);\n let mut first_tombstone = -1;\n // 線形探索法で探索し、空のバケットに到達したら終了する\n while self.buckets[index].is_some() {\n // key が見つかった場合、対応するバケットのインデックスを返す\n if self.buckets[index].as_ref().unwrap().key == key {\n // それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if first_tombstone != -1 {\n self.buckets[first_tombstone as usize] = self.buckets[index].take();\n self.buckets[index] = self.TOMBSTONE.clone();\n return first_tombstone as usize; // 移動後のバケットのインデックスを返す\n }\n return index; // バケットのインデックスを返す\n }\n // 最初に見つかった削除マーカーを記録する\n if first_tombstone == -1 &amp;&amp; self.buckets[index] == self.TOMBSTONE {\n first_tombstone = index as i32;\n }\n // バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % self.capacity;\n }\n // key が存在しない場合、追加位置のインデックスを返す\n if first_tombstone == -1 {\n index\n } else {\n first_tombstone as usize\n }\n }\n\n /* 取得操作 */\n fn get(&amp;mut self, key: i32) -&gt; Option&lt;&amp;str&gt; {\n // key に対応するバケットのインデックスを探索する\n let index = self.find_bucket(key);\n // キーと値のペアが見つかった場合、対応する val を返す\n if self.buckets[index].is_some() &amp;&amp; self.buckets[index] != self.TOMBSTONE {\n return self.buckets[index].as_ref().map(|pair| &amp;pair.val as &amp;str);\n }\n // キーと値のペアが存在しない場合、null を返す\n None\n }\n\n /* 追加操作 */\n fn put(&amp;mut self, key: i32, val: String) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if self.load_factor() &gt; self.load_thres {\n self.extend();\n }\n // key に対応するバケットのインデックスを探索する\n let index = self.find_bucket(key);\n // キーと値のペアが見つかった場合、val を上書きして戻る\n if self.buckets[index].is_some() &amp;&amp; self.buckets[index] != self.TOMBSTONE {\n self.buckets[index].as_mut().unwrap().val = val;\n return;\n }\n // キーと値のペアが存在しない場合、そのペアを追加する\n self.buckets[index] = Some(Pair { key, val });\n self.size += 1;\n }\n\n /* 削除操作 */\n fn remove(&amp;mut self, key: i32) {\n // key に対応するバケットのインデックスを探索する\n let index = self.find_bucket(key);\n // キーと値のペアが見つかった場合、削除マーカーで上書きする\n if self.buckets[index].is_some() &amp;&amp; self.buckets[index] != self.TOMBSTONE {\n self.buckets[index] = self.TOMBSTONE.clone();\n self.size -= 1;\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n fn extend(&amp;mut self) {\n // 元のハッシュテーブルを一時保存する\n let buckets_tmp = self.buckets.clone();\n // 拡張後の新しいハッシュテーブルを初期化する\n self.capacity *= self.extend_ratio;\n self.buckets = vec![None; self.capacity];\n self.size = 0;\n\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for pair in buckets_tmp {\n if pair.is_none() || pair == self.TOMBSTONE {\n continue;\n }\n let pair = pair.unwrap();\n\n self.put(pair.key, pair.val);\n }\n }\n /* ハッシュテーブルを表示する */\n fn print(&amp;self) {\n for pair in &amp;self.buckets {\n if pair.is_none() {\n println!(\"null\");\n } else if pair == &amp;self.TOMBSTONE {\n println!(\"TOMBSTONE\");\n } else {\n let pair = pair.as_ref().unwrap();\n println!(\"{} -&gt; {}\", pair.key, pair.val);\n }\n }\n }\n}\n</code></pre> hash_map_open_addressing.c<pre><code>/* オープンアドレス法のハッシュテーブル */\ntypedef struct {\n int size; // キーと値のペアの数\n int capacity; // ハッシュテーブルの容量\n double loadThres; // 容量の拡張を開始する負荷率のしきい値\n int extendRatio; // 容量の拡張倍率\n Pair **buckets; // バケット配列\n Pair *TOMBSTONE; // 削除マーカー\n} HashMapOpenAddressing;\n\n/* コンストラクタ */\nHashMapOpenAddressing *newHashMapOpenAddressing() {\n HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing));\n hashMap-&gt;size = 0;\n hashMap-&gt;capacity = 4;\n hashMap-&gt;loadThres = 2.0 / 3.0;\n hashMap-&gt;extendRatio = 2;\n hashMap-&gt;buckets = (Pair **)calloc(hashMap-&gt;capacity, sizeof(Pair *));\n hashMap-&gt;TOMBSTONE = (Pair *)malloc(sizeof(Pair));\n hashMap-&gt;TOMBSTONE-&gt;key = -1;\n hashMap-&gt;TOMBSTONE-&gt;val = \"-1\";\n\n return hashMap;\n}\n\n/* デストラクタ */\nvoid delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) {\n for (int i = 0; i &lt; hashMap-&gt;capacity; i++) {\n Pair *pair = hashMap-&gt;buckets[i];\n if (pair != NULL &amp;&amp; pair != hashMap-&gt;TOMBSTONE) {\n free(pair-&gt;val);\n free(pair);\n }\n }\n free(hashMap-&gt;buckets);\n free(hashMap-&gt;TOMBSTONE);\n free(hashMap);\n}\n\n/* ハッシュ関数 */\nint hashFunc(HashMapOpenAddressing *hashMap, int key) {\n return key % hashMap-&gt;capacity;\n}\n\n/* 負荷率 */\ndouble loadFactor(HashMapOpenAddressing *hashMap) {\n return (double)hashMap-&gt;size / (double)hashMap-&gt;capacity;\n}\n\n/* key に対応するバケットのインデックスを探索する */\nint findBucket(HashMapOpenAddressing *hashMap, int key) {\n int index = hashFunc(hashMap, key);\n int firstTombstone = -1;\n // 線形探索法で探索し、空のバケットに到達したら終了する\n while (hashMap-&gt;buckets[index] != NULL) {\n // key が見つかった場合、対応するバケットのインデックスを返す\n if (hashMap-&gt;buckets[index]-&gt;key == key) {\n // それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if (firstTombstone != -1) {\n hashMap-&gt;buckets[firstTombstone] = hashMap-&gt;buckets[index];\n hashMap-&gt;buckets[index] = hashMap-&gt;TOMBSTONE;\n return firstTombstone; // 移動後のバケットのインデックスを返す\n }\n return index; // バケットのインデックスを返す\n }\n // 最初に見つかった削除マーカーを記録する\n if (firstTombstone == -1 &amp;&amp; hashMap-&gt;buckets[index] == hashMap-&gt;TOMBSTONE) {\n firstTombstone = index;\n }\n // バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % hashMap-&gt;capacity;\n }\n // key が存在しない場合、追加位置のインデックスを返す\n return firstTombstone == -1 ? index : firstTombstone;\n}\n\n/* 取得操作 */\nchar *get(HashMapOpenAddressing *hashMap, int key) {\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(hashMap, key);\n // キーと値のペアが見つかった場合、対応する val を返す\n if (hashMap-&gt;buckets[index] != NULL &amp;&amp; hashMap-&gt;buckets[index] != hashMap-&gt;TOMBSTONE) {\n return hashMap-&gt;buckets[index]-&gt;val;\n }\n // キーと値のペアが存在しない場合、空文字列を返す\n return \"\";\n}\n\n/* 追加操作 */\nvoid put(HashMapOpenAddressing *hashMap, int key, char *val) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (loadFactor(hashMap) &gt; hashMap-&gt;loadThres) {\n extend(hashMap);\n }\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(hashMap, key);\n // キーと値のペアが見つかった場合、val を上書きして戻る\n if (hashMap-&gt;buckets[index] != NULL &amp;&amp; hashMap-&gt;buckets[index] != hashMap-&gt;TOMBSTONE) {\n free(hashMap-&gt;buckets[index]-&gt;val);\n hashMap-&gt;buckets[index]-&gt;val = (char *)malloc(sizeof(strlen(val) + 1));\n strcpy(hashMap-&gt;buckets[index]-&gt;val, val);\n hashMap-&gt;buckets[index]-&gt;val[strlen(val)] = '\\0';\n return;\n }\n // キーと値のペアが存在しない場合、そのペアを追加する\n Pair *pair = (Pair *)malloc(sizeof(Pair));\n pair-&gt;key = key;\n pair-&gt;val = (char *)malloc(sizeof(strlen(val) + 1));\n strcpy(pair-&gt;val, val);\n pair-&gt;val[strlen(val)] = '\\0';\n\n hashMap-&gt;buckets[index] = pair;\n hashMap-&gt;size++;\n}\n\n/* 削除操作 */\nvoid removeItem(HashMapOpenAddressing *hashMap, int key) {\n // key に対応するバケットのインデックスを探索する\n int index = findBucket(hashMap, key);\n // キーと値のペアが見つかった場合、削除マーカーで上書きする\n if (hashMap-&gt;buckets[index] != NULL &amp;&amp; hashMap-&gt;buckets[index] != hashMap-&gt;TOMBSTONE) {\n Pair *pair = hashMap-&gt;buckets[index];\n free(pair-&gt;val);\n free(pair);\n hashMap-&gt;buckets[index] = hashMap-&gt;TOMBSTONE;\n hashMap-&gt;size--;\n }\n}\n\n/* ハッシュテーブルの容量を拡張する */\nvoid extend(HashMapOpenAddressing *hashMap) {\n // 元のハッシュテーブルを一時保存する\n Pair **bucketsTmp = hashMap-&gt;buckets;\n int oldCapacity = hashMap-&gt;capacity;\n // 拡張後の新しいハッシュテーブルを初期化する\n hashMap-&gt;capacity *= hashMap-&gt;extendRatio;\n hashMap-&gt;buckets = (Pair **)calloc(hashMap-&gt;capacity, sizeof(Pair *));\n hashMap-&gt;size = 0;\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (int i = 0; i &lt; oldCapacity; i++) {\n Pair *pair = bucketsTmp[i];\n if (pair != NULL &amp;&amp; pair != hashMap-&gt;TOMBSTONE) {\n put(hashMap, pair-&gt;key, pair-&gt;val);\n free(pair-&gt;val);\n free(pair);\n }\n }\n free(bucketsTmp);\n}\n\n/* ハッシュテーブルを表示する */\nvoid print(HashMapOpenAddressing *hashMap) {\n for (int i = 0; i &lt; hashMap-&gt;capacity; i++) {\n Pair *pair = hashMap-&gt;buckets[i];\n if (pair == NULL) {\n printf(\"NULL\\n\");\n } else if (pair == hashMap-&gt;TOMBSTONE) {\n printf(\"TOMBSTONE\\n\");\n } else {\n printf(\"%d -&gt; %s\\n\", pair-&gt;key, pair-&gt;val);\n }\n }\n}\n</code></pre> hash_map_open_addressing.kt<pre><code>/* オープンアドレス法のハッシュテーブル */\nclass HashMapOpenAddressing {\n private var size: Int // キーと値のペアの数\n private var capacity: Int // ハッシュテーブルの容量\n private val loadThres: Double // 容量の拡張を開始する負荷率のしきい値\n private val extendRatio: Int // 容量の拡張倍率\n private var buckets: Array&lt;Pair?&gt; // バケット配列\n private val TOMBSTONE: Pair // 削除マーカー\n\n /* コンストラクタ */\n init {\n size = 0\n capacity = 4\n loadThres = 2.0 / 3.0\n extendRatio = 2\n buckets = arrayOfNulls(capacity)\n TOMBSTONE = Pair(-1, \"-1\")\n }\n\n /* ハッシュ関数 */\n fun hashFunc(key: Int): Int {\n return key % capacity\n }\n\n /* 負荷率 */\n fun loadFactor(): Double {\n return (size / capacity).toDouble()\n }\n\n /* key に対応するバケットのインデックスを探索する */\n fun findBucket(key: Int): Int {\n var index = hashFunc(key)\n var firstTombstone = -1\n // 線形探索法で探索し、空のバケットに到達したら終了する\n while (buckets[index] != null) {\n // key が見つかった場合、対応するバケットのインデックスを返す\n if (buckets[index]?.key == key) {\n // それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if (firstTombstone != -1) {\n buckets[firstTombstone] = buckets[index]\n buckets[index] = TOMBSTONE\n return firstTombstone // 移動後のバケットのインデックスを返す\n }\n return index // バケットのインデックスを返す\n }\n // 最初に見つかった削除マーカーを記録する\n if (firstTombstone == -1 &amp;&amp; buckets[index] == TOMBSTONE) {\n firstTombstone = index\n }\n // バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % capacity\n }\n // key が存在しない場合、追加位置のインデックスを返す\n return if (firstTombstone == -1) index else firstTombstone\n }\n\n /* 取得操作 */\n fun get(key: Int): String? {\n // key に対応するバケットのインデックスを探索する\n val index = findBucket(key)\n // キーと値のペアが見つかった場合、対応する val を返す\n if (buckets[index] != null &amp;&amp; buckets[index] != TOMBSTONE) {\n return buckets[index]?._val\n }\n // キーと値のペアが存在しない場合、null を返す\n return null\n }\n\n /* 追加操作 */\n fun put(key: Int, _val: String) {\n // 負荷率がしきい値を超えた場合、容量を拡張する\n if (loadFactor() &gt; loadThres) {\n extend()\n }\n // key に対応するバケットのインデックスを探索する\n val index = findBucket(key)\n // キーと値のペアが見つかった場合、val を上書きして戻る\n if (buckets[index] != null &amp;&amp; buckets[index] != TOMBSTONE) {\n buckets[index]!!._val = _val\n return\n }\n // キーと値のペアが存在しない場合、そのペアを追加する\n buckets[index] = Pair(key, _val)\n size++\n }\n\n /* 削除操作 */\n fun remove(key: Int) {\n // key に対応するバケットのインデックスを探索する\n val index = findBucket(key)\n // キーと値のペアが見つかった場合、削除マーカーで上書きする\n if (buckets[index] != null &amp;&amp; buckets[index] != TOMBSTONE) {\n buckets[index] = TOMBSTONE\n size--\n }\n }\n\n /* ハッシュテーブルの容量を拡張する */\n fun extend() {\n // 元のハッシュテーブルを一時保存する\n val bucketsTmp = buckets\n // 拡張後の新しいハッシュテーブルを初期化する\n capacity *= extendRatio\n buckets = arrayOfNulls(capacity)\n size = 0\n // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for (pair in bucketsTmp) {\n if (pair != null &amp;&amp; pair != TOMBSTONE) {\n put(pair.key, pair._val)\n }\n }\n }\n\n /* ハッシュテーブルを表示する */\n fun print() {\n for (pair in buckets) {\n if (pair == null) {\n println(\"null\")\n } else if (pair == TOMBSTONE) {\n println(\"TOMESTOME\")\n } else {\n println(\"${pair.key} -&gt; ${pair._val}\")\n }\n }\n }\n}\n</code></pre> hash_map_open_addressing.rb<pre><code>### オープンアドレス法のハッシュテーブル ###\nclass HashMapOpenAddressing\n TOMBSTONE = Pair.new(-1, '-1') # 削除マーカー\n\n ### コンストラクタ ###\n def initialize\n @size = 0 # キーと値のペアの数\n @capacity = 4 # ハッシュテーブルの容量\n @load_thres = 2.0 / 3.0 # 容量の拡張を開始する負荷率のしきい値\n @extend_ratio = 2 # 容量の拡張倍率\n @buckets = Array.new(@capacity) # バケット配列\n end\n\n ### ハッシュ関数 ###\n def hash_func(key)\n key % @capacity\n end\n\n ### 負荷率 ###\n def load_factor\n @size / @capacity\n end\n\n ### key に対応するバケットのインデックスを探索する ###\n def find_bucket(key)\n index = hash_func(key)\n first_tombstone = -1\n # 線形探索法で探索し、空のバケットに到達したら終了する\n while !@buckets[index].nil?\n # key が見つかった場合、対応するバケットのインデックスを返す\n if @buckets[index].key == key\n # それ以前に削除マーカーが見つかっていれば、そのインデックスにキーと値のペアを移す\n if first_tombstone != -1\n @buckets[first_tombstone] = @buckets[index]\n @buckets[index] = TOMBSTONE\n return first_tombstone # 移動後のバケットのインデックスを返す\n end\n return index # バケットのインデックスを返す\n end\n # 最初に見つかった削除マーカーを記録する\n first_tombstone = index if first_tombstone == -1 &amp;&amp; @buckets[index] == TOMBSTONE\n # バケットのインデックスを計算し、末尾を越えたら先頭に戻る\n index = (index + 1) % @capacity\n end\n # key が存在しない場合、追加位置のインデックスを返す\n first_tombstone == -1 ? index : first_tombstone\n end\n\n ### 取得操作 ###\n def get(key)\n # key に対応するバケットのインデックスを探索する\n index = find_bucket(key)\n # キーと値のペアが見つかった場合、対応する val を返す\n return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index])\n # キーと値のペアが存在しない場合、nil を返す\n nil\n end\n\n ### 追加操作 ###\n def put(key, val)\n # 負荷率がしきい値を超えた場合、容量を拡張する\n extend if load_factor &gt; @load_thres\n # key に対応するバケットのインデックスを探索する\n index = find_bucket(key)\n # キーと値のペアが見つかった場合、val を上書きして戻る\n unless [nil, TOMBSTONE].include?(@buckets[index])\n @buckets[index].val = val\n return\n end\n # キーと値のペアが存在しない場合、そのペアを追加する\n @buckets[index] = Pair.new(key, val)\n @size += 1\n end\n\n ### 削除操作 ###\n def remove(key)\n # key に対応するバケットのインデックスを探索する\n index = find_bucket(key)\n # キーと値のペアが見つかった場合、削除マーカーで上書きする\n unless [nil, TOMBSTONE].include?(@buckets[index])\n @buckets[index] = TOMBSTONE\n @size -= 1\n end\n end\n\n ### ハッシュテーブルの容量を拡張する ###\n def extend\n # 元のハッシュテーブルを一時保存する\n buckets_tmp = @buckets\n # 拡張後の新しいハッシュテーブルを初期化する\n @capacity *= @extend_ratio\n @buckets = Array.new(@capacity)\n @size = 0\n # キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルに移す\n for pair in buckets_tmp\n put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair)\n end\n end\n\n ### ハッシュテーブルを表示する ###\n def print\n for pair in @buckets\n if pair.nil?\n puts \"Nil\"\n elsif pair == TOMBSTONE\n puts \"TOMBSTONE\"\n else\n puts \"#{pair.key} -&gt; #{pair.val}\"\n end\n end\n end\nend\n</code></pre>","path":["第 6 章   ハッシュテーブル","6.2   ハッシュ衝突"],"tags":[]},{"location":"chapter_hashing/hash_collision/#2","level":3,"title":"2.   平方探索法","text":"<p>平方探索法も、線形探索法と同じくオープンアドレス法の代表的な戦略です。衝突時には一定の歩数ではなく、探索回数の二乗、つまり \\(1, 4, 9, \\dots\\) 歩を飛び越えます。</p> <p>主な利点は次のとおりです。</p> <ul> <li>探索回数の二乗の距離を飛び越えることで、線形探索法のクラスタリングを緩和しようとします。</li> <li>より大きな距離を飛び越えて空きを探すことで、データの分布を均等にしやすくなります。</li> </ul> <p>ただし、完璧な方法ではありません。</p> <ul> <li>ほかより埋まりやすい場所があるという、クラスタリングは依然として生じます。</li> <li>二乗で距離が増えるため、テーブル全体を探索できない場合があります。空のバケットがあっても、到達できない可能性があります。</li> </ul>","path":["第 6 章   ハッシュテーブル","6.2   ハッシュ衝突"],"tags":[]},{"location":"chapter_hashing/hash_collision/#3","level":3,"title":"3.   複数のハッシュ関数を使う方法","text":"<p>名前のとおり、複数のハッシュ関数 \\(f_1(x)\\)、\\(f_2(x)\\)、\\(f_3(x)\\)、\\(\\dots\\) を使って探索します。</p> <ul> <li>要素の挿入:\\(f_1(x)\\) で衝突したら \\(f_2(x)\\) を試し、空きが見つかるまで順に試して挿入します。</li> <li>要素の探索:挿入時と同じ順序でハッシュ関数を使い、目的の要素が見つかったら返します。空きに出会うか、すべての関数を試しても見つからなければ、存在しないので <code>None</code> を返します。</li> </ul> <p>線形探索法よりクラスタリングは起きにくいものの、複数のハッシュ関数による追加の計算が必要です。</p> <p>ヒント</p> <p>線形探索法、平方探索法、複数のハッシュ関数を使う方法のいずれも、オープンアドレス法には「要素を直接削除できない」という問題があります。</p>","path":["第 6 章   ハッシュテーブル","6.2   ハッシュ衝突"],"tags":[]},{"location":"chapter_hashing/hash_collision/#623","level":2,"title":"6.2.3   プログラミング言語ごとの選択","text":"<p>言語ごとに異なる実装戦略が採用されています。いくつか例を挙げます。</p> <ul> <li>Python はオープンアドレス法を採用しています。辞書 <code>dict</code> は疑似乱数を使って探索します。</li> <li>Java はチェイン法を採用しています。JDK 1.8 以降では、<code>HashMap</code> 内の配列の長さが 64 に、連結リストの長さが 8 に達すると、リストを赤黒木へ変換して探索性能を高めます。</li> <li>Go 1.24 以降、組み込みの <code>map</code> はデフォルトで Swiss Table に基づくオープンアドレス法を使います。Go 1.23 以前はチェイン法で、各バケットに最大 8 ペアを格納し、容量を超えるとオーバーフローバケットをつなぎます。</li> </ul>","path":["第 6 章   ハッシュテーブル","6.2   ハッシュ衝突"],"tags":[]},{"location":"chapter_hashing/hash_map/","level":1,"title":"6.1   ハッシュテーブル","text":"<p>ハッシュテーブル(hash table)は、ハッシュ表とも呼ばれます。キー <code>key</code> と値 <code>value</code> の対応を作り、要素を効率よく照会します。具体的には、キー <code>key</code> を入力すると、対応する値 <code>value</code> を \\(O(1)\\) 時間で取得できます。</p> <p>図 6-1 のように、\\(n\\) 人の学生がおり、それぞれに名前と学籍番号があるとします。「学籍番号を入力すると対応する名前を返す」機能は、図のハッシュテーブルで実現できます。</p> <p></p> <p> 図 6-1   ハッシュテーブルの抽象的な表現 </p> <p>配列や連結リストでも照会機能を実現できます。効率の比較を表 6-1 に示します。</p> <ul> <li>要素の追加:配列(連結リスト)の末尾に追加するだけなので、\\(O(1)\\) 時間です。</li> <li>要素の照会:配列(連結リスト)が未整列なので、すべての要素を走査する必要があり、\\(O(n)\\) 時間です。</li> <li>要素の削除:まず要素を探してから配列(連結リスト)から削除するため、\\(O(n)\\) 時間です。</li> </ul> <p> 表 6-1   要素の照会効率の比較 </p> 配列 連結リスト ハッシュテーブル 要素の探索 \\(O(n)\\) \\(O(n)\\) \\(O(1)\\) 要素の追加 \\(O(1)\\) \\(O(1)\\) \\(O(1)\\) 要素の削除 \\(O(n)\\) \\(O(n)\\) \\(O(1)\\) <p>ハッシュテーブルの追加、削除、照会、更新は、すべて時間計算量が \\(O(1)\\) で、非常に効率的です。</p>","path":["第 6 章   ハッシュテーブル","6.1   ハッシュテーブル"],"tags":[]},{"location":"chapter_hashing/hash_map/#611","level":2,"title":"6.1.1   ハッシュテーブルの主な操作","text":"<p>主な操作には、初期化、照会、キーと値のペアの追加や削除などがあります。コード例を示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map.py<pre><code># ハッシュテーブルを初期化\nhmap: dict = {}\n\n# 追加操作\n# ハッシュテーブルにペア (key, value) を追加\nhmap[12836] = \"小哈\"\nhmap[15937] = \"小啰\"\nhmap[16750] = \"小算\"\nhmap[13276] = \"小法\"\nhmap[10583] = \"小鸭\"\n\n# 照会操作\n# キー key を入力して値 value を取得\nname: str = hmap[15937]\n\n# 削除操作\n# ハッシュテーブルからペア (key, value) を削除\nhmap.pop(10583)\n</code></pre> hash_map.cpp<pre><code>/* ハッシュテーブルを初期化 */\nunordered_map&lt;int, string&gt; map;\n\n/* 追加操作 */\n// ハッシュテーブルにペア (key, value) を追加\nmap[12836] = \"小哈\";\nmap[15937] = \"小啰\";\nmap[16750] = \"小算\";\nmap[13276] = \"小法\";\nmap[10583] = \"小鸭\";\n\n/* 照会操作 */\n// キー key を入力して値 value を取得\nstring name = map[15937];\n\n/* 削除操作 */\n// ハッシュテーブルからペア (key, value) を削除\nmap.erase(10583);\n</code></pre> hash_map.java<pre><code>/* ハッシュテーブルを初期化 */\nMap&lt;Integer, String&gt; map = new HashMap&lt;&gt;();\n\n/* 追加操作 */\n// ハッシュテーブルにペア (key, value) を追加\nmap.put(12836, \"小哈\");\nmap.put(15937, \"小啰\");\nmap.put(16750, \"小算\");\nmap.put(13276, \"小法\");\nmap.put(10583, \"小鸭\");\n\n/* 照会操作 */\n// キー key を入力して値 value を取得\nString name = map.get(15937);\n\n/* 削除操作 */\n// ハッシュテーブルからペア (key, value) を削除\nmap.remove(10583);\n</code></pre> hash_map.cs<pre><code>/* ハッシュテーブルを初期化 */\nDictionary&lt;int, string&gt; map = new() {\n /* 追加操作 */\n // ハッシュテーブルにペア (key, value) を追加\n { 12836, \"小哈\" },\n { 15937, \"小啰\" },\n { 16750, \"小算\" },\n { 13276, \"小法\" },\n { 10583, \"小鸭\" }\n};\n\n/* 照会操作 */\n// キー key を入力して値 value を取得\nstring name = map[15937];\n\n/* 削除操作 */\n// ハッシュテーブルからペア (key, value) を削除\nmap.Remove(10583);\n</code></pre> hash_map_test.go<pre><code>/* ハッシュテーブルを初期化 */\nhmap := make(map[int]string)\n\n/* 追加操作 */\n// ハッシュテーブルにペア (key, value) を追加\nhmap[12836] = \"小哈\"\nhmap[15937] = \"小啰\"\nhmap[16750] = \"小算\"\nhmap[13276] = \"小法\"\nhmap[10583] = \"小鸭\"\n\n/* 照会操作 */\n// キー key を入力して値 value を取得\nname := hmap[15937]\n\n/* 削除操作 */\n// ハッシュテーブルからペア (key, value) を削除\ndelete(hmap, 10583)\n</code></pre> hash_map.swift<pre><code>/* ハッシュテーブルを初期化 */\nvar map: [Int: String] = [:]\n\n/* 追加操作 */\n// ハッシュテーブルにペア (key, value) を追加\nmap[12836] = \"小哈\"\nmap[15937] = \"小啰\"\nmap[16750] = \"小算\"\nmap[13276] = \"小法\"\nmap[10583] = \"小鸭\"\n\n/* 照会操作 */\n// キー key を入力して値 value を取得\nlet name = map[15937]!\n\n/* 削除操作 */\n// ハッシュテーブルからペア (key, value) を削除\nmap.removeValue(forKey: 10583)\n</code></pre> hash_map.js<pre><code>/* ハッシュテーブルを初期化 */\nconst map = new Map();\n/* 追加操作 */\n// ハッシュテーブルにペア (key, value) を追加\nmap.set(12836, '小哈');\nmap.set(15937, '小啰');\nmap.set(16750, '小算');\nmap.set(13276, '小法');\nmap.set(10583, '小鸭');\n\n/* 照会操作 */\n// キー key を入力して値 value を取得\nlet name = map.get(15937);\n\n/* 削除操作 */\n// ハッシュテーブルからペア (key, value) を削除\nmap.delete(10583);\n</code></pre> hash_map.ts<pre><code>/* ハッシュテーブルを初期化 */\nconst map = new Map&lt;number, string&gt;();\n/* 追加操作 */\n// ハッシュテーブルにペア (key, value) を追加\nmap.set(12836, '小哈');\nmap.set(15937, '小啰');\nmap.set(16750, '小算');\nmap.set(13276, '小法');\nmap.set(10583, '小鸭');\nconsole.info('\\n追加後のハッシュテーブルは\\nKey -&gt; Value');\nconsole.info(map);\n\n/* 照会操作 */\n// キー key を入力して値 value を取得\nlet name = map.get(15937);\nconsole.info('\\n学籍番号 15937 を入力して得た名前は ' + name);\n\n/* 削除操作 */\n// ハッシュテーブルからペア (key, value) を削除\nmap.delete(10583);\nconsole.info('\\n10583 を削除した後のハッシュテーブルは\\nKey -&gt; Value');\nconsole.info(map);\n</code></pre> hash_map.dart<pre><code>/* ハッシュテーブルを初期化 */\nMap&lt;int, String&gt; map = {};\n\n/* 追加操作 */\n// ハッシュテーブルにペア (key, value) を追加\nmap[12836] = \"小哈\";\nmap[15937] = \"小啰\";\nmap[16750] = \"小算\";\nmap[13276] = \"小法\";\nmap[10583] = \"小鸭\";\n\n/* 照会操作 */\n// キー key を入力して値 value を取得\nString name = map[15937];\n\n/* 削除操作 */\n// ハッシュテーブルからペア (key, value) を削除\nmap.remove(10583);\n</code></pre> hash_map.rs<pre><code>use std::collections::HashMap;\n\n/* ハッシュテーブルを初期化 */\nlet mut map: HashMap&lt;i32, String&gt; = HashMap::new();\n\n/* 追加操作 */\n// ハッシュテーブルにペア (key, value) を追加\nmap.insert(12836, \"小哈\".to_string());\nmap.insert(15937, \"小啰\".to_string());\nmap.insert(16750, \"小算\".to_string());\nmap.insert(13279, \"小法\".to_string());\nmap.insert(10583, \"小鸭\".to_string());\n\n/* 照会操作 */\n// キー key を入力して値 value を取得\nlet _name: Option&lt;&amp;String&gt; = map.get(&amp;15937);\n\n/* 削除操作 */\n// ハッシュテーブルからペア (key, value) を削除\nlet _removed_value: Option&lt;String&gt; = map.remove(&amp;10583);\n</code></pre> hash_map.c<pre><code>// C には組み込みのハッシュテーブルがない\n</code></pre> hash_map.kt<pre><code>/* ハッシュテーブルを初期化 */\nval map = HashMap&lt;Int,String&gt;()\n\n/* 追加操作 */\n// ハッシュテーブルにペア (key, value) を追加\nmap[12836] = \"小哈\"\nmap[15937] = \"小啰\"\nmap[16750] = \"小算\"\nmap[13276] = \"小法\"\nmap[10583] = \"小鸭\"\n\n/* 照会操作 */\n// キー key を入力して値 value を取得\nval name = map[15937]\n\n/* 削除操作 */\n// ハッシュテーブルからペア (key, value) を削除\nmap.remove(10583)\n</code></pre> hash_map.rb<pre><code># ハッシュテーブルを初期化\nhmap = {}\n\n# 追加操作\n# ハッシュテーブルにペア (key, value) を追加\nhmap[12836] = \"小哈\"\nhmap[15937] = \"小啰\"\nhmap[16750] = \"小算\"\nhmap[13276] = \"小法\"\nhmap[10583] = \"小鸭\"\n\n# 照会操作\n# キー key を入力して値 value を取得\nname = hmap[15937]\n\n# 削除操作\n# ハッシュテーブルからペア (key, value) を削除\nhmap.delete(10583)\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>ハッシュテーブルの主な走査方法は、キーと値のペアの走査、キーだけの走査、値だけの走査の三つです。コード例を示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby hash_map.py<pre><code># ハッシュテーブルを走査\n# キーと値のペアを走査 key-&gt;value\nfor key, value in hmap.items():\n print(key, \"-&gt;\", value)\n# キーだけを走査 key\nfor key in hmap.keys():\n print(key)\n# 値だけを走査 value\nfor value in hmap.values():\n print(value)\n</code></pre> hash_map.cpp<pre><code>/* ハッシュテーブルを走査 */\n// キーと値のペアを走査 key-&gt;value\nfor (auto kv: map) {\n cout &lt;&lt; kv.first &lt;&lt; \" -&gt; \" &lt;&lt; kv.second &lt;&lt; endl;\n}\n// イテレータで走査 key-&gt;value\nfor (auto iter = map.begin(); iter != map.end(); iter++) {\n cout &lt;&lt; iter-&gt;first &lt;&lt; \"-&gt;\" &lt;&lt; iter-&gt;second &lt;&lt; endl;\n}\n</code></pre> hash_map.java<pre><code>/* ハッシュテーブルを走査 */\n// キーと値のペアを走査 key-&gt;value\nfor (Map.Entry &lt;Integer, String&gt; kv: map.entrySet()) {\n System.out.println(kv.getKey() + \" -&gt; \" + kv.getValue());\n}\n// キーだけを走査 key\nfor (int key: map.keySet()) {\n System.out.println(key);\n}\n// 値だけを走査 value\nfor (String val: map.values()) {\n System.out.println(val);\n}\n</code></pre> hash_map.cs<pre><code>/* ハッシュテーブルを走査 */\n// キーと値のペアを走査 Key-&gt;Value\nforeach (var kv in map) {\n Console.WriteLine(kv.Key + \" -&gt; \" + kv.Value);\n}\n// キーだけを走査 key\nforeach (int key in map.Keys) {\n Console.WriteLine(key);\n}\n// 値だけを走査 value\nforeach (string val in map.Values) {\n Console.WriteLine(val);\n}\n</code></pre> hash_map_test.go<pre><code>/* ハッシュテーブルを走査 */\n// キーと値のペアを走査 key-&gt;value\nfor key, value := range hmap {\n fmt.Println(key, \"-&gt;\", value)\n}\n// キーだけを走査 key\nfor key := range hmap {\n fmt.Println(key)\n}\n// 値だけを走査 value\nfor _, value := range hmap {\n fmt.Println(value)\n}\n</code></pre> hash_map.swift<pre><code>/* ハッシュテーブルを走査 */\n// キーと値のペアを走査 Key-&gt;Value\nfor (key, value) in map {\n print(\"\\(key) -&gt; \\(value)\")\n}\n// キーだけを走査 Key\nfor key in map.keys {\n print(key)\n}\n// 値だけを走査 Value\nfor value in map.values {\n print(value)\n}\n</code></pre> hash_map.js<pre><code>/* ハッシュテーブルを走査 */\nconsole.info('\\nキーと値のペアを走査 Key-&gt;Value');\nfor (const [k, v] of map.entries()) {\n console.info(k + ' -&gt; ' + v);\n}\nconsole.info('\\nキーだけを走査 Key');\nfor (const k of map.keys()) {\n console.info(k);\n}\nconsole.info('\\n値だけを走査 Value');\nfor (const v of map.values()) {\n console.info(v);\n}\n</code></pre> hash_map.ts<pre><code>/* ハッシュテーブルを走査 */\nconsole.info('\\nキーと値のペアを走査 Key-&gt;Value');\nfor (const [k, v] of map.entries()) {\n console.info(k + ' -&gt; ' + v);\n}\nconsole.info('\\nキーだけを走査 Key');\nfor (const k of map.keys()) {\n console.info(k);\n}\nconsole.info('\\n値だけを走査 Value');\nfor (const v of map.values()) {\n console.info(v);\n}\n</code></pre> hash_map.dart<pre><code>/* ハッシュテーブルを走査 */\n// キーと値のペアを走査 Key-&gt;Value\nmap.forEach((key, value) {\n print('$key -&gt; $value');\n});\n\n// キーだけを走査 Key\nmap.keys.forEach((key) {\n print(key);\n});\n\n// 値だけを走査 Value\nmap.values.forEach((value) {\n print(value);\n});\n</code></pre> hash_map.rs<pre><code>/* ハッシュテーブルを走査 */\n// キーと値のペアを走査 Key-&gt;Value\nfor (key, value) in &amp;map {\n println!(\"{key} -&gt; {value}\");\n}\n\n// キーだけを走査 Key\nfor key in map.keys() {\n println!(\"{key}\");\n}\n\n// 値だけを走査 Value\nfor value in map.values() {\n println!(\"{value}\");\n}\n</code></pre> hash_map.c<pre><code>// C には組み込みのハッシュテーブルがない\n</code></pre> hash_map.kt<pre><code>/* ハッシュテーブルを走査 */\n// キーと値のペアを走査 key-&gt;value\nfor ((key, value) in map) {\n println(\"$key -&gt; $value\")\n}\n// キーだけを走査 key\nfor (key in map.keys) {\n println(key)\n}\n// 値だけを走査 value\nfor (_val in map.values) {\n println(_val)\n}\n</code></pre> hash_map.rb<pre><code># ハッシュテーブルを走査\n# キーと値のペアを走査 key-&gt;value\nhmap.entries.each { |key, value| puts \"#{key} -&gt; #{value}\" }\n\n# キーだけを走査 key\nhmap.keys.each { |key| puts key }\n\n# 値だけを走査 value\nhmap.values.each { |val| puts val }\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 6 章   ハッシュテーブル","6.1   ハッシュテーブル"],"tags":[]},{"location":"chapter_hashing/hash_map/#612","level":2,"title":"6.1.2   ハッシュテーブルの単純な実装","text":"<p>まず最も単純な場合として、一つの配列だけでハッシュテーブルを実装します。配列内の各格納場所をバケット(bucket)と呼び、それぞれにキーと値のペアを一つ格納できます。照会とは、<code>key</code> に対応するバケットを探し、そこから <code>value</code> を取得する操作です。</p> <p>では、<code>key</code> から対応するバケットをどう見つけるのでしょうか。ハッシュ関数(hash function)を使います。その役割は、大きな入力空間を小さな出力空間へ写すことです。ハッシュテーブルでは、入力空間はすべての <code>key</code>、出力空間はすべてのバケット、つまり配列のインデックスです。言い換えると、<code>key</code> を入力すれば、そのペアが配列のどこに格納されるかをハッシュ関数で求められます。</p> <p><code>key</code> を入力したとき、ハッシュ関数の計算は次の二段階に分かれます。</p> <ol> <li>何らかのハッシュアルゴリズム <code>hash()</code> でハッシュ値を求めます。</li> <li>ハッシュ値をバケット数(配列の長さ)<code>capacity</code> で割った余りを取り、対応するバケット(配列のインデックス)<code>index</code> を得ます。</li> </ol> <pre><code>index = hash(key) % capacity\n</code></pre> <p>その後、<code>index</code> でハッシュテーブル内の対応するバケットにアクセスし、<code>value</code> を取得できます。</p> <p>配列の長さを <code>capacity = 100</code>、ハッシュアルゴリズムを <code>hash(key) = key</code> とすると、ハッシュ関数は <code>key % 100</code> です。図 6-2 では、学籍番号を <code>key</code>、名前を <code>value</code> として、その仕組みを示しています。</p> <p></p> <p> 図 6-2   ハッシュ関数の仕組み </p> <p>以下は単純なハッシュテーブルの実装です。<code>key</code> と <code>value</code> をクラス <code>Pair</code> にまとめ、キーと値のペアを表しています。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_hash_map.py<pre><code>class Pair:\n \"\"\"キーと値のペア\"\"\"\n\n def __init__(self, key: int, val: str):\n self.key = key\n self.val = val\n\nclass ArrayHashMap:\n \"\"\"配列で実装したハッシュテーブル\"\"\"\n\n def __init__(self):\n \"\"\"コンストラクタ\"\"\"\n # 100 個のバケットを含む配列を初期化する\n self.buckets: list[Pair | None] = [None] * 100\n\n def hash_func(self, key: int) -&gt; int:\n \"\"\"ハッシュ関数\"\"\"\n index = key % 100\n return index\n\n def get(self, key: int) -&gt; str | None:\n \"\"\"取得操作\"\"\"\n index: int = self.hash_func(key)\n pair: Pair = self.buckets[index]\n if pair is None:\n return None\n return pair.val\n\n def put(self, key: int, val: str):\n \"\"\"追加と更新の操作\"\"\"\n pair = Pair(key, val)\n index: int = self.hash_func(key)\n self.buckets[index] = pair\n\n def remove(self, key: int):\n \"\"\"削除操作\"\"\"\n index: int = self.hash_func(key)\n # None に設定して、削除を表す\n self.buckets[index] = None\n\n def entry_set(self) -&gt; list[Pair]:\n \"\"\"キーと値のペアをすべて取得する\"\"\"\n result: list[Pair] = []\n for pair in self.buckets:\n if pair is not None:\n result.append(pair)\n return result\n\n def key_set(self) -&gt; list[int]:\n \"\"\"すべてのキーを取得する\"\"\"\n result = []\n for pair in self.buckets:\n if pair is not None:\n result.append(pair.key)\n return result\n\n def value_set(self) -&gt; list[str]:\n \"\"\"すべての値を取得する\"\"\"\n result = []\n for pair in self.buckets:\n if pair is not None:\n result.append(pair.val)\n return result\n\n def print(self):\n \"\"\"ハッシュテーブルを表示する\"\"\"\n for pair in self.buckets:\n if pair is not None:\n print(pair.key, \"-&gt;\", pair.val)\n</code></pre> array_hash_map.cpp<pre><code>/* キーと値のペア */\nstruct Pair {\n public:\n int key;\n string val;\n Pair(int key, string val) {\n this-&gt;key = key;\n this-&gt;val = val;\n }\n};\n\n/* 配列で実装したハッシュテーブル */\nclass ArrayHashMap {\n private:\n vector&lt;Pair *&gt; buckets;\n\n public:\n ArrayHashMap() {\n // 100 個のバケットを含む配列を初期化する\n buckets = vector&lt;Pair *&gt;(100);\n }\n\n ~ArrayHashMap() {\n // メモリを解放する\n for (const auto &amp;bucket : buckets) {\n delete bucket;\n }\n buckets.clear();\n }\n\n /* ハッシュ関数 */\n int hashFunc(int key) {\n int index = key % 100;\n return index;\n }\n\n /* 取得操作 */\n string get(int key) {\n int index = hashFunc(key);\n Pair *pair = buckets[index];\n if (pair == nullptr)\n return \"\";\n return pair-&gt;val;\n }\n\n /* 追加操作 */\n void put(int key, string val) {\n Pair *pair = new Pair(key, val);\n int index = hashFunc(key);\n buckets[index] = pair;\n }\n\n /* 削除操作 */\n void remove(int key) {\n int index = hashFunc(key);\n // メモリを解放し、nullptr に設定する\n delete buckets[index];\n buckets[index] = nullptr;\n }\n\n /* キーと値のペアをすべて取得する */\n vector&lt;Pair *&gt; pairSet() {\n vector&lt;Pair *&gt; pairSet;\n for (Pair *pair : buckets) {\n if (pair != nullptr) {\n pairSet.push_back(pair);\n }\n }\n return pairSet;\n }\n\n /* すべてのキーを取得する */\n vector&lt;int&gt; keySet() {\n vector&lt;int&gt; keySet;\n for (Pair *pair : buckets) {\n if (pair != nullptr) {\n keySet.push_back(pair-&gt;key);\n }\n }\n return keySet;\n }\n\n /* すべての値を取得する */\n vector&lt;string&gt; valueSet() {\n vector&lt;string&gt; valueSet;\n for (Pair *pair : buckets) {\n if (pair != nullptr) {\n valueSet.push_back(pair-&gt;val);\n }\n }\n return valueSet;\n }\n\n /* ハッシュテーブルを表示する */\n void print() {\n for (Pair *kv : pairSet()) {\n cout &lt;&lt; kv-&gt;key &lt;&lt; \" -&gt; \" &lt;&lt; kv-&gt;val &lt;&lt; endl;\n }\n }\n};\n</code></pre> array_hash_map.java<pre><code>/* キーと値のペア */\nclass Pair {\n public int key;\n public String val;\n\n public Pair(int key, String val) {\n this.key = key;\n this.val = val;\n }\n}\n\n/* 配列で実装したハッシュテーブル */\nclass ArrayHashMap {\n private List&lt;Pair&gt; buckets;\n\n public ArrayHashMap() {\n // 100 個のバケットを含む配列を初期化する\n buckets = new ArrayList&lt;&gt;();\n for (int i = 0; i &lt; 100; i++) {\n buckets.add(null);\n }\n }\n\n /* ハッシュ関数 */\n private int hashFunc(int key) {\n int index = key % 100;\n return index;\n }\n\n /* 取得操作 */\n public String get(int key) {\n int index = hashFunc(key);\n Pair pair = buckets.get(index);\n if (pair == null)\n return null;\n return pair.val;\n }\n\n /* 追加操作 */\n public void put(int key, String val) {\n Pair pair = new Pair(key, val);\n int index = hashFunc(key);\n buckets.set(index, pair);\n }\n\n /* 削除操作 */\n public void remove(int key) {\n int index = hashFunc(key);\n // null に設定して、削除を表す\n buckets.set(index, null);\n }\n\n /* キーと値のペアをすべて取得する */\n public List&lt;Pair&gt; pairSet() {\n List&lt;Pair&gt; pairSet = new ArrayList&lt;&gt;();\n for (Pair pair : buckets) {\n if (pair != null)\n pairSet.add(pair);\n }\n return pairSet;\n }\n\n /* すべてのキーを取得する */\n public List&lt;Integer&gt; keySet() {\n List&lt;Integer&gt; keySet = new ArrayList&lt;&gt;();\n for (Pair pair : buckets) {\n if (pair != null)\n keySet.add(pair.key);\n }\n return keySet;\n }\n\n /* すべての値を取得する */\n public List&lt;String&gt; valueSet() {\n List&lt;String&gt; valueSet = new ArrayList&lt;&gt;();\n for (Pair pair : buckets) {\n if (pair != null)\n valueSet.add(pair.val);\n }\n return valueSet;\n }\n\n /* ハッシュテーブルを表示する */\n public void print() {\n for (Pair kv : pairSet()) {\n System.out.println(kv.key + \" -&gt; \" + kv.val);\n }\n }\n}\n</code></pre> array_hash_map.cs<pre><code>/* キーと値のペア int-&gt;string */\nclass Pair(int key, string val) {\n public int key = key;\n public string val = val;\n}\n\n/* 配列で実装したハッシュテーブル */\nclass ArrayHashMap {\n List&lt;Pair?&gt; buckets;\n public ArrayHashMap() {\n // 100 個のバケットを含む配列を初期化する\n buckets = [];\n for (int i = 0; i &lt; 100; i++) {\n buckets.Add(null);\n }\n }\n\n /* ハッシュ関数 */\n int HashFunc(int key) {\n int index = key % 100;\n return index;\n }\n\n /* 取得操作 */\n public string? Get(int key) {\n int index = HashFunc(key);\n Pair? pair = buckets[index];\n if (pair == null) return null;\n return pair.val;\n }\n\n /* 追加操作 */\n public void Put(int key, string val) {\n Pair pair = new(key, val);\n int index = HashFunc(key);\n buckets[index] = pair;\n }\n\n /* 削除操作 */\n public void Remove(int key) {\n int index = HashFunc(key);\n // null に設定して、削除を表す\n buckets[index] = null;\n }\n\n /* キーと値のペアをすべて取得する */\n public List&lt;Pair&gt; PairSet() {\n List&lt;Pair&gt; pairSet = [];\n foreach (Pair? pair in buckets) {\n if (pair != null)\n pairSet.Add(pair);\n }\n return pairSet;\n }\n\n /* すべてのキーを取得する */\n public List&lt;int&gt; KeySet() {\n List&lt;int&gt; keySet = [];\n foreach (Pair? pair in buckets) {\n if (pair != null)\n keySet.Add(pair.key);\n }\n return keySet;\n }\n\n /* すべての値を取得する */\n public List&lt;string&gt; ValueSet() {\n List&lt;string&gt; valueSet = [];\n foreach (Pair? pair in buckets) {\n if (pair != null)\n valueSet.Add(pair.val);\n }\n return valueSet;\n }\n\n /* ハッシュテーブルを表示する */\n public void Print() {\n foreach (Pair kv in PairSet()) {\n Console.WriteLine(kv.key + \" -&gt; \" + kv.val);\n }\n }\n}\n</code></pre> array_hash_map.go<pre><code>/* キーと値のペア */\ntype pair struct {\n key int\n val string\n}\n\n/* 配列で実装したハッシュテーブル */\ntype arrayHashMap struct {\n buckets []*pair\n}\n\n/* ハッシュテーブルを初期化する */\nfunc newArrayHashMap() *arrayHashMap {\n // 100 個のバケットを含む配列を初期化する\n buckets := make([]*pair, 100)\n return &amp;arrayHashMap{buckets: buckets}\n}\n\n/* ハッシュ関数 */\nfunc (a *arrayHashMap) hashFunc(key int) int {\n index := key % 100\n return index\n}\n\n/* 取得操作 */\nfunc (a *arrayHashMap) get(key int) string {\n index := a.hashFunc(key)\n pair := a.buckets[index]\n if pair == nil {\n return \"Not Found\"\n }\n return pair.val\n}\n\n/* 追加操作 */\nfunc (a *arrayHashMap) put(key int, val string) {\n pair := &amp;pair{key: key, val: val}\n index := a.hashFunc(key)\n a.buckets[index] = pair\n}\n\n/* 削除操作 */\nfunc (a *arrayHashMap) remove(key int) {\n index := a.hashFunc(key)\n // nil に設定して、削除を表す\n a.buckets[index] = nil\n}\n\n/* すべてのキーと値のペアを取得する */\nfunc (a *arrayHashMap) pairSet() []*pair {\n var pairs []*pair\n for _, pair := range a.buckets {\n if pair != nil {\n pairs = append(pairs, pair)\n }\n }\n return pairs\n}\n\n/* すべてのキーを取得する */\nfunc (a *arrayHashMap) keySet() []int {\n var keys []int\n for _, pair := range a.buckets {\n if pair != nil {\n keys = append(keys, pair.key)\n }\n }\n return keys\n}\n\n/* すべての値を取得する */\nfunc (a *arrayHashMap) valueSet() []string {\n var values []string\n for _, pair := range a.buckets {\n if pair != nil {\n values = append(values, pair.val)\n }\n }\n return values\n}\n\n/* ハッシュテーブルを表示する */\nfunc (a *arrayHashMap) print() {\n for _, pair := range a.buckets {\n if pair != nil {\n fmt.Println(pair.key, \"-&gt;\", pair.val)\n }\n }\n}\n</code></pre> array_hash_map.swift<pre><code>/* キーと値のペア */\nclass Pair: Equatable {\n public var key: Int\n public var val: String\n\n public init(key: Int, val: String) {\n self.key = key\n self.val = val\n }\n\n public static func == (lhs: Pair, rhs: Pair) -&gt; Bool {\n lhs.key == rhs.key &amp;&amp; lhs.val == rhs.val\n }\n}\n\n/* 配列で実装したハッシュテーブル */\nclass ArrayHashMap {\n private var buckets: [Pair?]\n\n init() {\n // 100 個のバケットを含む配列を初期化する\n buckets = Array(repeating: nil, count: 100)\n }\n\n /* ハッシュ関数 */\n private func hashFunc(key: Int) -&gt; Int {\n let index = key % 100\n return index\n }\n\n /* 取得操作 */\n func get(key: Int) -&gt; String? {\n let index = hashFunc(key: key)\n let pair = buckets[index]\n return pair?.val\n }\n\n /* 追加操作 */\n func put(key: Int, val: String) {\n let pair = Pair(key: key, val: val)\n let index = hashFunc(key: key)\n buckets[index] = pair\n }\n\n /* 削除操作 */\n func remove(key: Int) {\n let index = hashFunc(key: key)\n // nil に設定して、削除を表す\n buckets[index] = nil\n }\n\n /* キーと値のペアをすべて取得する */\n func pairSet() -&gt; [Pair] {\n buckets.compactMap { $0 }\n }\n\n /* すべてのキーを取得する */\n func keySet() -&gt; [Int] {\n buckets.compactMap { $0?.key }\n }\n\n /* すべての値を取得する */\n func valueSet() -&gt; [String] {\n buckets.compactMap { $0?.val }\n }\n\n /* ハッシュテーブルを表示する */\n func print() {\n for pair in pairSet() {\n Swift.print(\"\\(pair.key) -&gt; \\(pair.val)\")\n }\n }\n}\n</code></pre> array_hash_map.js<pre><code>/* キーと値のペア Number -&gt; String */\nclass Pair {\n constructor(key, val) {\n this.key = key;\n this.val = val;\n }\n}\n\n/* 配列で実装したハッシュテーブル */\nclass ArrayHashMap {\n #buckets;\n constructor() {\n // 100 個のバケットを含む配列を初期化する\n this.#buckets = new Array(100).fill(null);\n }\n\n /* ハッシュ関数 */\n #hashFunc(key) {\n return key % 100;\n }\n\n /* 取得操作 */\n get(key) {\n let index = this.#hashFunc(key);\n let pair = this.#buckets[index];\n if (pair === null) return null;\n return pair.val;\n }\n\n /* 追加操作 */\n set(key, val) {\n let index = this.#hashFunc(key);\n this.#buckets[index] = new Pair(key, val);\n }\n\n /* 削除操作 */\n delete(key) {\n let index = this.#hashFunc(key);\n // null に設定して、削除を表す\n this.#buckets[index] = null;\n }\n\n /* キーと値のペアをすべて取得する */\n entries() {\n let arr = [];\n for (let i = 0; i &lt; this.#buckets.length; i++) {\n if (this.#buckets[i]) {\n arr.push(this.#buckets[i]);\n }\n }\n return arr;\n }\n\n /* すべてのキーを取得する */\n keys() {\n let arr = [];\n for (let i = 0; i &lt; this.#buckets.length; i++) {\n if (this.#buckets[i]) {\n arr.push(this.#buckets[i].key);\n }\n }\n return arr;\n }\n\n /* すべての値を取得する */\n values() {\n let arr = [];\n for (let i = 0; i &lt; this.#buckets.length; i++) {\n if (this.#buckets[i]) {\n arr.push(this.#buckets[i].val);\n }\n }\n return arr;\n }\n\n /* ハッシュテーブルを表示する */\n print() {\n let pairSet = this.entries();\n for (const pair of pairSet) {\n console.info(`${pair.key} -&gt; ${pair.val}`);\n }\n }\n}\n</code></pre> array_hash_map.ts<pre><code>/* キーと値のペア Number -&gt; String */\nclass Pair {\n public key: number;\n public val: string;\n\n constructor(key: number, val: string) {\n this.key = key;\n this.val = val;\n }\n}\n\n/* 配列で実装したハッシュテーブル */\nclass ArrayHashMap {\n private readonly buckets: (Pair | null)[];\n\n constructor() {\n // 100 個のバケットを含む配列を初期化する\n this.buckets = new Array(100).fill(null);\n }\n\n /* ハッシュ関数 */\n private hashFunc(key: number): number {\n return key % 100;\n }\n\n /* 取得操作 */\n public get(key: number): string | null {\n let index = this.hashFunc(key);\n let pair = this.buckets[index];\n if (pair === null) return null;\n return pair.val;\n }\n\n /* 追加操作 */\n public set(key: number, val: string) {\n let index = this.hashFunc(key);\n this.buckets[index] = new Pair(key, val);\n }\n\n /* 削除操作 */\n public delete(key: number) {\n let index = this.hashFunc(key);\n // null に設定して、削除を表す\n this.buckets[index] = null;\n }\n\n /* キーと値のペアをすべて取得する */\n public entries(): (Pair | null)[] {\n let arr: (Pair | null)[] = [];\n for (let i = 0; i &lt; this.buckets.length; i++) {\n if (this.buckets[i]) {\n arr.push(this.buckets[i]);\n }\n }\n return arr;\n }\n\n /* すべてのキーを取得する */\n public keys(): (number | undefined)[] {\n let arr: (number | undefined)[] = [];\n for (let i = 0; i &lt; this.buckets.length; i++) {\n if (this.buckets[i]) {\n arr.push(this.buckets[i].key);\n }\n }\n return arr;\n }\n\n /* すべての値を取得する */\n public values(): (string | undefined)[] {\n let arr: (string | undefined)[] = [];\n for (let i = 0; i &lt; this.buckets.length; i++) {\n if (this.buckets[i]) {\n arr.push(this.buckets[i].val);\n }\n }\n return arr;\n }\n\n /* ハッシュテーブルを表示する */\n public print() {\n let pairSet = this.entries();\n for (const pair of pairSet) {\n console.info(`${pair.key} -&gt; ${pair.val}`);\n }\n }\n}\n</code></pre> array_hash_map.dart<pre><code>/* キーと値のペア */\nclass Pair {\n int key;\n String val;\n Pair(this.key, this.val);\n}\n\n/* 配列で実装したハッシュテーブル */\nclass ArrayHashMap {\n late List&lt;Pair?&gt; _buckets;\n\n ArrayHashMap() {\n // 100 個のバケットを含む配列を初期化する\n _buckets = List.filled(100, null);\n }\n\n /* ハッシュ関数 */\n int _hashFunc(int key) {\n final int index = key % 100;\n return index;\n }\n\n /* 取得操作 */\n String? get(int key) {\n final int index = _hashFunc(key);\n final Pair? pair = _buckets[index];\n if (pair == null) {\n return null;\n }\n return pair.val;\n }\n\n /* 追加操作 */\n void put(int key, String val) {\n final Pair pair = Pair(key, val);\n final int index = _hashFunc(key);\n _buckets[index] = pair;\n }\n\n /* 削除操作 */\n void remove(int key) {\n final int index = _hashFunc(key);\n _buckets[index] = null;\n }\n\n /* キーと値のペアをすべて取得する */\n List&lt;Pair&gt; pairSet() {\n List&lt;Pair&gt; pairSet = [];\n for (final Pair? pair in _buckets) {\n if (pair != null) {\n pairSet.add(pair);\n }\n }\n return pairSet;\n }\n\n /* すべてのキーを取得する */\n List&lt;int&gt; keySet() {\n List&lt;int&gt; keySet = [];\n for (final Pair? pair in _buckets) {\n if (pair != null) {\n keySet.add(pair.key);\n }\n }\n return keySet;\n }\n\n /* すべての値を取得する */\n List&lt;String&gt; values() {\n List&lt;String&gt; valueSet = [];\n for (final Pair? pair in _buckets) {\n if (pair != null) {\n valueSet.add(pair.val);\n }\n }\n return valueSet;\n }\n\n /* ハッシュテーブルを表示する */\n void printHashMap() {\n for (final Pair kv in pairSet()) {\n print(\"${kv.key} -&gt; ${kv.val}\");\n }\n }\n}\n</code></pre> array_hash_map.rs<pre><code>/* キーと値のペア */\n#[derive(Debug, Clone, PartialEq)]\npub struct Pair {\n pub key: i32,\n pub val: String,\n}\n\n/* 配列で実装したハッシュテーブル */\npub struct ArrayHashMap {\n buckets: Vec&lt;Option&lt;Pair&gt;&gt;,\n}\n\nimpl ArrayHashMap {\n pub fn new() -&gt; ArrayHashMap {\n // 100 個のバケットを含む配列を初期化する\n Self {\n buckets: vec![None; 100],\n }\n }\n\n /* ハッシュ関数 */\n fn hash_func(&amp;self, key: i32) -&gt; usize {\n key as usize % 100\n }\n\n /* 取得操作 */\n pub fn get(&amp;self, key: i32) -&gt; Option&lt;&amp;String&gt; {\n let index = self.hash_func(key);\n self.buckets[index].as_ref().map(|pair| &amp;pair.val)\n }\n\n /* 追加操作 */\n pub fn put(&amp;mut self, key: i32, val: &amp;str) {\n let index = self.hash_func(key);\n self.buckets[index] = Some(Pair {\n key,\n val: val.to_string(),\n });\n }\n\n /* 削除操作 */\n pub fn remove(&amp;mut self, key: i32) {\n let index = self.hash_func(key);\n // None に設定して、削除を表す\n self.buckets[index] = None;\n }\n\n /* キーと値のペアをすべて取得する */\n pub fn entry_set(&amp;self) -&gt; Vec&lt;&amp;Pair&gt; {\n self.buckets\n .iter()\n .filter_map(|pair| pair.as_ref())\n .collect()\n }\n\n /* すべてのキーを取得する */\n pub fn key_set(&amp;self) -&gt; Vec&lt;&amp;i32&gt; {\n self.buckets\n .iter()\n .filter_map(|pair| pair.as_ref().map(|pair| &amp;pair.key))\n .collect()\n }\n\n /* すべての値を取得する */\n pub fn value_set(&amp;self) -&gt; Vec&lt;&amp;String&gt; {\n self.buckets\n .iter()\n .filter_map(|pair| pair.as_ref().map(|pair| &amp;pair.val))\n .collect()\n }\n\n /* ハッシュテーブルを表示する */\n pub fn print(&amp;self) {\n for pair in self.entry_set() {\n println!(\"{} -&gt; {}\", pair.key, pair.val);\n }\n }\n}\n</code></pre> array_hash_map.c<pre><code>/* キーと値のペア int-&gt;string */\ntypedef struct {\n int key;\n char *val;\n} Pair;\n\n/* 配列で実装したハッシュテーブル */\ntypedef struct {\n Pair *buckets[MAX_SIZE];\n} ArrayHashMap;\n\n/* コンストラクタ */\nArrayHashMap *newArrayHashMap() {\n ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap));\n for (int i=0; i &lt; MAX_SIZE; i++) {\n hmap-&gt;buckets[i] = NULL;\n }\n return hmap;\n}\n\n/* デストラクタ */\nvoid delArrayHashMap(ArrayHashMap *hmap) {\n for (int i = 0; i &lt; MAX_SIZE; i++) {\n if (hmap-&gt;buckets[i] != NULL) {\n free(hmap-&gt;buckets[i]-&gt;val);\n free(hmap-&gt;buckets[i]);\n }\n }\n free(hmap);\n}\n\n/* 追加操作 */\nvoid put(ArrayHashMap *hmap, const int key, const char *val) {\n Pair *Pair = malloc(sizeof(Pair));\n Pair-&gt;key = key;\n Pair-&gt;val = malloc(strlen(val) + 1);\n strcpy(Pair-&gt;val, val);\n\n int index = hashFunc(key);\n hmap-&gt;buckets[index] = Pair;\n}\n\n/* 削除操作 */\nvoid removeItem(ArrayHashMap *hmap, const int key) {\n int index = hashFunc(key);\n free(hmap-&gt;buckets[index]-&gt;val);\n free(hmap-&gt;buckets[index]);\n hmap-&gt;buckets[index] = NULL;\n}\n\n/* キーと値のペアをすべて取得する */\nvoid pairSet(ArrayHashMap *hmap, MapSet *set) {\n Pair *entries;\n int i = 0, index = 0;\n int total = 0;\n /* 有効なキーと値のペアの数を数える */\n for (i = 0; i &lt; MAX_SIZE; i++) {\n if (hmap-&gt;buckets[i] != NULL) {\n total++;\n }\n }\n entries = malloc(sizeof(Pair) * total);\n for (i = 0; i &lt; MAX_SIZE; i++) {\n if (hmap-&gt;buckets[i] != NULL) {\n entries[index].key = hmap-&gt;buckets[i]-&gt;key;\n entries[index].val = malloc(strlen(hmap-&gt;buckets[i]-&gt;val) + 1);\n strcpy(entries[index].val, hmap-&gt;buckets[i]-&gt;val);\n index++;\n }\n }\n set-&gt;set = entries;\n set-&gt;len = total;\n}\n\n/* すべてのキーを取得する */\nvoid keySet(ArrayHashMap *hmap, MapSet *set) {\n int *keys;\n int i = 0, index = 0;\n int total = 0;\n /* 有効なキーと値のペアの数を数える */\n for (i = 0; i &lt; MAX_SIZE; i++) {\n if (hmap-&gt;buckets[i] != NULL) {\n total++;\n }\n }\n keys = malloc(total * sizeof(int));\n for (i = 0; i &lt; MAX_SIZE; i++) {\n if (hmap-&gt;buckets[i] != NULL) {\n keys[index] = hmap-&gt;buckets[i]-&gt;key;\n index++;\n }\n }\n set-&gt;set = keys;\n set-&gt;len = total;\n}\n\n/* すべての値を取得する */\nvoid valueSet(ArrayHashMap *hmap, MapSet *set) {\n char **vals;\n int i = 0, index = 0;\n int total = 0;\n /* 有効なキーと値のペアの数を数える */\n for (i = 0; i &lt; MAX_SIZE; i++) {\n if (hmap-&gt;buckets[i] != NULL) {\n total++;\n }\n }\n vals = malloc(total * sizeof(char *));\n for (i = 0; i &lt; MAX_SIZE; i++) {\n if (hmap-&gt;buckets[i] != NULL) {\n vals[index] = hmap-&gt;buckets[i]-&gt;val;\n index++;\n }\n }\n set-&gt;set = vals;\n set-&gt;len = total;\n}\n\n/* ハッシュテーブルを表示する */\nvoid print(ArrayHashMap *hmap) {\n int i;\n MapSet set;\n pairSet(hmap, &amp;set);\n Pair *entries = (Pair *)set.set;\n for (i = 0; i &lt; set.len; i++) {\n printf(\"%d -&gt; %s\\n\", entries[i].key, entries[i].val);\n }\n free(set.set);\n}\n</code></pre> array_hash_map.kt<pre><code>/* キーと値のペア */\nclass Pair(\n var key: Int,\n var _val: String\n)\n\n/* 配列で実装したハッシュテーブル */\nclass ArrayHashMap {\n // 100 個のバケットを含む配列を初期化する\n private val buckets = arrayOfNulls&lt;Pair&gt;(100)\n\n /* ハッシュ関数 */\n fun hashFunc(key: Int): Int {\n val index = key % 100\n return index\n }\n\n /* 取得操作 */\n fun get(key: Int): String? {\n val index = hashFunc(key)\n val pair = buckets[index] ?: return null\n return pair._val\n }\n\n /* 追加操作 */\n fun put(key: Int, _val: String) {\n val pair = Pair(key, _val)\n val index = hashFunc(key)\n buckets[index] = pair\n }\n\n /* 削除操作 */\n fun remove(key: Int) {\n val index = hashFunc(key)\n // null に設定して、削除を表す\n buckets[index] = null\n }\n\n /* キーと値のペアをすべて取得する */\n fun pairSet(): MutableList&lt;Pair&gt; {\n val pairSet = mutableListOf&lt;Pair&gt;()\n for (pair in buckets) {\n if (pair != null)\n pairSet.add(pair)\n }\n return pairSet\n }\n\n /* すべてのキーを取得する */\n fun keySet(): MutableList&lt;Int&gt; {\n val keySet = mutableListOf&lt;Int&gt;()\n for (pair in buckets) {\n if (pair != null)\n keySet.add(pair.key)\n }\n return keySet\n }\n\n /* すべての値を取得する */\n fun valueSet(): MutableList&lt;String&gt; {\n val valueSet = mutableListOf&lt;String&gt;()\n for (pair in buckets) {\n if (pair != null)\n valueSet.add(pair._val)\n }\n return valueSet\n }\n\n /* ハッシュテーブルを表示する */\n fun print() {\n for (kv in pairSet()) {\n val key = kv.key\n val _val = kv._val\n println(\"$key -&gt; $_val\")\n }\n }\n}\n</code></pre> array_hash_map.rb<pre><code>### キーと値のペア ###\nclass Pair\n attr_accessor :key, :val\n\n def initialize(key, val)\n @key = key\n @val = val\n end\nend\n\n### 配列で実装したハッシュテーブル ###\nclass ArrayHashMap\n ### コンストラクタ ###\n def initialize\n # 100 個のバケットを含む配列を初期化する\n @buckets = Array.new(100)\n end\n\n ### ハッシュ関数 ###\n def hash_func(key)\n index = key % 100\n end\n\n ### 取得操作 ###\n def get(key)\n index = hash_func(key)\n pair = @buckets[index]\n\n return if pair.nil?\n pair.val\n end\n\n ### 追加操作 ###\n def put(key, val)\n pair = Pair.new(key, val)\n index = hash_func(key)\n @buckets[index] = pair\n end\n\n ### 削除操作 ###\n def remove(key)\n index = hash_func(key)\n # nil に設定して、削除を表す\n @buckets[index] = nil\n end\n\n ### キーと値のペアをすべて取得する ###\n def entry_set\n result = []\n @buckets.each { |pair| result &lt;&lt; pair unless pair.nil? }\n result\n end\n\n ### すべてのキーを取得する ###\n def key_set\n result = []\n @buckets.each { |pair| result &lt;&lt; pair.key unless pair.nil? }\n result\n end\n\n ### すべての値を取得する ###\n def value_set\n result = []\n @buckets.each { |pair| result &lt;&lt; pair.val unless pair.nil? }\n result\n end\n\n ### ハッシュテーブルを表示する ###\n def print\n @buckets.each { |pair| puts \"#{pair.key} -&gt; #{pair.val}\" unless pair.nil? }\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 6 章   ハッシュテーブル","6.1   ハッシュテーブル"],"tags":[]},{"location":"chapter_hashing/hash_map/#613","level":2,"title":"6.1.3   ハッシュ衝突と容量拡張","text":"<p>本質的に、ハッシュ関数はすべての <code>key</code> からなる入力空間を、配列のすべてのインデックスからなる出力空間へ写します。入力空間は通常、出力空間よりはるかに大きいため、理論上、複数の入力が同じ出力に対応する場合が必ずあります。</p> <p>上の例では、入力した <code>key</code> の下二桁が同じなら出力も同じです。例えば、学籍番号 12836 と 20336 の学生を照会すると、次のようになります。</p> <pre><code>12836 % 100 = 36\n20336 % 100 = 36\n</code></pre> <p>図 6-3 では、二つの学籍番号が同じ名前を指しており、明らかに正しくありません。このように、複数の入力が同じ出力に対応することをハッシュ衝突(hash collision)と呼びます。</p> <p></p> <p> 図 6-3   ハッシュ衝突の例 </p> <p>容量 \\(n\\) が大きいほど、複数の <code>key</code> が同じバケットに割り当てられる確率は低くなり、衝突が減ります。したがって、ハッシュテーブルの容量を拡張して衝突を減らせます。</p> <p>図 6-4 では、拡張前にペア <code>(136, A)</code> と <code>(236, D)</code> が衝突していますが、拡張後には解消しています。</p> <p></p> <p> 図 6-4   ハッシュテーブルの容量拡張 </p> <p>配列の容量拡張と同様に、すべてのペアを古いハッシュテーブルから新しいものへ移す必要があり、非常に時間がかかります。さらに、<code>capacity</code> が変わるため、すべてのペアの格納位置をハッシュ関数で再計算する必要があり、計算負荷が増えます。そのため、言語の実装では通常、十分な容量をあらかじめ確保して、頻繁な拡張を防ぎます。</p> <p>負荷率(load factor)は重要な概念で、要素数をバケット数で割った値です。衝突の程度を測るために使い、容量拡張を開始する条件にもよく用います。例えば Java では、負荷率が \\(0.75\\) を超えると、容量を元の \\(2\\) 倍に拡張します。</p>","path":["第 6 章   ハッシュテーブル","6.1   ハッシュテーブル"],"tags":[]},{"location":"chapter_hashing/summary/","level":1,"title":"6.4   まとめ","text":"","path":["第 6 章   ハッシュテーブル","6.4   まとめ"],"tags":[]},{"location":"chapter_hashing/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>ハッシュテーブルは、<code>key</code> を入力すると \\(O(1)\\) 時間で <code>value</code> を照会でき、非常に効率的です。</li> <li>主な操作には、照会、キーと値のペアの追加・削除、テーブルの走査などがあります。</li> <li>ハッシュ関数は <code>key</code> を配列のインデックスに写し、対応するバケットにアクセスして <code>value</code> を取得できるようにします。</li> <li>異なる二つの <code>key</code> が同じインデックスになり、誤った照会結果を生む場合があります。これをハッシュ衝突と呼びます。</li> <li>容量が大きいほど衝突の確率が下がるため、拡張によって衝突を緩和できます。ただし、配列と同様、容量拡張の負担は大きいものです。</li> <li>負荷率は要素数をバケット数で割った値で、衝突の程度を反映します。容量拡張を開始する条件によく使います。</li> <li>チェイン法は単一の要素を連結リストに置き換え、衝突した要素を同じリストに格納します。ただし、長すぎると照会効率が下がるため、さらに赤黒木へ変換して改善できます。</li> <li>オープンアドレス法は複数回の探索で衝突に対処します。線形探索法は一定の間隔で探しますが、要素を直接削除できず、クラスタリングが起きやすいという欠点があります。複数のハッシュ関数を使う方法はクラスタリングが起きにくい一方、計算量が増えます。</li> <li>言語ごとに異なる実装を採用しています。例えば Java の <code>HashMap</code> はチェイン法、Python の <code>dict</code> はオープンアドレス法です。</li> <li>ハッシュテーブルのハッシュアルゴリズムには、決定性、高い効率、均等な分布が求められます。暗号分野では、衝突耐性と雪崩効果も必要です。</li> <li>通常は大きな素数を法として、ハッシュ値の分布をできるだけ均等にし、衝突を減らします。</li> <li>主なアルゴリズムには MD5、SHA-1、SHA-2、SHA-3 などがあります。MD5 はファイルの完全性の確認、SHA-2 はセキュリティ用途やプロトコルによく使われます。</li> <li>言語は通常、データ型ごとの組み込みハッシュアルゴリズムを用意し、バケットのインデックスの計算に使います。一般には、不変オブジェクトだけがハッシュ化可能です。</li> </ul>","path":["第 6 章   ハッシュテーブル","6.4   まとめ"],"tags":[]},{"location":"chapter_hashing/summary/#2-q-a","level":3,"title":"2.   Q &amp; A","text":"<p>Q:ハッシュテーブルの時間計算量は、どのような場合に \\(O(n)\\) になりますか?</p> <p>衝突がひどいと、時間計算量は \\(O(n)\\) に悪化します。ハッシュ関数の設計、容量の設定、衝突の分布が適切なら \\(O(1)\\) です。言語の組み込みハッシュテーブルを使う場合、通常は \\(O(1)\\) と考えます。</p> <p>Q:ハッシュ関数を \\(f(x) = x\\) にすれば、衝突しないのではありませんか?</p> <p>\\(f(x) = x\\) なら、各要素は一意のバケットに対応し、配列と同じになります。しかし、入力空間は通常、出力空間である配列の長さよりはるかに大きいため、最後に配列の長さで剰余を取ることが一般的です。つまり、ハッシュテーブルは大きな状態空間を小さな空間へ写し、\\(O(1)\\) の照会効率を提供することを目指します。</p> <p>Q:内部が配列、連結リスト、二分木なのに、なぜそれらより高い効率を得られるのですか?</p> <p>まず、時間効率は上がりますが、空間効率は下がります。ハッシュテーブルでは、かなりのメモリが未使用です。</p> <p>次に、時間効率が上がるのは特定の用途だけです。同じ時間計算量で配列や連結リストを使えるなら、通常はそのほうが速くなります。ハッシュ関数の計算に負担がかかり、定数項が大きいためです。</p> <p>最後に、時間計算量は悪化する可能性があります。例えばチェイン法では、連結リストや赤黒木で探索しており、\\(O(n)\\) 時間に退化するリスクが残ります。</p> <p>Q:複数のハッシュ関数を使う方法でも、要素を直接削除できませんか? 削除済みの場所は再利用できますか?</p> <p>これはオープンアドレス法の一種なので、同じく直接は削除できず、削除済みの印が必要です。その場所は再利用できます。新しい要素を挿入する際、ハッシュ関数で見つけた位置に削除済みの印があれば、そこを使えます。探索する位置の列を保ちながら、領域の利用効率も確保できます。</p> <p>Q:線形探索法では、なぜ要素を探すときにもハッシュ衝突が起きるのですか?</p> <p>ハッシュ関数で対応するバケットとペアに到達しても、<code>key</code> が一致しなければ衝突があるということです。そのため、あらかじめ決めた間隔で後ろへ探し、正しいペアを見つけるか、見つからないと分かるまで続けます。</p> <p>Q:容量を拡張すると、なぜ衝突を緩和できるのですか?</p> <p>ハッシュ関数の最後の手順は通常、配列の長さ \\(n\\) で剰余を取り、出力をインデックスの範囲内に収めることです。拡張後は \\(n\\) が変わるため、<code>key</code> に対応するインデックスも変わる可能性があります。同じバケットにあった複数の <code>key</code> が別々に割り当てられれば、衝突が緩和されます。</p> <p>Q:効率よく読み書きしたいなら、配列を直接使えばよいのではありませんか?</p> <p><code>key</code> が狭い範囲の連続した整数なら、配列を直接使うのが単純で効率的です。ただし、文字列などの型なら、ハッシュ関数でインデックスへ写してから、バケットの配列に要素を格納する必要があります。その構造がハッシュテーブルです。</p>","path":["第 6 章   ハッシュテーブル","6.4   まとめ"],"tags":[]},{"location":"chapter_heap/","level":1,"title":"第 8 章   ヒープ","text":"<p>概要</p> <p>ヒープは山並みのように、幾重にも起伏し、さまざまな姿を見せます。</p> <p>山々の高さはそれぞれ異なりますが、最も高い峰がいつも最初に目に入ります。</p>","path":["第 8 章   ヒープ"],"tags":[]},{"location":"chapter_heap/#_1","level":2,"title":"この章の内容","text":"<ul> <li>8.1   ヒープ</li> <li>8.2   ヒープの構築</li> <li>8.3   Top-k 問題</li> <li>8.4   まとめ</li> <li>8.5   練習問題</li> </ul>","path":["第 8 章   ヒープ"],"tags":[]},{"location":"chapter_heap/build_heap/","level":1,"title":"8.2   ヒープの構築","text":"<p>リストのすべての要素を使ってヒープを作りたい場合があります。この過程を「ヒープの構築」と呼びます。</p>","path":["第 8 章   ヒープ","8.2   ヒープの構築"],"tags":[]},{"location":"chapter_heap/build_heap/#821","level":2,"title":"8.2.1   挿入操作を使う方法","text":"<p>まず空のヒープを作り、リストを走査して各要素を順に挿入します。つまり、要素をヒープの末尾に追加してから、その要素を下から上へヒープ化します。</p> <p>要素を一つ挿入するたびに、ヒープの長さは一つ増えます。ノードが二分木の上から下へ順に追加されるので、ヒープは「上から下へ」構築されます。</p> <p>要素数を \\(n\\) とすると、一要素の挿入に \\(O(\\log{n})\\) 時間かかるため、この構築方法の時間計算量は \\(O(n \\log n)\\) です。</p>","path":["第 8 章   ヒープ","8.2   ヒープの構築"],"tags":[]},{"location":"chapter_heap/build_heap/#822","level":2,"title":"8.2.2   走査しながらヒープ化する方法","text":"<p>実は、次の二つの手順で、より効率よくヒープを構築できます。</p> <ol> <li>リストの全要素をそのままヒープへ追加します。この時点では、まだヒープの性質を満たしていません。</li> <li>ヒープを逆順、つまりレベル順走査の逆順に走査し、葉以外の各ノードを順に上から下へヒープ化します。</li> </ol> <p>一つのノードをヒープ化するたびに、そのノードを根とする部分木が正しい部分ヒープになります。逆順に走査するため、ヒープは「下から上へ」構築されます。</p> <p>逆順に走査するのは、現在のノードの下にある部分木がすでに正しい部分ヒープであることを保証し、現在のノードのヒープ化を有効にするためです。</p> <p>葉ノードには子がないため、それ自体が正しい部分ヒープであり、ヒープ化は不要です。次のコードのように、葉以外の最後のノードは最後のノードの親です。そこから逆順に走査してヒープ化します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py<pre><code>def __init__(self, nums: list[int]):\n \"\"\"コンストラクタ:入力リストからヒープを構築する\"\"\"\n # リストの要素をそのままヒープに追加する\n self.max_heap = nums\n # 葉ノード以外の全ノードをヒープ化する\n for i in range(self.parent(self.size() - 1), -1, -1):\n self.sift_down(i)\n</code></pre> my_heap.cpp<pre><code>/* コンストラクタ:入力リストからヒープを構築する */\nMaxHeap(vector&lt;int&gt; nums) {\n // リストの要素をそのままヒープに追加する\n maxHeap = nums;\n // 葉ノード以外の全ノードをヒープ化する\n for (int i = parent(size() - 1); i &gt;= 0; i--) {\n siftDown(i);\n }\n}\n</code></pre> my_heap.java<pre><code>/* コンストラクタ:入力リストからヒープを構築する */\nMaxHeap(List&lt;Integer&gt; nums) {\n // リストの要素をそのままヒープに追加する\n maxHeap = new ArrayList&lt;&gt;(nums);\n // 葉ノード以外の全ノードをヒープ化する\n for (int i = parent(size() - 1); i &gt;= 0; i--) {\n siftDown(i);\n }\n}\n</code></pre> my_heap.cs<pre><code>/* コンストラクタ:入力リストからヒープを構築する */\nMaxHeap(IEnumerable&lt;int&gt; nums) {\n // リストの要素をそのままヒープに追加する\n maxHeap = new List&lt;int&gt;(nums);\n // 葉ノード以外の全ノードをヒープ化する\n var size = Parent(this.Size() - 1);\n for (int i = size; i &gt;= 0; i--) {\n SiftDown(i);\n }\n}\n</code></pre> my_heap.go<pre><code>/* コンストラクタ:スライスからヒープを構築する */\nfunc newMaxHeap(nums []any) *maxHeap {\n // リストの要素をそのままヒープに追加する\n h := &amp;maxHeap{data: nums}\n for i := h.parent(len(h.data) - 1); i &gt;= 0; i-- {\n // 葉ノード以外の全ノードをヒープ化する\n h.siftDown(i)\n }\n return h\n}\n</code></pre> my_heap.swift<pre><code>/* コンストラクタ:入力リストからヒープを構築する */\ninit(nums: [Int]) {\n // リストの要素をそのままヒープに追加する\n maxHeap = nums\n // 葉ノード以外の全ノードをヒープ化する\n for i in (0 ... parent(i: size() - 1)).reversed() {\n siftDown(i: i)\n }\n}\n</code></pre> my_heap.js<pre><code>/* コンストラクタ:空のヒープを構築するか、入力リストからヒープを構築する */\nconstructor(nums) {\n // リストの要素をそのままヒープに追加する\n this.#maxHeap = nums === undefined ? [] : [...nums];\n // 葉ノード以外の全ノードをヒープ化する\n for (let i = this.#parent(this.size() - 1); i &gt;= 0; i--) {\n this.#siftDown(i);\n }\n}\n</code></pre> my_heap.ts<pre><code>/* コンストラクタ:空のヒープを構築するか、入力リストからヒープを構築する */\nconstructor(nums?: number[]) {\n // リストの要素をそのままヒープに追加する\n this.maxHeap = nums === undefined ? [] : [...nums];\n // 葉ノード以外の全ノードをヒープ化する\n for (let i = this.parent(this.size() - 1); i &gt;= 0; i--) {\n this.siftDown(i);\n }\n}\n</code></pre> my_heap.dart<pre><code>/* コンストラクタ:入力リストからヒープを構築する */\nMaxHeap(List&lt;int&gt; nums) {\n // リストの要素をそのままヒープに追加する\n _maxHeap = nums;\n // 葉ノード以外の全ノードをヒープ化する\n for (int i = _parent(size() - 1); i &gt;= 0; i--) {\n siftDown(i);\n }\n}\n</code></pre> my_heap.rs<pre><code>/* コンストラクタ:入力リストからヒープを構築する */\nfn new(nums: Vec&lt;i32&gt;) -&gt; Self {\n // リストの要素をそのままヒープに追加する\n let mut heap = MaxHeap { max_heap: nums };\n // 葉ノード以外の全ノードをヒープ化する\n for i in (0..=Self::parent(heap.size() - 1)).rev() {\n heap.sift_down(i);\n }\n heap\n}\n</code></pre> my_heap.c<pre><code>/* コンストラクタ:スライスからヒープを構築する */\nMaxHeap *newMaxHeap(int nums[], int size) {\n // すべての要素をヒープに挿入する\n MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap));\n maxHeap-&gt;size = size;\n memcpy(maxHeap-&gt;data, nums, size * sizeof(int));\n for (int i = parent(maxHeap, size - 1); i &gt;= 0; i--) {\n // 葉ノード以外の全ノードをヒープ化する\n siftDown(maxHeap, i);\n }\n return maxHeap;\n}\n</code></pre> my_heap.kt<pre><code>/* 最大ヒープ */\nclass MaxHeap(nums: MutableList&lt;Int&gt;?) {\n // 配列ではなくリストを使うため、容量の拡張を考慮する必要がない\n private val maxHeap = mutableListOf&lt;Int&gt;()\n\n /* コンストラクタ:入力リストからヒープを構築する */\n init {\n // リストの要素をそのままヒープに追加する\n maxHeap.addAll(nums!!)\n // 葉ノード以外の全ノードをヒープ化する\n for (i in parent(size() - 1) downTo 0) {\n siftDown(i)\n }\n }\n\n /* 左の子のインデックスを取得 */\n private fun left(i: Int): Int {\n return 2 * i + 1\n }\n\n /* 右の子のインデックスを取得 */\n private fun right(i: Int): Int {\n return 2 * i + 2\n }\n\n /* 親のインデックスを取得 */\n private fun parent(i: Int): Int {\n return (i - 1) / 2 // 切り捨て除算\n }\n\n /* 要素を交換する */\n private fun swap(i: Int, j: Int) {\n val temp = maxHeap[i]\n maxHeap[i] = maxHeap[j]\n maxHeap[j] = temp\n }\n\n /* ヒープのサイズを取得する */\n fun size(): Int {\n return maxHeap.size\n }\n\n /* ヒープが空か判定 */\n fun isEmpty(): Boolean {\n /* ヒープが空か判定 */\n return size() == 0\n }\n\n /* ヒープの先頭要素にアクセスする */\n fun peek(): Int {\n return maxHeap[0]\n }\n\n /* ヒープに要素を挿入する */\n fun push(_val: Int) {\n // ノードを追加する\n maxHeap.add(_val)\n // 下から上へヒープ化する\n siftUp(size() - 1)\n }\n\n /* ノード i から上向きにヒープ化する */\n private fun siftUp(it: Int) {\n // Kotlin の関数の引数は変更できないため、一時変数を作成する\n var i = it\n while (true) {\n // ノード i の親ノードを取得する\n val p = parent(i)\n // 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if (p &lt; 0 || maxHeap[i] &lt;= maxHeap[p]) break\n // 2 つのノードを交換する\n swap(i, p)\n // 上向きのヒープ化を繰り返す\n i = p\n }\n }\n\n /* ヒープから要素を取り出す */\n fun pop(): Int {\n // 空かどうかを確認する\n if (isEmpty()) throw IndexOutOfBoundsException()\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n swap(0, size() - 1)\n // ノードを削除する\n val _val = maxHeap.removeAt(size() - 1)\n // 上から下へヒープ化する\n siftDown(0)\n // ヒープの先頭要素を返す\n return _val\n }\n\n /* ノード i から下向きにヒープ化する */\n private fun siftDown(it: Int) {\n // Kotlin の関数の引数は変更できないため、一時変数を作成する\n var i = it\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n val l = left(i)\n val r = right(i)\n var ma = i\n if (l &lt; size() &amp;&amp; maxHeap[l] &gt; maxHeap[ma]) ma = l\n if (r &lt; size() &amp;&amp; maxHeap[r] &gt; maxHeap[ma]) ma = r\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma == i) break\n // 2 つのノードを交換する\n swap(i, ma)\n // 下向きのヒープ化を繰り返す\n i = ma\n }\n }\n\n /* ヒープ(二分木)を表示する */\n fun print() {\n val queue = PriorityQueue { a: Int, b: Int -&gt; b - a }\n queue.addAll(maxHeap)\n printHeap(queue)\n }\n}\n</code></pre> my_heap.rb<pre><code>### コンストラクタ:入力リストからヒープを構築する ###\ndef initialize(nums)\n # リストの要素をそのままヒープに追加する\n @max_heap = nums\n # 葉ノード以外の全ノードをヒープ化する\n parent(size - 1).downto(0) do |i|\n sift_down(i)\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 8 章   ヒープ","8.2   ヒープの構築"],"tags":[]},{"location":"chapter_heap/build_heap/#823","level":2,"title":"8.2.3   計算量の解析","text":"<p>二つ目の構築方法の時間計算量を求めてみましょう。</p> <ul> <li>完全二分木のノード数を \\(n\\) とすると、葉ノード数は \\((n + 1) / 2\\) です。ここで \\(/\\) は切り捨ての整数除算です。したがって、ヒープ化が必要なノード数は \\(n / 2\\) です。</li> <li>上から下へのヒープ化では、各ノードは最大でも葉まで移動するため、反復回数の最大値は木の高さ \\(\\log n\\) です。</li> </ul> <p>両者を掛けると、構築の時間計算量は \\(O(n \\log n)\\) となります。ただし、この見積もりは正確ではありません。木の下の層には上の層よりはるかに多くのノードがあることを考慮していないためです。</p> <p>より正確に計算しましょう。計算を簡単にするため、ノード数 \\(n\\)、高さ \\(h\\) のパーフェクト二分木を仮定します。この仮定は結果の正しさに影響しません。</p> <p></p> <p> 図 8-5   パーフェクト二分木の各層のノード数 </p> <p>図 8-5 のように、上から下へヒープ化するときの最大反復回数は、そのノードから葉までの距離、つまりノードの高さです。各層の「ノード数 \\(\\times\\) ノードの高さ」を足せば、すべてのノードのヒープ化の反復回数の合計が得られます。</p> \\[ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \\dots + 2^{(h-1)}\\times1 \\] <p>この式の整理には、学校で学ぶ数列の知識を使います。まず \\(T(h)\\) を \\(2\\) 倍します。</p> \\[ \\begin{aligned} T(h) &amp; = 2^0h + 2^1(h-1) + 2^2(h-2) + \\dots + 2^{h-1}\\times1 \\newline 2 T(h) &amp; = 2^1h + 2^2(h-1) + 2^3(h-2) + \\dots + 2^{h}\\times1 \\newline \\end{aligned} \\] <p>項をずらして引き算し、下の式 \\(2 T(h)\\) から上の式 \\(T(h)\\) を引くと、次のようになります。</p> \\[ 2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \\dots + 2^{h-1} + 2^h \\] <p>この式の \\(T(h)\\) には等比数列が現れるので、和の公式を使うと時間計算量は次のようになります。</p> \\[ \\begin{aligned} T(h) &amp; = 2 \\frac{1 - 2^h}{1 - 2} - h \\newline &amp; = 2^{h+1} - h - 2 \\newline &amp; = O(2^h) \\end{aligned} \\] <p>さらに、高さ \\(h\\) のパーフェクト二分木のノード数は \\(n = 2^{h+1} - 1\\) なので、\\(O(2^h) = O(n)\\) と分かります。したがって、リストを入力してヒープを構築する時間計算量は \\(O(n)\\) で、非常に効率的です。</p>","path":["第 8 章   ヒープ","8.2   ヒープの構築"],"tags":[]},{"location":"chapter_heap/exercises/","level":1,"title":"8.5   練習問題","text":"","path":["第 8 章   ヒープ","8.5   練習問題"],"tags":[]},{"location":"chapter_heap/exercises/#851","level":2,"title":"8.5.1   知識の確認","text":"","path":["第 8 章   ヒープ","8.5   練習問題"],"tags":[]},{"location":"chapter_heap/exercises/#1-10","level":3,"title":"1.   10 をヒープに挿入した後の調整","text":"<p>配列 <code>[9, 7, 8, 3, 5]</code> は最大ヒープを表します。このヒープに 10 を挿入します。</p> <ol> <li>まず 10 を配列の末尾に追加すると、その親ノードの値はいくつですか。</li> <li>新しいノードから上方向にヒープ化します。各交換後の配列を書いてください。</li> <li>最終的なヒープの先頭要素はいくつですか。交換は全部で何回行いましたか。</li> </ol> 解答例 <ol> <li> <p>追加した 10 のインデックスは 5、その親のインデックスは \\(\\lfloor(5-1)/2\\rfloor=2\\) なので、親の値は 8 です。</p> </li> <li> <p>10 は 8 より大きいため、一回目の交換後は <code>[9, 7, 10, 3, 5, 8]</code> となります。 10 は親の 9 よりも大きいため、二回目の交換後は <code>[10, 7, 9, 3, 5, 8]</code> となります。 ここで 10 が根に達したので、ヒープ化を終えます。</p> </li> <li> <p>最終的なヒープの先頭は 10 で、交換回数は 2 回です。</p> </li> </ol>","path":["第 8 章   ヒープ","8.5   練習問題"],"tags":[]},{"location":"chapter_heap/exercises/#2","level":3,"title":"2.   最小ヒープの親子関係を確認する","text":"<p>配列 <code>[1, 4, 3, 7, 6, 2]</code> は完全二分木を表します。最小ヒープでは、各親ノードの値がその子以下である必要があります。 インデックス \\(i\\) に対し、左右の子のインデックスはそれぞれ \\(2i+1\\) と \\(2i+2\\) です。</p> <ol> <li>インデックス 2 の子のインデックスと値を、それぞれ答えてください。</li> <li>インデックス 2 のノードの値は 3 です。子との関係は最小ヒープの規則に違反していますか。違反していれば、どの二つの要素を交換しますか。</li> <li>問い 2 で違反すると判断した場合は交換後の配列を書き、違反しない場合は交換が不要だと説明してください。最後に、残りの親子関係を確認してください。</li> </ol> 解答例 <ol> <li> <p>インデックス 2 の左の子はインデックス 5 で、値は 2 です。右の子のインデックスは 6 ですが、配列の長さが 6 なので、右の子はありません。</p> </li> <li> <p>親の値 3 が子の値 2 より大きく、最小ヒープの規則に違反するため、インデックス 2 と 5 の要素を交換します。</p> </li> <li> <p>交換後は <code>[1, 4, 2, 7, 6, 3]</code> です。一つずつ確認すると、 <code>1 ≤ 4</code>、<code>1 ≤ 2</code>、<code>4 ≤ 7</code>、<code>4 ≤ 6</code>、<code>2 ≤ 3</code> が成り立ちます。 すべての親が自分の子以下なので、最小ヒープの規則を満たしています。</p> </li> </ol>","path":["第 8 章   ヒープ","8.5   練習問題"],"tags":[]},{"location":"chapter_heap/exercises/#3","level":3,"title":"3.   最小ヒープで大きいほうから三つの数を保持する","text":"<p>データストリーム <code>[4, 1, 7, 3, 8]</code> から大きいほうから 3 個の数を保持するには、要素数が 3 以下の最小ヒープを使えます。</p> <p>まず最初の 3 個を順に最小ヒープへ入れます。ヒープがいっぱいになったら、新しい数を読み込むたびに、 先頭より大きければ先頭を削除して新しい数を入れ、そうでなければ変更しません。</p> <p>各数を読み込んだ後に、ヒープが保持する数と先頭の値を書いてください。 保持する数は集合で示せばよく、ヒープ配列内の並びを書く必要はありません。</p> 解答例 <p>各数を読み込んだ後の結果は、次のとおりです。</p> 読み込んだ数 保持する数 ヒープの先頭 4 <code>{4}</code> 4 1 <code>{1, 4}</code> 1 7 <code>{1, 4, 7}</code> 1 3 <code>{3, 4, 7}</code> 3 8 <code>{4, 7, 8}</code> 4 <p>ヒープがいっぱいになると、先頭は現在保持している数の最小値です。新しい数が先頭より大きい場合にだけ、 先頭を新しい数で置き換えます。最後に残る <code>{4, 7, 8}</code> が、大きいほうから 3 個の数です。</p>","path":["第 8 章   ヒープ","8.5   練習問題"],"tags":[]},{"location":"chapter_heap/exercises/#852","level":2,"title":"8.5.2   プログラミング演習","text":"","path":["第 8 章   ヒープ","8.5   練習問題"],"tags":[]},{"location":"chapter_heap/exercises/#1-k","level":3,"title":"1.   配列の k 番目に大きい要素を探す","text":"<p>整数配列 <code>nums</code> と整数 \\(k\\) が与えられます。\\(1 \\le k \\le n\\) を満たし、\\(n\\) は配列の長さです。配列を降順に並べたとき、\\(k\\) 番目の要素を返してください。</p> <p>重複する要素も別々に数えます。例えば、<code>[5, 5, 2]</code> の 2 番目に大きい要素も 5 です。要素数が \\(k\\) 以下の最小ヒープを使って解いてください。</p> 解くためのヒント <ol> <li>k 番目に大きい要素は、大きいほうから k 個の数の中で最小のものです</li> <li>各数をまず最小ヒープに挿入し、要素数が k を超えたら最小値を取り出します</li> <li>走査が終わると、ヒープには大きいほうから k 個の数が残り、先頭が答えになります</li> </ol> <p>LeetCode</p>","path":["第 8 章   ヒープ","8.5   練習問題"],"tags":[]},{"location":"chapter_heap/heap/","level":1,"title":"8.1   ヒープ","text":"<p>ヒープ(heap)は特定の条件を満たす完全二分木です。図 8-1 のように、主に二種類に分かれます。</p> <ul> <li>最小ヒープ(min heap):任意のノードの値が、その子の値以下(\\(\\leq\\))です。</li> <li>最大ヒープ(max heap):任意のノードの値が、その子の値以上(\\(\\geq\\))です。</li> </ul> <p></p> <p> 図 8-1   最小ヒープと最大ヒープ </p> <p>ヒープは完全二分木の特殊な場合なので、次の特徴を持ちます。</p> <ul> <li>最下層のノードは左詰めで、ほかの層はすべて埋まっています。</li> <li>根ノードを「ヒープの先頭」、最下層の一番右のノードを「ヒープの末尾」と呼びます。</li> <li>最大ヒープ(最小ヒープ)では、先頭要素、つまり根ノードの値が最大(最小)です。</li> </ul>","path":["第 8 章   ヒープ","8.1   ヒープ"],"tags":[]},{"location":"chapter_heap/heap/#811","level":2,"title":"8.1.1   ヒープの主な操作","text":"<p>多くのプログラミング言語が提供するのは、優先度付きキュー(priority queue)です。これは、優先度に基づいて順序付けられるキューとして定義される抽象データ構造です。</p> <p>実際には、ヒープは優先度付きキューの実装によく使われ、最大ヒープは大きい要素から順に取り出す優先度付きキューに相当します。使う側からは、両者を同等のデータ構造と見なせます。そのため本書では特に区別せず、「ヒープ」と呼びます。</p> <p>主な操作を表 8-1 に示します。メソッド名は言語によって異なります。</p> <p> 表 8-1   ヒープの操作効率 </p> メソッド名 説明 時間計算量 <code>push()</code> 要素をヒープに挿入する \\(O(\\log n)\\) <code>pop()</code> ヒープの先頭要素を取り出す \\(O(\\log n)\\) <code>peek()</code> 先頭要素にアクセスする(最大/最小ヒープでは最大値/最小値) \\(O(1)\\) <code>size()</code> ヒープの要素数を取得する \\(O(1)\\) <code>isEmpty()</code> ヒープが空かどうかを判定する \\(O(1)\\) <p>実際には、言語が提供するヒープクラスや優先度付きキューのクラスをそのまま使えます。</p> <p>ソートで昇順と降順を切り替えるように、<code>flag</code> の設定や <code>Comparator</code> の変更によって、最小ヒープと最大ヒープを切り替えられます。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby heap.py<pre><code># 最小ヒープを初期化\nmin_heap, flag = [], 1\n# 最大ヒープを初期化\nmax_heap, flag = [], -1\n\n# Python の heapq モジュールはデフォルトで最小ヒープを実装する\n# 要素の符号を反転してから挿入すると、大小関係が逆転し最大ヒープを実現できる\n# この例では flag = 1 が最小ヒープ、flag = -1 が最大ヒープに対応する\n\n# 要素をヒープに挿入\nheapq.heappush(max_heap, flag * 1)\nheapq.heappush(max_heap, flag * 3)\nheapq.heappush(max_heap, flag * 2)\nheapq.heappush(max_heap, flag * 5)\nheapq.heappush(max_heap, flag * 4)\n\n# ヒープの先頭要素を取得\npeek: int = flag * max_heap[0] # 5\n\n# ヒープの先頭要素を取り出す\n# 取り出した要素は降順の列になる\nval = flag * heapq.heappop(max_heap) # 5\nval = flag * heapq.heappop(max_heap) # 4\nval = flag * heapq.heappop(max_heap) # 3\nval = flag * heapq.heappop(max_heap) # 2\nval = flag * heapq.heappop(max_heap) # 1\n\n# ヒープの要素数を取得\nsize: int = len(max_heap)\n\n# ヒープが空かどうかを判定\nis_empty: bool = not max_heap\n\n# リストを入力してヒープを構築\nmin_heap: list[int] = [1, 3, 2, 5, 4]\nheapq.heapify(min_heap)\n</code></pre> heap.cpp<pre><code>/* ヒープを初期化 */\n// 最小ヒープを初期化\npriority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; minHeap;\n// 最大ヒープを初期化\npriority_queue&lt;int, vector&lt;int&gt;, less&lt;int&gt;&gt; maxHeap;\n\n/* 要素をヒープに挿入 */\nmaxHeap.push(1);\nmaxHeap.push(3);\nmaxHeap.push(2);\nmaxHeap.push(5);\nmaxHeap.push(4);\n\n/* ヒープの先頭要素を取得 */\nint peek = maxHeap.top(); // 5\n\n/* ヒープの先頭要素を取り出す */\n// 取り出した要素は降順の列になる\nmaxHeap.pop(); // 5\nmaxHeap.pop(); // 4\nmaxHeap.pop(); // 3\nmaxHeap.pop(); // 2\nmaxHeap.pop(); // 1\n\n/* ヒープの要素数を取得 */\nint size = maxHeap.size();\n\n/* ヒープが空かどうかを判定 */\nbool isEmpty = maxHeap.empty();\n\n/* リストを入力してヒープを構築 */\nvector&lt;int&gt; input{1, 3, 2, 5, 4};\npriority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; minHeap(input.begin(), input.end());\n</code></pre> heap.java<pre><code>/* ヒープを初期化 */\n// 最小ヒープを初期化\nQueue&lt;Integer&gt; minHeap = new PriorityQueue&lt;&gt;();\n// 最大ヒープを初期化(ラムダ式で Comparator を変更する)\nQueue&lt;Integer&gt; maxHeap = new PriorityQueue&lt;&gt;((a, b) -&gt; b - a);\n\n/* 要素をヒープに挿入 */\nmaxHeap.offer(1);\nmaxHeap.offer(3);\nmaxHeap.offer(2);\nmaxHeap.offer(5);\nmaxHeap.offer(4);\n\n/* ヒープの先頭要素を取得 */\nint peek = maxHeap.peek(); // 5\n\n/* ヒープの先頭要素を取り出す */\n// 取り出した要素は降順の列になる\npeek = maxHeap.poll(); // 5\npeek = maxHeap.poll(); // 4\npeek = maxHeap.poll(); // 3\npeek = maxHeap.poll(); // 2\npeek = maxHeap.poll(); // 1\n\n/* ヒープの要素数を取得 */\nint size = maxHeap.size();\n\n/* ヒープが空かどうかを判定 */\nboolean isEmpty = maxHeap.isEmpty();\n\n/* リストを入力してヒープを構築 */\nminHeap = new PriorityQueue&lt;&gt;(Arrays.asList(1, 3, 2, 5, 4));\n</code></pre> heap.cs<pre><code>/* ヒープを初期化 */\n// 最小ヒープを初期化\nPriorityQueue&lt;int, int&gt; minHeap = new();\n// 最大ヒープを初期化(ラムダ式で Comparer を変更する)\nPriorityQueue&lt;int, int&gt; maxHeap = new(Comparer&lt;int&gt;.Create((x, y) =&gt; y.CompareTo(x)));\n\n/* 要素をヒープに挿入 */\nmaxHeap.Enqueue(1, 1);\nmaxHeap.Enqueue(3, 3);\nmaxHeap.Enqueue(2, 2);\nmaxHeap.Enqueue(5, 5);\nmaxHeap.Enqueue(4, 4);\n\n/* ヒープの先頭要素を取得 */\nint peek = maxHeap.Peek();//5\n\n/* ヒープの先頭要素を取り出す */\n// 取り出した要素は降順の列になる\npeek = maxHeap.Dequeue(); // 5\npeek = maxHeap.Dequeue(); // 4\npeek = maxHeap.Dequeue(); // 3\npeek = maxHeap.Dequeue(); // 2\npeek = maxHeap.Dequeue(); // 1\n\n/* ヒープの要素数を取得 */\nint size = maxHeap.Count;\n\n/* ヒープが空かどうかを判定 */\nbool isEmpty = maxHeap.Count == 0;\n\n/* リストを入力してヒープを構築 */\nminHeap = new PriorityQueue&lt;int, int&gt;([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]);\n</code></pre> heap.go<pre><code>// Go では heap.Interface を実装して整数の最大ヒープを構築できる\n// heap.Interface の実装には sort.Interface の実装も必要\ntype intHeap []any\n\n// Push は heap.Interface のメソッドで、ヒープへの挿入を実装する\nfunc (h *intHeap) Push(x any) {\n // Push と Pop はポインタレシーバーを引数として使う\n // スライスの内容だけでなく長さも変更するため\n *h = append(*h, x.(int))\n}\n\n// Pop は heap.Interface のメソッドで、先頭要素の取り出しを実装する\nfunc (h *intHeap) Pop() any {\n // 取り出す要素は末尾に格納されている\n last := (*h)[len(*h)-1]\n *h = (*h)[:len(*h)-1]\n return last\n}\n\n// Len は sort.Interface のメソッド\nfunc (h *intHeap) Len() int {\n return len(*h)\n}\n\n// Less は sort.Interface のメソッド\nfunc (h *intHeap) Less(i, j int) bool {\n // 最小ヒープを実装する場合は、小なり記号に変更する\n return (*h)[i].(int) &gt; (*h)[j].(int)\n}\n\n// Swap は sort.Interface のメソッド\nfunc (h *intHeap) Swap(i, j int) {\n (*h)[i], (*h)[j] = (*h)[j], (*h)[i]\n}\n\n// Top ヒープの先頭要素を取得\nfunc (h *intHeap) Top() any {\n return (*h)[0]\n}\n\n/* 実行用コード */\nfunc TestHeap(t *testing.T) {\n /* ヒープを初期化 */\n // 最大ヒープを初期化\n maxHeap := &amp;intHeap{}\n heap.Init(maxHeap)\n /* 要素をヒープに挿入 */\n // heap.Interface のメソッドを呼んで要素を追加\n heap.Push(maxHeap, 1)\n heap.Push(maxHeap, 3)\n heap.Push(maxHeap, 2)\n heap.Push(maxHeap, 4)\n heap.Push(maxHeap, 5)\n\n /* ヒープの先頭要素を取得 */\n top := maxHeap.Top()\n fmt.Printf(\"ヒープの先頭要素は %d\\n\", top)\n\n /* ヒープの先頭要素を取り出す */\n // heap.Interface のメソッドを呼んで要素を削除\n heap.Pop(maxHeap) // 5\n heap.Pop(maxHeap) // 4\n heap.Pop(maxHeap) // 3\n heap.Pop(maxHeap) // 2\n heap.Pop(maxHeap) // 1\n\n /* ヒープの要素数を取得 */\n size := len(*maxHeap)\n fmt.Printf(\"ヒープの要素数は %d\\n\", size)\n\n /* ヒープが空かどうかを判定 */\n isEmpty := len(*maxHeap) == 0\n fmt.Printf(\"ヒープは空か %t\\n\", isEmpty)\n}\n</code></pre> heap.swift<pre><code>/* ヒープを初期化 */\n// Swift の Heap 型は最大ヒープと最小ヒープの両方に対応し、swift-collections の導入が必要\nvar heap = Heap&lt;Int&gt;()\n\n/* 要素をヒープに挿入 */\nheap.insert(1)\nheap.insert(3)\nheap.insert(2)\nheap.insert(5)\nheap.insert(4)\n\n/* ヒープの先頭要素を取得 */\nvar peek = heap.max()!\n\n/* ヒープの先頭要素を取り出す */\npeek = heap.removeMax() // 5\npeek = heap.removeMax() // 4\npeek = heap.removeMax() // 3\npeek = heap.removeMax() // 2\npeek = heap.removeMax() // 1\n\n/* ヒープの要素数を取得 */\nlet size = heap.count\n\n/* ヒープが空かどうかを判定 */\nlet isEmpty = heap.isEmpty\n\n/* リストを入力してヒープを構築 */\nlet heap2 = Heap([1, 3, 2, 5, 4])\n</code></pre> heap.js<pre><code>// JavaScript には組み込みの Heap クラスがない\n</code></pre> heap.ts<pre><code>// TypeScript には組み込みの Heap クラスがない\n</code></pre> heap.dart<pre><code>// Dart には組み込みの Heap クラスがない\n</code></pre> heap.rs<pre><code>use std::collections::BinaryHeap;\nuse std::cmp::Reverse;\n\n/* ヒープを初期化 */\n// 最小ヒープを初期化\nlet mut min_heap = BinaryHeap::&lt;Reverse&lt;i32&gt;&gt;::new();\n// 最大ヒープを初期化\nlet mut max_heap = BinaryHeap::new();\n\n/* 要素をヒープに挿入 */\nmax_heap.push(1);\nmax_heap.push(3);\nmax_heap.push(2);\nmax_heap.push(5);\nmax_heap.push(4);\n\n/* ヒープの先頭要素を取得 */\nlet peek = max_heap.peek().unwrap(); // 5\n\n/* ヒープの先頭要素を取り出す */\n// 取り出した要素は降順の列になる\nlet peek = max_heap.pop().unwrap(); // 5\nlet peek = max_heap.pop().unwrap(); // 4\nlet peek = max_heap.pop().unwrap(); // 3\nlet peek = max_heap.pop().unwrap(); // 2\nlet peek = max_heap.pop().unwrap(); // 1\n\n/* ヒープの要素数を取得 */\nlet size = max_heap.len();\n\n/* ヒープが空かどうかを判定 */\nlet is_empty = max_heap.is_empty();\n\n/* リストを入力してヒープを構築 */\nlet min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]);\n</code></pre> heap.c<pre><code>// C には組み込みの Heap クラスがない\n</code></pre> heap.kt<pre><code>/* ヒープを初期化 */\n// 最小ヒープを初期化\nvar minHeap = PriorityQueue&lt;Int&gt;()\n// 最大ヒープを初期化(ラムダ式で Comparator を変更する)\nval maxHeap = PriorityQueue { a: Int, b: Int -&gt; b - a }\n\n/* 要素をヒープに挿入 */\nmaxHeap.offer(1)\nmaxHeap.offer(3)\nmaxHeap.offer(2)\nmaxHeap.offer(5)\nmaxHeap.offer(4)\n\n/* ヒープの先頭要素を取得 */\nvar peek = maxHeap.peek() // 5\n\n/* ヒープの先頭要素を取り出す */\n// 取り出した要素は降順の列になる\npeek = maxHeap.poll() // 5\npeek = maxHeap.poll() // 4\npeek = maxHeap.poll() // 3\npeek = maxHeap.poll() // 2\npeek = maxHeap.poll() // 1\n\n/* ヒープの要素数を取得 */\nval size = maxHeap.size\n\n/* ヒープが空かどうかを判定 */\nval isEmpty = maxHeap.isEmpty()\n\n/* リストを入力してヒープを構築 */\nminHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4))\n</code></pre> heap.rb<pre><code># Ruby には組み込みの Heap クラスがない\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 8 章   ヒープ","8.1   ヒープ"],"tags":[]},{"location":"chapter_heap/heap/#812","level":2,"title":"8.1.2   ヒープの実装","text":"<p>以下では最大ヒープを実装します。最小ヒープにするには、大小の判定をすべて逆にするだけです(例えば、\\(\\geq\\) を \\(\\leq\\) に置き換えます)。興味があれば実装してみてください。</p>","path":["第 8 章   ヒープ","8.1   ヒープ"],"tags":[]},{"location":"chapter_heap/heap/#1","level":3,"title":"1.   ヒープの格納と表現","text":"<p>「二分木」の章で、完全二分木は配列で表すのに適していると学びました。ヒープも完全二分木なので、配列に格納します。</p> <p>配列で二分木を表すとき、要素はノードの値、インデックスは木の中での位置を表します。ノードのポインタは、インデックスの対応式で実現します。</p> <p>図 8-2 のように、インデックス \\(i\\) の左の子は \\(2i + 1\\)、右の子は \\(2i + 2\\)、親は \\((i - 1) / 2\\)(切り捨ての整数除算)です。インデックスが範囲外なら、空のノード、またはノードが存在しないことを表します。</p> <p></p> <p> 図 8-2   ヒープの表現と格納 </p> <p>この対応式を関数にまとめておくと、後で使いやすくなります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py<pre><code>def left(self, i: int) -&gt; int:\n \"\"\"左の子のインデックスを取得\"\"\"\n return 2 * i + 1\n\ndef right(self, i: int) -&gt; int:\n \"\"\"右の子のインデックスを取得\"\"\"\n return 2 * i + 2\n\ndef parent(self, i: int) -&gt; int:\n \"\"\"親のインデックスを取得\"\"\"\n return (i - 1) // 2 # 切り捨て除算\n</code></pre> my_heap.cpp<pre><code>/* 左の子のインデックスを取得 */\nint left(int i) {\n return 2 * i + 1;\n}\n\n/* 右の子のインデックスを取得 */\nint right(int i) {\n return 2 * i + 2;\n}\n\n/* 親のインデックスを取得 */\nint parent(int i) {\n return (i - 1) / 2; // 切り捨て除算\n}\n</code></pre> my_heap.java<pre><code>/* 左の子のインデックスを取得 */\nint left(int i) {\n return 2 * i + 1;\n}\n\n/* 右の子のインデックスを取得 */\nint right(int i) {\n return 2 * i + 2;\n}\n\n/* 親のインデックスを取得 */\nint parent(int i) {\n return (i - 1) / 2; // 切り捨て除算\n}\n</code></pre> my_heap.cs<pre><code>/* 左の子のインデックスを取得 */\nint Left(int i) {\n return 2 * i + 1;\n}\n\n/* 右の子のインデックスを取得 */\nint Right(int i) {\n return 2 * i + 2;\n}\n\n/* 親のインデックスを取得 */\nint Parent(int i) {\n return (i - 1) / 2; // 切り捨て除算\n}\n</code></pre> my_heap.go<pre><code>/* 左の子のインデックスを取得 */\nfunc (h *maxHeap) left(i int) int {\n return 2*i + 1\n}\n\n/* 右の子のインデックスを取得 */\nfunc (h *maxHeap) right(i int) int {\n return 2*i + 2\n}\n\n/* 親のインデックスを取得 */\nfunc (h *maxHeap) parent(i int) int {\n // 切り捨て除算\n return (i - 1) / 2\n}\n</code></pre> my_heap.swift<pre><code>/* 左の子のインデックスを取得 */\nfunc left(i: Int) -&gt; Int {\n 2 * i + 1\n}\n\n/* 右の子のインデックスを取得 */\nfunc right(i: Int) -&gt; Int {\n 2 * i + 2\n}\n\n/* 親のインデックスを取得 */\nfunc parent(i: Int) -&gt; Int {\n (i - 1) / 2 // 切り捨て除算\n}\n</code></pre> my_heap.js<pre><code>/* 左の子のインデックスを取得 */\n#left(i) {\n return 2 * i + 1;\n}\n\n/* 右の子のインデックスを取得 */\n#right(i) {\n return 2 * i + 2;\n}\n\n/* 親のインデックスを取得 */\n#parent(i) {\n return Math.floor((i - 1) / 2); // 切り捨て除算\n}\n</code></pre> my_heap.ts<pre><code>/* 左の子のインデックスを取得 */\nleft(i: number): number {\n return 2 * i + 1;\n}\n\n/* 右の子のインデックスを取得 */\nright(i: number): number {\n return 2 * i + 2;\n}\n\n/* 親のインデックスを取得 */\nparent(i: number): number {\n return Math.floor((i - 1) / 2); // 切り捨て除算\n}\n</code></pre> my_heap.dart<pre><code>/* 左の子のインデックスを取得 */\nint _left(int i) {\n return 2 * i + 1;\n}\n\n/* 右の子のインデックスを取得 */\nint _right(int i) {\n return 2 * i + 2;\n}\n\n/* 親のインデックスを取得 */\nint _parent(int i) {\n return (i - 1) ~/ 2; // 切り捨て除算\n}\n</code></pre> my_heap.rs<pre><code>/* 左の子のインデックスを取得 */\nfn left(i: usize) -&gt; usize {\n 2 * i + 1\n}\n\n/* 右の子のインデックスを取得 */\nfn right(i: usize) -&gt; usize {\n 2 * i + 2\n}\n\n/* 親のインデックスを取得 */\nfn parent(i: usize) -&gt; usize {\n (i - 1) / 2 // 切り捨て除算\n}\n</code></pre> my_heap.c<pre><code>/* 左の子のインデックスを取得 */\nint left(MaxHeap *maxHeap, int i) {\n return 2 * i + 1;\n}\n\n/* 右の子のインデックスを取得 */\nint right(MaxHeap *maxHeap, int i) {\n return 2 * i + 2;\n}\n\n/* 親のインデックスを取得 */\nint parent(MaxHeap *maxHeap, int i) {\n return (i - 1) / 2; // 小数点以下を切り捨てる\n}\n</code></pre> my_heap.kt<pre><code>/* 左の子のインデックスを取得 */\nfun left(i: Int): Int {\n return 2 * i + 1\n}\n\n/* 右の子のインデックスを取得 */\nfun right(i: Int): Int {\n return 2 * i + 2\n}\n\n/* 親のインデックスを取得 */\nfun parent(i: Int): Int {\n return (i - 1) / 2 // 切り捨て除算\n}\n</code></pre> my_heap.rb<pre><code>### 左の子のインデックスを取得 ###\ndef left(i)\n 2 * i + 1\nend\n\n### 右の子のインデックスを取得 ###\ndef right(i)\n 2 * i + 2\nend\n\n### 親のインデックスを取得 ###\ndef parent(i)\n (i - 1) / 2 # 切り捨て除算\nend\n</code></pre>","path":["第 8 章   ヒープ","8.1   ヒープ"],"tags":[]},{"location":"chapter_heap/heap/#2","level":3,"title":"2.   先頭要素へのアクセス","text":"<p>先頭要素は二分木の根ノード、つまりリストの最初の要素です。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py<pre><code>def peek(self) -&gt; int:\n \"\"\"ヒープの先頭要素にアクセスする\"\"\"\n return self.max_heap[0]\n</code></pre> my_heap.cpp<pre><code>/* ヒープの先頭要素にアクセスする */\nint peek() {\n return maxHeap[0];\n}\n</code></pre> my_heap.java<pre><code>/* ヒープの先頭要素にアクセスする */\nint peek() {\n return maxHeap.get(0);\n}\n</code></pre> my_heap.cs<pre><code>/* ヒープの先頭要素にアクセスする */\nint Peek() {\n return maxHeap[0];\n}\n</code></pre> my_heap.go<pre><code>/* ヒープの先頭要素にアクセスする */\nfunc (h *maxHeap) peek() any {\n return h.data[0]\n}\n</code></pre> my_heap.swift<pre><code>/* ヒープの先頭要素にアクセスする */\nfunc peek() -&gt; Int {\n maxHeap[0]\n}\n</code></pre> my_heap.js<pre><code>/* ヒープの先頭要素にアクセスする */\npeek() {\n return this.#maxHeap[0];\n}\n</code></pre> my_heap.ts<pre><code>/* ヒープの先頭要素にアクセスする */\npeek(): number {\n return this.maxHeap[0];\n}\n</code></pre> my_heap.dart<pre><code>/* ヒープの先頭要素にアクセスする */\nint peek() {\n return _maxHeap[0];\n}\n</code></pre> my_heap.rs<pre><code>/* ヒープの先頭要素にアクセスする */\nfn peek(&amp;self) -&gt; Option&lt;i32&gt; {\n self.max_heap.first().copied()\n}\n</code></pre> my_heap.c<pre><code>/* ヒープの先頭要素にアクセスする */\nint peek(MaxHeap *maxHeap) {\n return maxHeap-&gt;data[0];\n}\n</code></pre> my_heap.kt<pre><code>/* ヒープの先頭要素にアクセスする */\nfun peek(): Int {\n return maxHeap[0]\n}\n</code></pre> my_heap.rb<pre><code>### ヒープの先頭要素にアクセスする ###\ndef peek\n @max_heap[0]\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 8 章   ヒープ","8.1   ヒープ"],"tags":[]},{"location":"chapter_heap/heap/#3","level":3,"title":"3.   要素の挿入","text":"<p>要素 <code>val</code> をまずヒープの末尾に追加します。<code>val</code> がほかの要素より大きい場合、ヒープの条件が崩れている可能性があるため、挿入したノードから根までの経路上の各ノードを修復する必要があります。この操作をヒープ化(heapify)と呼びます。</p> <p>挿入したノードから、下から上へヒープ化します。図 8-3 のように、挿入したノードと親の値を比較し、挿入したノードのほうが大きければ交換します。同じ操作を繰り返して下から上へ修復し、根を越えるか、交換が不要なノードに達したら終了します。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 8-3   ヒープへの挿入の手順 </p> <p>ノード総数を \\(n\\) とすると、木の高さは \\(O(\\log n)\\) です。したがって、ヒープ化の反復は最大 \\(O(\\log n)\\) 回で、挿入の時間計算量は \\(O(\\log n)\\) です。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py<pre><code>def push(self, val: int):\n \"\"\"ヒープに要素を挿入する\"\"\"\n # ノードを追加する\n self.max_heap.append(val)\n # 下から上へヒープ化する\n self.sift_up(self.size() - 1)\n\ndef sift_up(self, i: int):\n \"\"\"ノード i から上向きにヒープ化する\"\"\"\n while True:\n # ノード i の親ノードを取得する\n p = self.parent(i)\n # 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if p &lt; 0 or self.max_heap[i] &lt;= self.max_heap[p]:\n break\n # 2 つのノードを交換する\n self.swap(i, p)\n # 上向きのヒープ化を繰り返す\n i = p\n</code></pre> my_heap.cpp<pre><code>/* ヒープに要素を挿入する */\nvoid push(int val) {\n // ノードを追加する\n maxHeap.push_back(val);\n // 下から上へヒープ化する\n siftUp(size() - 1);\n}\n\n/* ノード i から上向きにヒープ化する */\nvoid siftUp(int i) {\n while (true) {\n // ノード i の親ノードを取得する\n int p = parent(i);\n // 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if (p &lt; 0 || maxHeap[i] &lt;= maxHeap[p])\n break;\n // 2 つのノードを交換する\n swap(maxHeap[i], maxHeap[p]);\n // 上向きのヒープ化を繰り返す\n i = p;\n }\n}\n</code></pre> my_heap.java<pre><code>/* ヒープに要素を挿入する */\nvoid push(int val) {\n // ノードを追加する\n maxHeap.add(val);\n // 下から上へヒープ化する\n siftUp(size() - 1);\n}\n\n/* ノード i から上向きにヒープ化する */\nvoid siftUp(int i) {\n while (true) {\n // ノード i の親ノードを取得する\n int p = parent(i);\n // 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if (p &lt; 0 || maxHeap.get(i) &lt;= maxHeap.get(p))\n break;\n // 2 つのノードを交換する\n swap(i, p);\n // 上向きのヒープ化を繰り返す\n i = p;\n }\n}\n</code></pre> my_heap.cs<pre><code>/* ヒープに要素を挿入する */\nvoid Push(int val) {\n // ノードを追加する\n maxHeap.Add(val);\n // 下から上へヒープ化する\n SiftUp(Size() - 1);\n}\n\n/* ノード i から上向きにヒープ化する */\nvoid SiftUp(int i) {\n while (true) {\n // ノード i の親ノードを取得する\n int p = Parent(i);\n // 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if (p &lt; 0 || maxHeap[i] &lt;= maxHeap[p])\n break;\n // 2 つのノードを交換する\n Swap(i, p);\n // 上向きのヒープ化を繰り返す\n i = p;\n }\n}\n</code></pre> my_heap.go<pre><code>/* ヒープに要素を挿入する */\nfunc (h *maxHeap) push(val any) {\n // ノードを追加する\n h.data = append(h.data, val)\n // 下から上へヒープ化する\n h.siftUp(len(h.data) - 1)\n}\n\n/* ノード i から上向きにヒープ化する */\nfunc (h *maxHeap) siftUp(i int) {\n for true {\n // ノード i の親ノードを取得する\n p := h.parent(i)\n // 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if p &lt; 0 || h.data[i].(int) &lt;= h.data[p].(int) {\n break\n }\n // 2 つのノードを交換する\n h.swap(i, p)\n // 上向きのヒープ化を繰り返す\n i = p\n }\n}\n</code></pre> my_heap.swift<pre><code>/* ヒープに要素を挿入する */\nfunc push(val: Int) {\n // ノードを追加する\n maxHeap.append(val)\n // 下から上へヒープ化する\n siftUp(i: size() - 1)\n}\n\n/* ノード i から上向きにヒープ化する */\nfunc siftUp(i: Int) {\n var i = i\n while true {\n // ノード i の親ノードを取得する\n let p = parent(i: i)\n // 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if p &lt; 0 || maxHeap[i] &lt;= maxHeap[p] {\n break\n }\n // 2 つのノードを交換する\n swap(i: i, j: p)\n // 上向きのヒープ化を繰り返す\n i = p\n }\n}\n</code></pre> my_heap.js<pre><code>/* ヒープに要素を挿入する */\npush(val) {\n // ノードを追加する\n this.#maxHeap.push(val);\n // 下から上へヒープ化する\n this.#siftUp(this.size() - 1);\n}\n\n/* ノード i から上向きにヒープ化する */\n#siftUp(i) {\n while (true) {\n // ノード i の親ノードを取得する\n const p = this.#parent(i);\n // 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if (p &lt; 0 || this.#maxHeap[i] &lt;= this.#maxHeap[p]) break;\n // 2 つのノードを交換する\n this.#swap(i, p);\n // 上向きのヒープ化を繰り返す\n i = p;\n }\n}\n</code></pre> my_heap.ts<pre><code>/* ヒープに要素を挿入する */\npush(val: number): void {\n // ノードを追加する\n this.maxHeap.push(val);\n // 下から上へヒープ化する\n this.siftUp(this.size() - 1);\n}\n\n/* ノード i から上向きにヒープ化する */\nsiftUp(i: number): void {\n while (true) {\n // ノード i の親ノードを取得する\n const p = this.parent(i);\n // 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if (p &lt; 0 || this.maxHeap[i] &lt;= this.maxHeap[p]) break;\n // 2 つのノードを交換する\n this.swap(i, p);\n // 上向きのヒープ化を繰り返す\n i = p;\n }\n}\n</code></pre> my_heap.dart<pre><code>/* ヒープに要素を挿入する */\nvoid push(int val) {\n // ノードを追加する\n _maxHeap.add(val);\n // 下から上へヒープ化する\n siftUp(size() - 1);\n}\n\n/* ノード i から上向きにヒープ化する */\nvoid siftUp(int i) {\n while (true) {\n // ノード i の親ノードを取得する\n int p = _parent(i);\n // 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if (p &lt; 0 || _maxHeap[i] &lt;= _maxHeap[p]) {\n break;\n }\n // 2 つのノードを交換する\n _swap(i, p);\n // 上向きのヒープ化を繰り返す\n i = p;\n }\n}\n</code></pre> my_heap.rs<pre><code>/* ヒープに要素を挿入する */\nfn push(&amp;mut self, val: i32) {\n // ノードを追加する\n self.max_heap.push(val);\n // 下から上へヒープ化する\n self.sift_up(self.size() - 1);\n}\n\n/* ノード i から上向きにヒープ化する */\nfn sift_up(&amp;mut self, mut i: usize) {\n loop {\n // ノード i はすでにヒープの先頭なので、ヒープ化を終了する\n if i == 0 {\n break;\n }\n // ノード i の親ノードを取得する\n let p = Self::parent(i);\n // 「ノードの修復が不要」の場合、ヒープ化を終了する\n if self.max_heap[i] &lt;= self.max_heap[p] {\n break;\n }\n // 2 つのノードを交換する\n self.swap(i, p);\n // 上向きのヒープ化を繰り返す\n i = p;\n }\n}\n</code></pre> my_heap.c<pre><code>/* ヒープに要素を挿入する */\nvoid push(MaxHeap *maxHeap, int val) {\n // 既定の設定では、これほど多くのノードを追加してはならない\n if (maxHeap-&gt;size == MAX_SIZE) {\n printf(\"ヒープが満杯です!\");\n return;\n }\n // ノードを追加する\n maxHeap-&gt;data[maxHeap-&gt;size] = val;\n maxHeap-&gt;size++;\n\n // 下から上へヒープ化する\n siftUp(maxHeap, maxHeap-&gt;size - 1);\n}\n\n/* ノード i から上向きにヒープ化する */\nvoid siftUp(MaxHeap *maxHeap, int i) {\n while (true) {\n // ノード i の親ノードを取得する\n int p = parent(maxHeap, i);\n // 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if (p &lt; 0 || maxHeap-&gt;data[i] &lt;= maxHeap-&gt;data[p]) {\n break;\n }\n // 2 つのノードを交換する\n swap(maxHeap, i, p);\n // 上向きのヒープ化を繰り返す\n i = p;\n }\n}\n</code></pre> my_heap.kt<pre><code>/* ヒープに要素を挿入する */\nfun push(_val: Int) {\n // ノードを追加する\n maxHeap.add(_val)\n // 下から上へヒープ化する\n siftUp(size() - 1)\n}\n\n/* ノード i から上向きにヒープ化する */\nfun siftUp(it: Int) {\n // Kotlin の関数の引数は変更できないため、一時変数を作成する\n var i = it\n while (true) {\n // ノード i の親ノードを取得する\n val p = parent(i)\n // 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n if (p &lt; 0 || maxHeap[i] &lt;= maxHeap[p]) break\n // 2 つのノードを交換する\n swap(i, p)\n // 上向きのヒープ化を繰り返す\n i = p\n }\n}\n</code></pre> my_heap.rb<pre><code>### ヒープに要素を挿入する ###\ndef push(val)\n # ノードを追加する\n @max_heap &lt;&lt; val\n # 下から上へヒープ化する\n sift_up(size - 1)\nend\n\n### ノード i から上向きにヒープ化する ###\ndef sift_up(i)\n loop do\n # ノード i の親ノードを取得する\n p = parent(i)\n # 「根ノードを越えた」または「ノードの修復が不要」の場合、ヒープ化を終了する\n break if p &lt; 0 || @max_heap[i] &lt;= @max_heap[p]\n # 2 つのノードを交換する\n swap(i, p)\n # 上向きのヒープ化を繰り返す\n i = p\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 8 章   ヒープ","8.1   ヒープ"],"tags":[]},{"location":"chapter_heap/heap/#4","level":3,"title":"4.   先頭要素の取り出し","text":"<p>先頭要素は二分木の根ノード、つまりリストの最初の要素です。これをリストから直接削除すると、すべてのノードのインデックスが変わり、その後のヒープ化による修復が難しくなります。インデックスの変化をできるだけ少なくするため、次の手順を使います。</p> <ol> <li>ヒープの先頭と末尾、つまり根ノードと一番右の葉を交換します。</li> <li>交換後、末尾をリストから削除します(交換済みなので、実際に削除するのは元の先頭要素です)。</li> <li>根ノードから、上から下へヒープ化します。</li> </ol> <p>図 8-4 のように、上から下へのヒープ化は、下から上へのヒープ化とは逆方向の操作です。根の値と二つの子の値を比較し、最大の子と根を交換します。これを繰り返し、葉を越えるか交換が不要なノードに達したら終了します。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 8-4   ヒープの先頭要素を取り出す手順 </p> <p>挿入と同様に、先頭要素の取り出しの時間計算量も \\(O(\\log n)\\) です。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby my_heap.py<pre><code>def pop(self) -&gt; int:\n \"\"\"ヒープから要素を取り出す\"\"\"\n # 空かどうかを確認する\n if self.is_empty():\n raise IndexError(\"ヒープが空です\")\n # 根と最右の葉を交換(先頭要素と末尾要素を交換)\n self.swap(0, self.size() - 1)\n # ノードを削除する\n val = self.max_heap.pop()\n # 上から下へヒープ化する\n self.sift_down(0)\n # ヒープの先頭要素を返す\n return val\n\ndef sift_down(self, i: int):\n \"\"\"ノード i から下向きにヒープ化する\"\"\"\n while True:\n # ノード i, l, r のうち値が最大のノードを求め、ma とする\n l, r, ma = self.left(i), self.right(i), i\n if l &lt; self.size() and self.max_heap[l] &gt; self.max_heap[ma]:\n ma = l\n if r &lt; self.size() and self.max_heap[r] &gt; self.max_heap[ma]:\n ma = r\n # ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if ma == i:\n break\n # 2 つのノードを交換する\n self.swap(i, ma)\n # 下向きのヒープ化を繰り返す\n i = ma\n</code></pre> my_heap.cpp<pre><code>/* ヒープから要素を取り出す */\nvoid pop() {\n // 空かどうかを確認する\n if (isEmpty()) {\n throw out_of_range(\"ヒープが空です\");\n }\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n swap(maxHeap[0], maxHeap[size() - 1]);\n // ノードを削除する\n maxHeap.pop_back();\n // 上から下へヒープ化する\n siftDown(0);\n}\n\n/* ノード i から下向きにヒープ化する */\nvoid siftDown(int i) {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n int l = left(i), r = right(i), ma = i;\n if (l &lt; size() &amp;&amp; maxHeap[l] &gt; maxHeap[ma])\n ma = l;\n if (r &lt; size() &amp;&amp; maxHeap[r] &gt; maxHeap[ma])\n ma = r;\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma == i)\n break;\n swap(maxHeap[i], maxHeap[ma]);\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n</code></pre> my_heap.java<pre><code>/* ヒープから要素を取り出す */\nint pop() {\n // 空かどうかを確認する\n if (isEmpty())\n throw new IndexOutOfBoundsException();\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n swap(0, size() - 1);\n // ノードを削除する\n int val = maxHeap.remove(size() - 1);\n // 上から下へヒープ化する\n siftDown(0);\n // ヒープの先頭要素を返す\n return val;\n}\n\n/* ノード i から下向きにヒープ化する */\nvoid siftDown(int i) {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n int l = left(i), r = right(i), ma = i;\n if (l &lt; size() &amp;&amp; maxHeap.get(l) &gt; maxHeap.get(ma))\n ma = l;\n if (r &lt; size() &amp;&amp; maxHeap.get(r) &gt; maxHeap.get(ma))\n ma = r;\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma == i)\n break;\n // 2 つのノードを交換する\n swap(i, ma);\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n</code></pre> my_heap.cs<pre><code>/* ヒープから要素を取り出す */\nint Pop() {\n // 空かどうかを確認する\n if (IsEmpty())\n throw new IndexOutOfRangeException();\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n Swap(0, Size() - 1);\n // ノードを削除する\n int val = maxHeap.Last();\n maxHeap.RemoveAt(Size() - 1);\n // 上から下へヒープ化する\n SiftDown(0);\n // ヒープの先頭要素を返す\n return val;\n}\n\n/* ノード i から下向きにヒープ化する */\nvoid SiftDown(int i) {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n int l = Left(i), r = Right(i), ma = i;\n if (l &lt; Size() &amp;&amp; maxHeap[l] &gt; maxHeap[ma])\n ma = l;\n if (r &lt; Size() &amp;&amp; maxHeap[r] &gt; maxHeap[ma])\n ma = r;\n // 「ノード i の値が最大」または「葉ノードを越えた」場合、ヒープ化を終了する\n if (ma == i) break;\n // 2 つのノードを交換する\n Swap(i, ma);\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n</code></pre> my_heap.go<pre><code>/* ヒープから要素を取り出す */\nfunc (h *maxHeap) pop() any {\n // 空かどうかを確認する\n if h.isEmpty() {\n fmt.Println(\"エラー\")\n return nil\n }\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n h.swap(0, h.size()-1)\n // ノードを削除する\n val := h.data[len(h.data)-1]\n h.data = h.data[:len(h.data)-1]\n // 上から下へヒープ化する\n h.siftDown(0)\n\n // ヒープの先頭要素を返す\n return val\n}\n\n/* ノード i から下向きにヒープ化する */\nfunc (h *maxHeap) siftDown(i int) {\n for true {\n // ノード i, l, r のうち値が最大のノードを求め、max とする\n l, r, max := h.left(i), h.right(i), i\n if l &lt; h.size() &amp;&amp; h.data[l].(int) &gt; h.data[max].(int) {\n max = l\n }\n if r &lt; h.size() &amp;&amp; h.data[r].(int) &gt; h.data[max].(int) {\n max = r\n }\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if max == i {\n break\n }\n // 2 つのノードを交換する\n h.swap(i, max)\n // 下向きのヒープ化を繰り返す\n i = max\n }\n}\n</code></pre> my_heap.swift<pre><code>/* ヒープから要素を取り出す */\nfunc pop() -&gt; Int {\n // 空かどうかを確認する\n if isEmpty() {\n fatalError(\"ヒープが空です\")\n }\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n swap(i: 0, j: size() - 1)\n // ノードを削除する\n let val = maxHeap.remove(at: size() - 1)\n // 上から下へヒープ化する\n siftDown(i: 0)\n // ヒープの先頭要素を返す\n return val\n}\n\n/* ノード i から下向きにヒープ化する */\nfunc siftDown(i: Int) {\n var i = i\n while true {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n let l = left(i: i)\n let r = right(i: i)\n var ma = i\n if l &lt; size(), maxHeap[l] &gt; maxHeap[ma] {\n ma = l\n }\n if r &lt; size(), maxHeap[r] &gt; maxHeap[ma] {\n ma = r\n }\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if ma == i {\n break\n }\n // 2 つのノードを交換する\n swap(i: i, j: ma)\n // 下向きのヒープ化を繰り返す\n i = ma\n }\n}\n</code></pre> my_heap.js<pre><code>/* ヒープから要素を取り出す */\npop() {\n // 空かどうかを確認する\n if (this.isEmpty()) throw new Error('ヒープが空です');\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n this.#swap(0, this.size() - 1);\n // ノードを削除する\n const val = this.#maxHeap.pop();\n // 上から下へヒープ化する\n this.#siftDown(0);\n // ヒープの先頭要素を返す\n return val;\n}\n\n/* ノード i から下向きにヒープ化する */\n#siftDown(i) {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n const l = this.#left(i),\n r = this.#right(i);\n let ma = i;\n if (l &lt; this.size() &amp;&amp; this.#maxHeap[l] &gt; this.#maxHeap[ma]) ma = l;\n if (r &lt; this.size() &amp;&amp; this.#maxHeap[r] &gt; this.#maxHeap[ma]) ma = r;\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma === i) break;\n // 2 つのノードを交換する\n this.#swap(i, ma);\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n</code></pre> my_heap.ts<pre><code>/* ヒープから要素を取り出す */\npop(): number {\n // 空かどうかを確認する\n if (this.isEmpty()) throw new RangeError('ヒープが空です。');\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n this.swap(0, this.size() - 1);\n // ノードを削除する\n const val = this.maxHeap.pop();\n // 上から下へヒープ化する\n this.siftDown(0);\n // ヒープの先頭要素を返す\n return val;\n}\n\n/* ノード i から下向きにヒープ化する */\nsiftDown(i: number): void {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n const l = this.left(i),\n r = this.right(i);\n let ma = i;\n if (l &lt; this.size() &amp;&amp; this.maxHeap[l] &gt; this.maxHeap[ma]) ma = l;\n if (r &lt; this.size() &amp;&amp; this.maxHeap[r] &gt; this.maxHeap[ma]) ma = r;\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma === i) break;\n // 2 つのノードを交換する\n this.swap(i, ma);\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n</code></pre> my_heap.dart<pre><code>/* ヒープから要素を取り出す */\nint pop() {\n // 空かどうかを確認する\n if (isEmpty()) throw Exception('ヒープが空です');\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n _swap(0, size() - 1);\n // ノードを削除する\n int val = _maxHeap.removeLast();\n // 上から下へヒープ化する\n siftDown(0);\n // ヒープの先頭要素を返す\n return val;\n}\n\n/* ノード i から下向きにヒープ化する */\nvoid siftDown(int i) {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n int l = _left(i);\n int r = _right(i);\n int ma = i;\n if (l &lt; size() &amp;&amp; _maxHeap[l] &gt; _maxHeap[ma]) ma = l;\n if (r &lt; size() &amp;&amp; _maxHeap[r] &gt; _maxHeap[ma]) ma = r;\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma == i) break;\n // 2 つのノードを交換する\n _swap(i, ma);\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n</code></pre> my_heap.rs<pre><code>/* ヒープから要素を取り出す */\nfn pop(&amp;mut self) -&gt; i32 {\n // 空かどうかを確認する\n if self.is_empty() {\n panic!(\"インデックスが範囲外です\");\n }\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n self.swap(0, self.size() - 1);\n // ノードを削除する\n let val = self.max_heap.pop().unwrap();\n // 上から下へヒープ化する\n self.sift_down(0);\n // ヒープの先頭要素を返す\n val\n}\n\n/* ノード i から下向きにヒープ化する */\nfn sift_down(&amp;mut self, mut i: usize) {\n loop {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n let (l, r, mut ma) = (Self::left(i), Self::right(i), i);\n if l &lt; self.size() &amp;&amp; self.max_heap[l] &gt; self.max_heap[ma] {\n ma = l;\n }\n if r &lt; self.size() &amp;&amp; self.max_heap[r] &gt; self.max_heap[ma] {\n ma = r;\n }\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if ma == i {\n break;\n }\n // 2 つのノードを交換する\n self.swap(i, ma);\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n</code></pre> my_heap.c<pre><code>/* ヒープから要素を取り出す */\nint pop(MaxHeap *maxHeap) {\n // 空かどうかを確認する\n if (isEmpty(maxHeap)) {\n printf(\"ヒープが空です!\");\n return INT_MAX;\n }\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n swap(maxHeap, 0, size(maxHeap) - 1);\n // ノードを削除する\n int val = maxHeap-&gt;data[maxHeap-&gt;size - 1];\n maxHeap-&gt;size--;\n // 上から下へヒープ化する\n siftDown(maxHeap, 0);\n\n // ヒープの先頭要素を返す\n return val;\n}\n\n/* ノード i から下向きにヒープ化する */\nvoid siftDown(MaxHeap *maxHeap, int i) {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、max とする\n int l = left(maxHeap, i);\n int r = right(maxHeap, i);\n int max = i;\n if (l &lt; size(maxHeap) &amp;&amp; maxHeap-&gt;data[l] &gt; maxHeap-&gt;data[max]) {\n max = l;\n }\n if (r &lt; size(maxHeap) &amp;&amp; maxHeap-&gt;data[r] &gt; maxHeap-&gt;data[max]) {\n max = r;\n }\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (max == i) {\n break;\n }\n // 2 つのノードを交換する\n swap(maxHeap, i, max);\n // 下向きのヒープ化を繰り返す\n i = max;\n }\n}\n</code></pre> my_heap.kt<pre><code>/* ヒープから要素を取り出す */\nfun pop(): Int {\n // 空かどうかを確認する\n if (isEmpty()) throw IndexOutOfBoundsException()\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n swap(0, size() - 1)\n // ノードを削除する\n val _val = maxHeap.removeAt(size() - 1)\n // 上から下へヒープ化する\n siftDown(0)\n // ヒープの先頭要素を返す\n return _val\n}\n\n/* ノード i から下向きにヒープ化する */\nfun siftDown(it: Int) {\n // Kotlin の関数の引数は変更できないため、一時変数を作成する\n var i = it\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n val l = left(i)\n val r = right(i)\n var ma = i\n if (l &lt; size() &amp;&amp; maxHeap[l] &gt; maxHeap[ma]) ma = l\n if (r &lt; size() &amp;&amp; maxHeap[r] &gt; maxHeap[ma]) ma = r\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma == i) break\n // 2 つのノードを交換する\n swap(i, ma)\n // 下向きのヒープ化を繰り返す\n i = ma\n }\n}\n</code></pre> my_heap.rb<pre><code>### ヒープから要素を取り出す ###\ndef pop\n # 空かどうかを確認する\n raise IndexError, \"ヒープが空です\" if is_empty?\n # 根と最右の葉を交換(先頭要素と末尾要素を交換)\n swap(0, size - 1)\n # ノードを削除する\n val = @max_heap.pop\n # 上から下へヒープ化する\n sift_down(0)\n # ヒープの先頭要素を返す\n val\nend\n\n### ノード i から下向きにヒープ化する ###\ndef sift_down(i)\n loop do\n # ノード i, l, r のうち値が最大のノードを求め、ma とする\n l, r, ma = left(i), right(i), i\n ma = l if l &lt; size &amp;&amp; @max_heap[l] &gt; @max_heap[ma]\n ma = r if r &lt; size &amp;&amp; @max_heap[r] &gt; @max_heap[ma]\n\n # ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n break if ma == i\n\n # 2 つのノードを交換する\n swap(i, ma)\n # 下向きのヒープ化を繰り返す\n i = ma\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 8 章   ヒープ","8.1   ヒープ"],"tags":[]},{"location":"chapter_heap/heap/#813","level":2,"title":"8.1.3   ヒープの主な用途","text":"<ul> <li>優先度付きキュー:ヒープは、優先度付きキューの実装で通常最初に選ばれるデータ構造です。エンキューとデキューはともに \\(O(\\log n)\\)、構築は \\(O(n)\\) と、いずれも非常に効率的です。</li> <li>ヒープソート:データからヒープを作り、要素を取り出し続けると整列済みのデータを得られます。ただし、通常はさらにすっきりした方法で実装します。詳しくは「ヒープソート」の節を参照してください。</li> <li>大きいほうから \\(k\\) 個の要素の取得:古典的なアルゴリズムの問題であり、代表的な用途でもあります。例えば、話題のニュース上位 10 件を微博のトレンドに選んだり、販売数上位 10 商品を選んだりします。</li> </ul>","path":["第 8 章   ヒープ","8.1   ヒープ"],"tags":[]},{"location":"chapter_heap/summary/","level":1,"title":"8.4   まとめ","text":"","path":["第 8 章   ヒープ","8.4   まとめ"],"tags":[]},{"location":"chapter_heap/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>ヒープは完全二分木であり、満たす条件によって最大ヒープと最小ヒープに分けられます。最大(最小)ヒープでは、ヒープの先頭要素が最大(最小)です。</li> <li>優先度付きキューは、取り出しに優先度を持つキューと定義され、通常はヒープで実装されます。</li> <li>ヒープの主な操作とその時間計算量は、要素の挿入が \\(O(\\log n)\\)、先頭要素の取り出しが \\(O(\\log n)\\)、先頭要素へのアクセスが \\(O(1)\\) などです。</li> <li>完全二分木は配列で表現するのに非常に適しているため、通常は配列にヒープを格納します。</li> <li>ヒープ化はヒープの性質を維持するための操作であり、挿入と取り出しの両方で用います。</li> <li>\\(n\\) 個の要素を入力してヒープを構築する時間計算量は \\(O(n)\\) まで改善でき、非常に効率がよいです。</li> <li>Top-k は古典的なアルゴリズムの問題であり、ヒープを用いると時間計算量 \\(O(n \\log k)\\) で効率よく解けます。</li> </ul>","path":["第 8 章   ヒープ","8.4   まとめ"],"tags":[]},{"location":"chapter_heap/summary/#2-q-a","level":3,"title":"2.   Q &amp; A","text":"<p>Q:データ構造の「ヒープ」とメモリ管理の「ヒープ」は同じ概念ですか?</p> <p>両者は同じ概念ではなく、たまたまどちらも「ヒープ」と呼ばれているだけです。コンピュータシステムのメモリにおけるヒープは動的メモリ割り当ての一部であり、プログラムは実行時にここへデータを格納できます。プログラムは必要な量のヒープメモリを要求し、オブジェクトや配列などの複雑な構造を格納します。データが不要になったら、メモリリークを防ぐためにそのメモリを解放する必要があります。スタックメモリに比べ、ヒープメモリの管理と使用にはより注意が必要であり、不適切な使い方はメモリリークや不正なポインタなどの問題につながります。</p>","path":["第 8 章   ヒープ","8.4   まとめ"],"tags":[]},{"location":"chapter_heap/top_k/","level":1,"title":"8.3   Top-k 問題","text":"<p>問題</p> <p>長さ \\(n\\) の未整列配列 <code>nums</code> が与えられます。大きいほうから \\(k\\) 個の要素を返してください。</p> <p>まず、考え方が直接的な二つの解法を紹介し、続いて、より効率的なヒープを使う解法を紹介します。</p>","path":["第 8 章   ヒープ","8.3   Top-k 問題"],"tags":[]},{"location":"chapter_heap/top_k/#831","level":2,"title":"8.3.1   方法一:走査して選ぶ","text":"<p>図 8-6 のように \\(k\\) 回走査し、各回で \\(1\\)、\\(2\\)、\\(\\dots\\)、\\(k\\) 番目に大きい要素を取り出せます。時間計算量は \\(O(nk)\\) です。</p> <p>この方法は \\(k \\ll n\\) の場合にしか適していません。\\(k\\) が \\(n\\) に近づくと、時間計算量は \\(O(n^2)\\) に近づき、非常に時間がかかるためです。</p> <p></p> <p> 図 8-6   走査して大きいほうから k 個を探す </p> <p>ヒント</p> <p>\\(k = n\\) なら、完全に整列された列が得られ、この方法は「選択ソート」と同じになります。</p>","path":["第 8 章   ヒープ","8.3   Top-k 問題"],"tags":[]},{"location":"chapter_heap/top_k/#832","level":2,"title":"8.3.2   方法二:ソートする","text":"<p>図 8-7 のように、配列 <code>nums</code> をソートして右端の \\(k\\) 要素を返すことができます。時間計算量は \\(O(n \\log n)\\) です。</p> <p>ただし、この方法は必要以上の処理をしています。大きいほうから \\(k\\) 個を求めるだけでよく、ほかの要素のソートは不要だからです。</p> <p></p> <p> 図 8-7   ソートして大きいほうから k 個を探す </p>","path":["第 8 章   ヒープ","8.3   Top-k 問題"],"tags":[]},{"location":"chapter_heap/top_k/#833","level":2,"title":"8.3.3   方法三:ヒープを使う","text":"<p>ヒープを使うと、Top-k 問題をより効率よく解けます。流れを図 8-8 に示します。</p> <ol> <li>先頭要素が最小となる最小ヒープを初期化します。</li> <li>配列の最初の \\(k\\) 個の要素を順に挿入します。</li> <li>\\(k + 1\\) 番目以降は、現在の要素がヒープの先頭より大きければ、先頭を取り出して現在の要素を挿入します。</li> <li>走査が終わると、ヒープには大きいほうから \\(k\\) 個の要素が残ります。</li> </ol> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 8-8   ヒープで大きいほうから k 個を探す </p> <p>コード例は次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby top_k.py<pre><code>def top_k_heap(nums: list[int], k: int) -&gt; list[int]:\n \"\"\"ヒープを使って配列内の大きいほうから k 個の要素を求める\"\"\"\n # 最小ヒープを初期化する\n heap = []\n # 配列の先頭 k 個の要素をヒープに挿入する\n for i in range(k):\n heapq.heappush(heap, nums[i])\n # k+1 番目の要素から始め、ヒープの長さを k に保つ\n for i in range(k, len(nums)):\n # 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if nums[i] &gt; heap[0]:\n heapq.heappop(heap)\n heapq.heappush(heap, nums[i])\n return heap\n</code></pre> top_k.cpp<pre><code>/* ヒープを使って配列内の大きいほうから k 個の要素を求める */\npriority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; topKHeap(vector&lt;int&gt; &amp;nums, int k) {\n // 最小ヒープを初期化する\n priority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; heap;\n // 配列の先頭 k 個の要素をヒープに挿入する\n for (int i = 0; i &lt; k; i++) {\n heap.push(nums[i]);\n }\n // k+1 番目の要素から始め、ヒープの長さを k に保つ\n for (int i = k; i &lt; nums.size(); i++) {\n // 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if (nums[i] &gt; heap.top()) {\n heap.pop();\n heap.push(nums[i]);\n }\n }\n return heap;\n}\n</code></pre> top_k.java<pre><code>/* ヒープを使って配列内の大きいほうから k 個の要素を求める */\nQueue&lt;Integer&gt; topKHeap(int[] nums, int k) {\n // 最小ヒープを初期化する\n Queue&lt;Integer&gt; heap = new PriorityQueue&lt;Integer&gt;();\n // 配列の先頭 k 個の要素をヒープに挿入する\n for (int i = 0; i &lt; k; i++) {\n heap.offer(nums[i]);\n }\n // k+1 番目の要素から始め、ヒープの長さを k に保つ\n for (int i = k; i &lt; nums.length; i++) {\n // 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if (nums[i] &gt; heap.peek()) {\n heap.poll();\n heap.offer(nums[i]);\n }\n }\n return heap;\n}\n</code></pre> top_k.cs<pre><code>/* ヒープを使って配列内の大きいほうから k 個の要素を求める */\nPriorityQueue&lt;int, int&gt; TopKHeap(int[] nums, int k) {\n // 最小ヒープを初期化する\n PriorityQueue&lt;int, int&gt; heap = new();\n // 配列の先頭 k 個の要素をヒープに挿入する\n for (int i = 0; i &lt; k; i++) {\n heap.Enqueue(nums[i], nums[i]);\n }\n // k+1 番目の要素から始め、ヒープの長さを k に保つ\n for (int i = k; i &lt; nums.Length; i++) {\n // 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if (nums[i] &gt; heap.Peek()) {\n heap.Dequeue();\n heap.Enqueue(nums[i], nums[i]);\n }\n }\n return heap;\n}\n</code></pre> top_k.go<pre><code>/* ヒープを使って配列内の大きいほうから k 個の要素を求める */\nfunc topKHeap(nums []int, k int) *minHeap {\n // 最小ヒープを初期化する\n h := &amp;minHeap{}\n heap.Init(h)\n // 配列の先頭 k 個の要素をヒープに挿入する\n for i := 0; i &lt; k; i++ {\n heap.Push(h, nums[i])\n }\n // k+1 番目の要素から始め、ヒープの長さを k に保つ\n for i := k; i &lt; len(nums); i++ {\n // 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if nums[i] &gt; h.Top().(int) {\n heap.Pop(h)\n heap.Push(h, nums[i])\n }\n }\n return h\n}\n</code></pre> top_k.swift<pre><code>/* ヒープを使って配列内の大きいほうから k 個の要素を求める */\nfunc topKHeap(nums: [Int], k: Int) -&gt; [Int] {\n // 最小ヒープを初期化し、先頭 k 個の要素でヒープを構築する\n var heap = Heap(nums.prefix(k))\n // k+1 番目の要素から始め、ヒープの長さを k に保つ\n for i in nums.indices.dropFirst(k) {\n // 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if nums[i] &gt; heap.min()! {\n _ = heap.removeMin()\n heap.insert(nums[i])\n }\n }\n return heap.unordered\n}\n</code></pre> top_k.js<pre><code>/* ヒープに要素を挿入する */\nfunction pushMinHeap(maxHeap, val) {\n // 要素の符号を反転する\n maxHeap.push(-val);\n}\n\n/* ヒープから要素を取り出す */\nfunction popMinHeap(maxHeap) {\n // 要素の符号を反転する\n return -maxHeap.pop();\n}\n\n/* ヒープの先頭要素にアクセスする */\nfunction peekMinHeap(maxHeap) {\n // 要素の符号を反転する\n return -maxHeap.peek();\n}\n\n/* ヒープ内の要素を取り出す */\nfunction getMinHeap(maxHeap) {\n // 要素の符号を反転する\n return maxHeap.getMaxHeap().map((num) =&gt; -num);\n}\n\n/* ヒープを使って配列内の大きいほうから k 個の要素を求める */\nfunction topKHeap(nums, k) {\n // 最小ヒープを初期化する\n // 注意:ヒープの全要素の符号を反転することで、最大ヒープを使って最小ヒープを再現する\n const maxHeap = new MaxHeap([]);\n // 配列の先頭 k 個の要素をヒープに挿入する\n for (let i = 0; i &lt; k; i++) {\n pushMinHeap(maxHeap, nums[i]);\n }\n // k+1 番目の要素から始め、ヒープの長さを k に保つ\n for (let i = k; i &lt; nums.length; i++) {\n // 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if (nums[i] &gt; peekMinHeap(maxHeap)) {\n popMinHeap(maxHeap);\n pushMinHeap(maxHeap, nums[i]);\n }\n }\n // ヒープ内の要素を返す\n return getMinHeap(maxHeap);\n}\n</code></pre> top_k.ts<pre><code>/* ヒープに要素を挿入する */\nfunction pushMinHeap(maxHeap: MaxHeap, val: number): void {\n // 要素の符号を反転する\n maxHeap.push(-val);\n}\n\n/* ヒープから要素を取り出す */\nfunction popMinHeap(maxHeap: MaxHeap): number {\n // 要素の符号を反転する\n return -maxHeap.pop();\n}\n\n/* ヒープの先頭要素にアクセスする */\nfunction peekMinHeap(maxHeap: MaxHeap): number {\n // 要素の符号を反転する\n return -maxHeap.peek();\n}\n\n/* ヒープ内の要素を取り出す */\nfunction getMinHeap(maxHeap: MaxHeap): number[] {\n // 要素の符号を反転する\n return maxHeap.getMaxHeap().map((num: number) =&gt; -num);\n}\n\n/* ヒープを使って配列内の大きいほうから k 個の要素を求める */\nfunction topKHeap(nums: number[], k: number): number[] {\n // 最小ヒープを初期化する\n // 注意:ヒープの全要素の符号を反転することで、最大ヒープを使って最小ヒープを再現する\n const maxHeap = new MaxHeap([]);\n // 配列の先頭 k 個の要素をヒープに挿入する\n for (let i = 0; i &lt; k; i++) {\n pushMinHeap(maxHeap, nums[i]);\n }\n // k+1 番目の要素から始め、ヒープの長さを k に保つ\n for (let i = k; i &lt; nums.length; i++) {\n // 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if (nums[i] &gt; peekMinHeap(maxHeap)) {\n popMinHeap(maxHeap);\n pushMinHeap(maxHeap, nums[i]);\n }\n }\n // ヒープ内の要素を返す\n return getMinHeap(maxHeap);\n}\n</code></pre> top_k.dart<pre><code>/* ヒープを使って配列内の大きいほうから k 個の要素を求める */\nMinHeap topKHeap(List&lt;int&gt; nums, int k) {\n // 最小ヒープを初期化し、配列の先頭 k 個の要素を挿入する\n MinHeap heap = MinHeap(nums.sublist(0, k));\n // k+1 番目の要素から始め、ヒープの長さを k に保つ\n for (int i = k; i &lt; nums.length; i++) {\n // 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if (nums[i] &gt; heap.peek()) {\n heap.pop();\n heap.push(nums[i]);\n }\n }\n return heap;\n}\n</code></pre> top_k.rs<pre><code>/* ヒープを使って配列内の大きいほうから k 個の要素を求める */\nfn top_k_heap(nums: Vec&lt;i32&gt;, k: usize) -&gt; BinaryHeap&lt;Reverse&lt;i32&gt;&gt; {\n // BinaryHeap は最大ヒープなので、Reverse で要素の順序を反転して最小ヒープを実装する\n let mut heap = BinaryHeap::&lt;Reverse&lt;i32&gt;&gt;::new();\n // 配列の先頭 k 個の要素をヒープに挿入する\n for &amp;num in nums.iter().take(k) {\n heap.push(Reverse(num));\n }\n // k+1 番目の要素から始め、ヒープの長さを k に保つ\n for &amp;num in nums.iter().skip(k) {\n // 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if num &gt; heap.peek().unwrap().0 {\n heap.pop();\n heap.push(Reverse(num));\n }\n }\n heap\n}\n</code></pre> top_k.c<pre><code>/* ヒープに要素を挿入する */\nvoid pushMinHeap(MaxHeap *maxHeap, int val) {\n // 要素の符号を反転する\n push(maxHeap, -val);\n}\n\n/* ヒープから要素を取り出す */\nint popMinHeap(MaxHeap *maxHeap) {\n // 要素の符号を反転する\n return -pop(maxHeap);\n}\n\n/* ヒープの先頭要素にアクセスする */\nint peekMinHeap(MaxHeap *maxHeap) {\n // 要素の符号を反転する\n return -peek(maxHeap);\n}\n\n/* ヒープ内の要素を取り出す */\nint *getMinHeap(MaxHeap *maxHeap) {\n // ヒープの全要素の符号を反転し、res 配列に格納する\n int *res = (int *)malloc(maxHeap-&gt;size * sizeof(int));\n for (int i = 0; i &lt; maxHeap-&gt;size; i++) {\n res[i] = -maxHeap-&gt;data[i];\n }\n return res;\n}\n\n/* ヒープ内の要素を取り出す */\nint *getMinHeap(MaxHeap *maxHeap) {\n // ヒープの全要素の符号を反転し、res 配列に格納する\n int *res = (int *)malloc(maxHeap-&gt;size * sizeof(int));\n for (int i = 0; i &lt; maxHeap-&gt;size; i++) {\n res[i] = -maxHeap-&gt;data[i];\n }\n return res;\n}\n\n// ヒープを使って配列内の大きいほうから k 個の要素を求める関数\nint *topKHeap(int *nums, int sizeNums, int k) {\n // 最小ヒープを初期化する\n // 注意:ヒープの全要素の符号を反転することで、最大ヒープを使って最小ヒープを再現する\n int *empty = (int *)malloc(0);\n MaxHeap *maxHeap = newMaxHeap(empty, 0);\n // 配列の先頭 k 個の要素をヒープに挿入する\n for (int i = 0; i &lt; k; i++) {\n pushMinHeap(maxHeap, nums[i]);\n }\n // k+1 番目の要素から始め、ヒープの長さを k に保つ\n for (int i = k; i &lt; sizeNums; i++) {\n // 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if (nums[i] &gt; peekMinHeap(maxHeap)) {\n popMinHeap(maxHeap);\n pushMinHeap(maxHeap, nums[i]);\n }\n }\n int *res = getMinHeap(maxHeap);\n // メモリを解放する\n delMaxHeap(maxHeap);\n return res;\n}\n</code></pre> top_k.kt<pre><code>/* ヒープを使って配列内の大きいほうから k 個の要素を求める */\nfun topKHeap(nums: IntArray, k: Int): Queue&lt;Int&gt; {\n // 最小ヒープを初期化する\n val heap = PriorityQueue&lt;Int&gt;()\n // 配列の先頭 k 個の要素をヒープに挿入する\n for (i in 0..&lt;k) {\n heap.offer(nums[i])\n }\n // k+1 番目の要素から始め、ヒープの長さを k に保つ\n for (i in k..&lt;nums.size) {\n // 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if (nums[i] &gt; heap.peek()) {\n heap.poll()\n heap.offer(nums[i])\n }\n }\n return heap\n}\n</code></pre> top_k.rb<pre><code>### ヒープを使って配列内の大きいほうから k 個の要素を求める ###\ndef top_k_heap(nums, k)\n # 最小ヒープを初期化する\n # 注意:ヒープの全要素の符号を反転することで、最大ヒープを使って最小ヒープを再現する\n max_heap = MaxHeap.new([])\n\n # 配列の先頭 k 個の要素をヒープに挿入する\n for i in 0...k\n push_min_heap(max_heap, nums[i])\n end\n\n # k+1 番目の要素から始め、ヒープの長さを k に保つ\n for i in k...nums.length\n # 現在の要素がヒープの先頭要素より大きい場合、先頭要素を取り出し、現在の要素を挿入する\n if nums[i] &gt; peek_min_heap(max_heap)\n pop_min_heap(max_heap)\n push_min_heap(max_heap, nums[i])\n end\n end\n\n get_min_heap(max_heap)\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>\\(n\\) 回の反復で挿入と取り出しを行い、ヒープの最大長は \\(k\\) なので、時間計算量は \\(O(n \\log k)\\) です。非常に効率的で、\\(k\\) が小さいと \\(O(n)\\) に近づき、\\(k\\) が大きくても \\(O(n \\log n)\\) を超えません。</p> <p>さらに、この方法は動的なデータストリームにも適しています。データが追加され続けても、ヒープ内の要素を維持することで、大きいほうから \\(k\\) 個を動的に更新できます。</p>","path":["第 8 章   ヒープ","8.3   Top-k 問題"],"tags":[]},{"location":"chapter_hello_algo/","level":1,"title":"序文","text":"<p>数年前、私は力扣で「剑指 Offer」シリーズの解説を公開し、多くの読者から励ましと支援をいただきました。読者との交流で最もよく尋ねられたのが、「アルゴリズムはどう学び始めればよいのか」という質問です。私は次第に、この問いに強い関心を持つようになりました。</p> <p>手探りのまま問題演習に取り組む方法は、単純で直接的、しかも効果があるためか、最もよく選ばれているようです。ただ、問題演習は「マインスイーパ」に似ています。独学の力がある人は地雷を一つずつ取り除けますが、基礎が足りない人は何度も地雷を踏み、挫折するたびに後ずさりしてしまうかもしれません。教科書を通読する方法も一般的ですが、就職を目指す人は、卒業論文、履歴書の提出、筆記試験や面接の準備で精一杯です。分厚い本を読みこなすことは、大きな難題になりがちです。</p> <p>あなたも同じような悩みを抱えているなら、幸いにもこの本があなたを「見つけて」くれました。本書はこの問いに対する私なりの答えであり、最適解ではなくても、少なくとも前向きな試みです。本書を読んだだけで内定を得られるわけではありませんが、データ構造とアルゴリズムの「知識の地図」を探り、さまざまな「地雷」の形、大きさ、分布を知り、多様な「地雷の取り除き方」を身に付ける手助けをします。それらを身に付ければ、より自在に問題演習や文献の読解に取り組み、体系的な知識を少しずつ築けるはずです。</p> <p>私はファインマン教授の言葉、「知識は無料ではありません。注意を払わなければなりません。」に深く共感しています。この意味では、本書も完全に「無料」ではありません。あなたが本書に注いでくれる貴重な「注意」に応えるため、私も最大限の「注意」を払い、全力で執筆に取り組みます。</p> <p>自分の学識や力がまだ十分でないことは承知しています。内容はしばらく推敲を重ねてきましたが、なお多くの誤りが残っているはずです。先生方や学習者の皆さんからのご批判、ご指摘をお願いいたします。</p> <p></p> Hello、アルゴリズム! <p>コンピュータの登場は、世界に大きな変革をもたらしました。高速な計算能力と優れたプログラマビリティを備えたコンピュータは、アルゴリズムの実行とデータ処理に理想的な媒体となりました。ゲームの写実的な映像、自動運転の知的な判断、AlphaGo の見事な対局、ChatGPT の自然な対話などは、いずれもコンピュータ上でアルゴリズムが巧みに働く例です。</p> <p>実際には、コンピュータが生まれる前から、アルゴリズムとデータ構造は世界の隅々に存在していました。初期のアルゴリズムは、古代の数え方や道具の製作手順など、比較的単純でした。文明が進むにつれ、アルゴリズムも精緻で複雑になりました。巧みな職人の技、生産力を解き放つ工業製品、宇宙の運行を説明する科学法則まで、平凡なものにも驚異的なものにも、その背後には巧妙なアルゴリズムの考え方が隠れています。</p> <p>データ構造も同じく至るところにあります。大きくは社会的ネットワーク、小さくは地下鉄の路線まで、多くのシステムは「グラフ」でモデル化できます。国から家庭まで、社会の主な組織形態には「木」の特徴が見られます。冬の重ね着は「スタック」のように、最初に着た服を最後に脱ぎます。バドミントンのシャトルの筒は、一方から入れて他方から取り出す「キュー」です。辞書は「ハッシュテーブル」のように、目的の項目を素早く探せます。</p> <p>本書は、分かりやすいアニメーション図解と実行できるコード例を通して、アルゴリズムとデータ構造の中心的な概念を理解し、プログラミングで実装できるようになることを目指しています。そのうえで、複雑な世界にアルゴリズムがどのように息づいているかを示し、その美しさを伝えたいと考えています。本書があなたの役に立つことを願っています。</p>","path":["序文"],"tags":[]},{"location":"chapter_introduction/","level":1,"title":"第 1 章   アルゴリズムとの出会い","text":"<p>概要</p> <p>一人の少女がデータと絡み合うように軽やかに踊り、その裾にはアルゴリズムの旋律が揺れています。</p> <p>少女はあなたを踊りに誘います。その足取りについていき、論理と美しさに満ちたアルゴリズムの世界へ踏み出しましょう。</p>","path":["第 1 章   アルゴリズムとの出会い"],"tags":[]},{"location":"chapter_introduction/#_1","level":2,"title":"この章の内容","text":"<ul> <li>1.1   アルゴリズムはどこにでもある</li> <li>1.2   アルゴリズムとは</li> <li>1.3   まとめ</li> </ul>","path":["第 1 章   アルゴリズムとの出会い"],"tags":[]},{"location":"chapter_introduction/algorithms_are_everywhere/","level":1,"title":"1.1   アルゴリズムはどこにでもある","text":"<p>「アルゴリズム」と聞くと、自然に数学を思い浮かべるかもしれません。しかし、多くのアルゴリズムは複雑な数学を使わず、むしろ基本的な論理に基づいています。その論理は、私たちの日常生活の至るところにあります。</p> <p>本格的にアルゴリズムを学ぶ前に、面白い事実を紹介します。あなたはすでに、知らず知らずのうちに多くのアルゴリズムを学び、日常的に使っています。具体的な例を挙げて確かめてみましょう。</p> <p>例一:辞書を引く。中国語の辞書では、各漢字に対応するピンインがあり、そのアルファベット順に並んでいます。ピンインの頭文字が \\(r\\) の字を探すなら、通常は図 1-1 のように進めます。</p> <ol> <li>辞書のおよそ真ん中のページを開き、そのページの頭文字を確認します。\\(m\\) だったとします。</li> <li>ピンインのアルファベット順では \\(r\\) は \\(m\\) より後なので、前半を除外し、探す範囲を後半に絞ります。</li> <li>手順 <code>1.</code> と <code>2.</code> を繰り返し、頭文字が \\(r\\) のページを見つけます。</li> </ol> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 1-1   辞書を引く手順 </p> <p>小学生が身に付ける辞書の引き方は、実は有名な「二分探索」です。データ構造の観点では辞書を整列済みの「配列」と見なし、アルゴリズムの観点では一連の操作を「二分探索」と捉えられます。</p> <p>例二:トランプを並べる。トランプをするときは、毎回、手札を小さい順に並べます。その流れを図 1-2 に示します。</p> <ol> <li>手札を「整列済み」と「未整列」の部分に分け、最初は一番左の 1 枚だけが整列済みとします。</li> <li>未整列の部分から 1 枚取り出し、整列済みの部分の正しい位置へ挿入します。これで左の 2 枚が整列済みになります。</li> <li>手順 <code>2.</code> を繰り返し、毎回 1 枚を未整列の部分から整列済みの部分へ挿入し、すべてが整列済みになるまで続けます。</li> </ol> <p></p> <p> 図 1-2   トランプを並べる手順 </p> <p>この並べ方は、本質的に「挿入ソート」です。小さなデータ集合に対して非常に効率がよく、多くのプログラミング言語のソート用ライブラリ関数にも使われています。</p> <p>例三:お釣りを渡す。スーパーで \\(69\\) 元の商品を買い、レジで \\(100\\) 元を渡したとします。店員は \\(31\\) 元のお釣りを渡すため、図 1-3 のように自然に考えます。</p> <ol> <li>選べるのは \\(31\\) 元より小さい額面で、\\(1\\) 元、\\(5\\) 元、\\(10\\) 元、\\(20\\) 元です。</li> <li>選択肢のうち最大の \\(20\\) 元を取り出すと、残りは \\(31 - 20 = 11\\) 元です。</li> <li>残りの選択肢のうち最大の \\(10\\) 元を取り出すと、残りは \\(11 - 10 = 1\\) 元です。</li> <li>残りの選択肢のうち最大の \\(1\\) 元を取り出すと、残りは \\(1 - 1 = 0\\) 元です。</li> <li>お釣りがそろいました。内訳は \\(20 + 10 + 1 = 31\\) 元です。</li> </ol> <p></p> <p> 図 1-3   お釣りを渡す過程 </p> <p>この手順では、毎回その時点で最もよい選択、つまりできるだけ大きい額面を選び、最終的に実行可能なお釣りの渡し方を得ました。データ構造とアルゴリズムの観点では、これは「貪欲法」です。</p> <p>一皿の料理から恒星間航行まで、ほとんどあらゆる問題の解決にアルゴリズムが関わっています。コンピュータの登場により、データ構造をプログラミングでメモリに格納し、CPU や GPU にアルゴリズムを実行させるコードを書けるようになりました。こうして、身の回りの問題をコンピュータへ移し、さまざまな複雑な問題をより効率的に解決できます。</p> <p>ヒント</p> <p>データ構造、アルゴリズム、配列、二分探索などがまだ十分に分からなくても、そのまま読み進めてください。本書が、データ構造とアルゴリズムの知識の世界へと案内します。</p>","path":["第 1 章   アルゴリズムとの出会い","1.1   アルゴリズムはどこにでもある"],"tags":[]},{"location":"chapter_introduction/summary/","level":1,"title":"1.3   まとめ","text":"","path":["第 1 章   アルゴリズムとの出会い","1.3   まとめ"],"tags":[]},{"location":"chapter_introduction/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>アルゴリズムは日常生活の至るところにあり、手の届かない難解な知識ではありません。私たちはすでに、多くのアルゴリズムを知らず知らずに学び、大小さまざまな問題の解決に使っています。</li> <li>辞書を引く仕組みは二分探索と同じです。二分探索は、重要なアルゴリズムの考え方である分割統治を体現しています。</li> <li>トランプを並べる過程は挿入ソートによく似ています。挿入ソートは、小規模なデータ集合のソートに適しています。</li> <li>お釣りを渡す手順は本質的に貪欲法であり、各手順でその時点で最もよいと思われる選択をします。</li> <li>アルゴリズムは、有限の時間内に特定の問題を解く命令や操作手順の集まりです。データ構造は、コンピュータ内でデータを組織化して格納する方法です。</li> <li>データ構造とアルゴリズムは密接に結び付いています。データ構造はアルゴリズムの土台であり、アルゴリズムはデータ構造に生命を吹き込みます。</li> <li>データ構造とアルゴリズムは、ブロックの組み立てに例えられます。ブロックはデータ、形や接続方法はデータ構造、組み立ての手順はアルゴリズムに対応します。</li> </ul>","path":["第 1 章   アルゴリズムとの出会い","1.3   まとめ"],"tags":[]},{"location":"chapter_introduction/summary/#2-q-a","level":3,"title":"2.   Q &amp; A","text":"<p>Q:プログラマーとして日々働いていますが、アルゴリズムで問題を解いたことはありません。一般的なアルゴリズムは言語に組み込まれており、そのまま使えます。これは、仕事上の問題がまだアルゴリズムを必要とする段階に達していないということでしょうか?</p> <p>具体的な仕事のスキルを武術の「技」に例えるなら、基礎科目はむしろ「内功」、つまり内に蓄えた力に近いでしょう。</p> <p>アルゴリズムやほかの基礎科目を学ぶ意義は、仕事でゼロから実装することではなく、学んだ知識を基に専門的な対応や判断を行い、仕事全体の質を高めることにあると私は考えます。簡単な例として、各プログラミング言語に組み込まれたソート関数を考えましょう。</p> <ul> <li>データ構造とアルゴリズムを学んでいなければ、どんなデータもその関数に渡すかもしれません。動作は滑らかで性能もよく、問題はないように見えます。</li> <li>学んでいれば、組み込みソートの時間計算量が \\(O(n \\log n)\\) だと分かります。データが学籍番号のような固定桁数の整数なら、さらに効率のよい「基数ソート」で \\(O(nk)\\) に減らせます。ここで \\(k\\) は桁数です。データが非常に多ければ、短縮した実行時間はコスト削減や利用体験の改善など、大きな価値を生みます。</li> </ul> <p>エンジニアリングでは最適解に達するのが難しい問題が多く、数多くの問題が「そこそこ」の解決にとどまっています。難しさは問題そのものの性質だけでなく、それを見る人の知識にも左右されます。知識が充実し経験が増えるほど、分析は深まり、問題をより見事に解けるようになります。</p> <p>Q:今ではほとんどのコードを AI エージェントに書いてもらいます。それでもアルゴリズムを学ぶ必要はありますか?</p> <p>李沐先生の言葉を引用します。「エージェントは能力の増幅器、つまり能力 x10 であって、加算器、つまり能力 +10 ではない」。</p> <p>AI が能力の増幅器なら、元になる「能力」も重要です。特に二つを蓄える価値があると思います。一つは表現力、特に AI と協働する力です。目標を明確にし、背景を補い、ずれを早めに修正します。AI に問題を説明することは、自分の考えを整理することでもあります。もう一つは基礎知識です。細部は AI に補ってもらえますが、基本概念を自分で理解してこそ、要点を突く質問ができ、正誤を判断し、出力を役立つ成果に変えられます。</p> <p>AI の特徴的な点は、従来の道具のように機能の境界が明確でないことです。水のように、使う人によって異なる形になります。人の能力が高く、センスがよいほど、AI の増幅効果もはっきり表れます。</p>","path":["第 1 章   アルゴリズムとの出会い","1.3   まとめ"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/","level":1,"title":"1.2   アルゴリズムとは","text":"","path":["第 1 章   アルゴリズムとの出会い","1.2   アルゴリズムとは"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#121","level":2,"title":"1.2.1   アルゴリズムの定義","text":"<p>アルゴリズム(algorithm)は、有限の時間内に特定の問題を解くための命令や操作手順の集まりで、次の特徴を持ちます。</p> <ul> <li>問題が明確であり、入力と出力がはっきり定義されています。</li> <li>実行可能であり、有限の手順、時間、メモリ領域で完了できます。</li> <li>各手順の意味が明確で、同じ入力と実行条件なら、出力は常に同じになります。</li> </ul>","path":["第 1 章   アルゴリズムとの出会い","1.2   アルゴリズムとは"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#122","level":2,"title":"1.2.2   データ構造の定義","text":"<p>データ構造(data structure)は、データを組織化して格納する方法です。データの内容、データ間の関係、操作方法を含み、次の設計目標を持ちます。</p> <ul> <li>使用領域をできるだけ減らし、コンピュータのメモリを節約します。</li> <li>アクセス、追加、削除、更新などの操作をできるだけ速くします。</li> <li>データと論理的な情報を簡潔に表現し、アルゴリズムが効率よく動作できるようにします。</li> </ul> <p>データ構造の設計は、さまざまなトレードオフを伴う過程です。ある面を改善するには、別の面で妥協が必要になることがよくあります。二つ例を挙げます。</p> <ul> <li>連結リストは配列より追加や削除が容易ですが、データへのアクセス速度を犠牲にしています。</li> <li>グラフは連結リストより豊かな論理情報を表せますが、より多くのメモリを使います。</li> </ul>","path":["第 1 章   アルゴリズムとの出会い","1.2   アルゴリズムとは"],"tags":[]},{"location":"chapter_introduction/what_is_dsa/#123","level":2,"title":"1.2.3   データ構造とアルゴリズムの関係","text":"<p>図 1-4 のように、データ構造とアルゴリズムは強く関連し、密接に結び付いています。具体的には次の三点に表れます。</p> <ul> <li>データ構造はアルゴリズムの土台です。構造化して格納したデータと、それを操作する方法を提供します。</li> <li>アルゴリズムはデータ構造に生命を吹き込みます。データ構造自体は情報を格納するだけですが、アルゴリズムと組み合わせると特定の問題を解けます。</li> <li>通常、一つのアルゴリズムは異なるデータ構造で実装できますが、実行効率には大きな差が生じる場合があります。適切なデータ構造の選択が鍵となります。</li> </ul> <p></p> <p> 図 1-4   データ構造とアルゴリズムの関係 </p> <p>データ構造とアルゴリズムは、図 1-5 の組み立てブロックに似ています。ブロックのセットには多くの部品と詳しい組み立て説明書が付いています。その手順に従えば、見事な模型を組み立てられます。</p> <p></p> <p> 図 1-5   ブロックの組み立て </p> <p>詳しい対応関係を表 1-1 に示します。</p> <p> 表 1-1   データ構造とアルゴリズムを組み立てブロックに例える </p> データ構造とアルゴリズム 組み立てブロック 入力データ 組み立て前のブロック データ構造 形、大きさ、接続方法など、ブロックの構成 アルゴリズム 目標の形へ組み立てるための一連の操作手順 出力データ ブロックの模型 <p>データ構造とアルゴリズムはプログラミング言語に依存しません。だからこそ、本書では複数の言語による実装を提供できています。</p> <p>慣用的な略称</p> <p>実際の議論では、「データ構造とアルゴリズム」を「アルゴリズム」と略すことがよくあります。例えば、よく知られる LeetCode のアルゴリズム問題も、実際にはデータ構造とアルゴリズムの両方の知識を問うものです。</p>","path":["第 1 章   アルゴリズムとの出会い","1.2   アルゴリズムとは"],"tags":[]},{"location":"chapter_preface/","level":1,"title":"第 0 章   はじめに","text":"<p>概要</p> <p>アルゴリズムは美しい交響曲のようであり、一行一行のコードが旋律となって流れます。</p> <p>この本があなたの心の中で静かに響き、独特で深い旋律を残すことを願っています。</p>","path":["第 0 章   はじめに"],"tags":[]},{"location":"chapter_preface/#_1","level":2,"title":"この章の内容","text":"<ul> <li>0.1   本書について</li> <li>0.2   本書の使い方</li> <li>0.3   まとめ</li> </ul>","path":["第 0 章   はじめに"],"tags":[]},{"location":"chapter_preface/about_the_book/","level":1,"title":"0.1   本書について","text":"<p>本プロジェクトは、オープンソースで無料、かつ初学者に親しみやすいデータ構造とアルゴリズムの入門書を作ることを目指しています。</p> <ul> <li>全編にアニメーション図解を用い、明快な内容と緩やかな学習曲線で、初学者をデータ構造とアルゴリズムの知識の地図へと案内します。</li> <li>ソースコードはワンクリックで実行でき、実践を通してプログラミング技能を高め、アルゴリズムの仕組みやデータ構造の内部実装を理解できます。</li> <li>読者同士が助け合って学ぶことを勧めています。コメント欄で質問や考えを共有し、対話を通して一緒に成長しましょう。</li> </ul>","path":["第 0 章   はじめに","0.1   本書について"],"tags":[]},{"location":"chapter_preface/about_the_book/#011","level":2,"title":"0.1.1   対象読者","text":"<p>アルゴリズムを初めて学ぶ人、まだ触れたことがない人、あるいは問題演習を少し経験していても、データ構造とアルゴリズムの理解が曖昧で、分かったり分からなくなったりする人に、本書はぴったりです。</p> <p>すでに多くの問題を解き、大部分の問題形式になじみがある人には、知識を復習し、体系を整理する助けになります。リポジトリのソースコードは、「問題演習のツール集」や「アルゴリズム辞典」として使えます。</p> <p>アルゴリズムの達人の皆さんからは、貴重なご意見をお待ちしています。執筆への参加も歓迎します。</p> <p>前提知識</p> <p>少なくとも一つの言語のプログラミングの基礎があり、簡単なコードを読み書きできることが必要です。</p>","path":["第 0 章   はじめに","0.1   本書について"],"tags":[]},{"location":"chapter_preface/about_the_book/#012","level":2,"title":"0.1.2   内容の構成","text":"<p>本書の主な内容を図 0-1 に示します。</p> <ul> <li>計算量解析:データ構造とアルゴリズムの評価の観点と方法を扱います。時間計算量と空間計算量の求め方、主な種類、例などを紹介します。</li> <li>データ構造:基本データ型とデータ構造の分類方法を扱います。配列、連結リスト、スタック、キュー、ハッシュテーブル、木、ヒープ、グラフなどの定義、長所と短所、主な操作、種類、代表的な用途、実装方法などを紹介します。</li> <li>アルゴリズム:探索、ソート、分割統治、バックトラッキング、動的計画法、貪欲法などの定義、長所と短所、効率、用途、問題を解く手順、例題などを扱います。</li> </ul> <p></p> <p> 図 0-1   本書の主な内容 </p>","path":["第 0 章   はじめに","0.1   本書について"],"tags":[]},{"location":"chapter_preface/about_the_book/#013","level":2,"title":"0.1.3   謝辞","text":"<p>本書は、オープンソースコミュニティの多くの貢献者の協力によって改善を続けています。時間と労力を注いでくださった執筆者の皆さんに感謝します。お名前は次のとおりです(GitHub が自動生成した順序):krahets、coderonion、Gonglja、nuomi1、Reanon、justin-tse、hpstory、danielsss、curtishd、night-cruise、S-N-O-R-L-A-X、rongyi、msk397、gvenusleo、khoaxuantu、rivertwilight、K3v123、gyt95、zhuoqinyue、yuelinxin、Zuoxun、mingXta、Phoenix0415、FangYuan33、GN-Yu、longsizhuo、pengchzn、QiLOL、Cathay-Chen、guowei-gong、xBLACKICEx、IsChristina、JoseHung、qualifier1024、hello-ikun、magentaqin、Guanngxu、thomasq0、sunshinesDL、L-Super、Transmigration-zhou、WSL0809、Slone123c、lhxsm、yuan0221、what-is-me、theNefelibatas、Shyam-Chen、sangxiaai、longranger2、codeberg-user、xiongsp、JeffersonHuang、prinpal、seven1240、Wonderdch、malone6、xiaomiusa87、gaofer、bluebean-cloud、a16su、SamJin98、hongyun-robot、nanlei、XiaChuerwu、yd-j、iron-irax、mgisr、steventimes、junminhong、heshuyue、danny900714、Nigh、Dr-XYZ、MolDuM、XC-Zero、reeswell、PXG-XPG、NI-SW、Horbin-Magician、Enlightenus、YangXuanyi、xjr7670、beatrix-chan、DullSword、qq909244296、iStig、boloboloda、hts0000、gledfish、fbigm、echo1937、jiaxianhua、wenjianmin、keshida、kilikilikid、lclc6、lwbaptx、linyejoe2、liuxjerry、szu17dmy、dshlstarr、Yucao-cy、coderlef、czruby、bongbongbakudan、beintentional、ZongYangL、ZhongYuuu、ZhongGuanbin、hezhizhen、linzeyan、ZJKung、JTCPOWI、KawaiiAsh、luluxia、xb534、ztkuaikuai、yw-1021、ElaBosak233、baagod、zhouLion、yishangzhang、yi427、yanedie、yabo083、weibk、wangwang105、th1nk3r-ing、tao363、4yDX3906、syd168、sslmj2020、smilelsb、siqyka、selear、sdshaoda、Xi-Row、popozhu、nuquist19、noobcodemaker、XiaoK29、chadyi、lyl625760、lucaswangdev、llql1211、0130w、shanghai-Jerry、EJackYang、Javesun99、eltociear、lipusheng、KNChiu、BlindTerran、ShiMaRing、lovelock、FreddieLi、FloranceYeh、fanchenggang、gltianwen、goerll、nedchu、curly210102、CuB3y0nd、KraHsu、CarrotDLaw、youshaoXG、bubble9um、Asashishi、Asa0oo0o0o、fanenr、eagleanurag、akshiterate、52coder、foursevenlove、KorsChen、hopkings2008、yang-le、realwujing、Evilrabbit520、Umer-Jahangir、Turing-1024-Lee、Suremotoo、paoxiaomooo、Chieko-Seren、Senrian、Allen-Scai、19santosh99、ymmmas、Risuntsy、Richard-Zhang1019、RafaelCaso、qingpeng9802、primexiao、Urbaner3、codetypess、nidhoggfgg、MwumLi、CreatorMetaSky、martinx、ZnYang2018、hugtyftg、logan-qiu、psychelzh、Kunchen-Luo、Keynman、KeiichiKasai。</p> <p>本書のコードレビューは、coderonion、curtishd、Gonglja、gvenusleo、hpstory、justin-tse、khoaxuantu、krahets、night-cruise、nuomi1、Reanon、rongyi が担当しました(アルファベット順)。各言語のコードの規範と統一性を支えた皆さんの時間と労力に感謝します。</p> <p>英語版は yuelinxin、K3v123、magentaqin、QiLOL、Phoenix0415、SamJin98、yanedie、RafaelCaso、pengchzn、thomasq0、既存の日本語版は eltociear、ロシア語版は И. А. Шевкун と Yuyan Huang、繁体字中国語版は Shyam-Chen と Dr-XYZ がレビューしました。皆さんの貢献によって、より幅広い読者に届けられるようになりました。感謝します。</p> <p>本書の ePub 電子書籍生成ツールは zhongfq が開発しました。読者に、より柔軟な読書の方法を提供してくれたことに感謝します。</p> <p>執筆の過程では、多くの方に助けていただきました。</p> <ul> <li>会社のメンターである李汐博士に感謝します。語り合う中で「すぐに行動しよう」と励ましてくださり、本書を書く決意が固まりました。</li> <li>恋人の泡泡に感謝します。本書の最初の読者として、アルゴリズムの初心者の立場から多くの貴重な意見をくれ、初学者に読みやすい本になりました。</li> <li>腾宝、琦宝、飞宝に感謝します。最初の一行「Hello World!」を書いたときの素敵な記憶を呼び起こす、創造的な書名を考えてくれました。</li> <li>校铨に感謝します。知的財産についての専門的な支援は、このオープンソースの本を整えるうえで重要な役割を果たしました。</li> <li>苏潼に感謝します。美しい表紙とロゴをデザインし、私のこだわりに付き合って何度も根気よく修正してくれました。</li> <li>@squidfunk に、組版への助言と、開発されたオープンソースのドキュメントテーマ Material-for-MkDocs について感謝します。</li> </ul> <p>執筆中、データ構造とアルゴリズムに関する多くの教科書や記事を読みました。それらは優れた手本となり、本書の正確さと品質を支えてくれました。先生方や先人の皆さんの素晴らしい貢献に感謝します。</p> <p>本書は、手を動かしながら考える学習方法を勧めています。この点で『动手学深度学习』(手を動かして学ぶ深層学習)から大きな影響を受けました。この優れた本を、読者の皆さんにも強くお勧めします。</p> <p>両親に心から感謝します。いつも支え、励ましてくれたおかげで、この面白い取り組みに挑戦する機会を得られました。</p>","path":["第 0 章   はじめに","0.1   本書について"],"tags":[]},{"location":"chapter_preface/suggestions/","level":1,"title":"0.2   本書の使い方","text":"<p>ヒント</p> <p>快適に読み進めるために、この節を最後まで読むことを勧めます。</p>","path":["第 0 章   はじめに","0.2   本書の使い方"],"tags":[]},{"location":"chapter_preface/suggestions/#021","level":2,"title":"0.2.1   表記の約束","text":"<ul> <li>見出しの末尾に <code>*</code> が付いた節は、やや難しい発展的な内容です。時間が限られていれば、後回しにしてかまいません。</li> <li>原書では、専門用語には太字(紙の本と PDF 版)または下線(Web 版)を使います。例えば、配列(array)のように表します。文献を読むためにも、用語を覚えることを勧めます。</li> <li>重要な内容や要点をまとめた文は 太字 にしています。特に注意して読んでください。</li> <li>特定の意味を持たせた語句は、曖昧さを避けるために「かぎ括弧」で示します。</li> <li>言語によって呼び方が異なる場合、本書では Python に合わせます。例えば、「空」を表すには <code>None</code> を使います。</li> <li>本書では、コンパクトなレイアウトを優先し、言語のコメント規約に一部従っていません。コメントは主に、見出しコメント、内容コメント、複数行コメントの三種類に分かれます。</li> </ul> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code>\"\"\"見出しコメント:関数、クラス、テストケースなどを示す\"\"\"\n\n# 内容コメント:コードを詳しく説明する\n\n\"\"\"\n複数行の\nコメント\n\"\"\"\n</code></pre> <pre><code>/* 見出しコメント:関数、クラス、テストケースなどを示す */\n\n// 内容コメント:コードを詳しく説明する\n\n/**\n * 複数行の\n * コメント\n */\n</code></pre> <pre><code>/* 見出しコメント:関数、クラス、テストケースなどを示す */\n\n// 内容コメント:コードを詳しく説明する\n\n/**\n * 複数行の\n * コメント\n */\n</code></pre> <pre><code>/* 見出しコメント:関数、クラス、テストケースなどを示す */\n\n// 内容コメント:コードを詳しく説明する\n\n/**\n * 複数行の\n * コメント\n */\n</code></pre> <pre><code>/* 見出しコメント:関数、クラス、テストケースなどを示す */\n\n// 内容コメント:コードを詳しく説明する\n\n/**\n * 複数行の\n * コメント\n */\n</code></pre> <pre><code>/* 見出しコメント:関数、クラス、テストケースなどを示す */\n\n// 内容コメント:コードを詳しく説明する\n\n/**\n * 複数行の\n * コメント\n */\n</code></pre> <pre><code>/* 見出しコメント:関数、クラス、テストケースなどを示す */\n\n// 内容コメント:コードを詳しく説明する\n\n/**\n * 複数行の\n * コメント\n */\n</code></pre> <pre><code>/* 見出しコメント:関数、クラス、テストケースなどを示す */\n\n// 内容コメント:コードを詳しく説明する\n\n/**\n * 複数行の\n * コメント\n */\n</code></pre> <pre><code>/* 見出しコメント:関数、クラス、テストケースなどを示す */\n\n// 内容コメント:コードを詳しく説明する\n\n/**\n * 複数行の\n * コメント\n */\n</code></pre> <pre><code>/* 見出しコメント:関数、クラス、テストケースなどを示す */\n\n// 内容コメント:コードを詳しく説明する\n\n// 複数行の\n// コメント\n</code></pre> <pre><code>/* 見出しコメント:関数、クラス、テストケースなどを示す */\n\n// 内容コメント:コードを詳しく説明する\n\n/**\n * 複数行の\n * コメント\n */\n</code></pre> <pre><code>/* 見出しコメント:関数、クラス、テストケースなどを示す */\n\n// 内容コメント:コードを詳しく説明する\n\n/**\n * 複数行の\n * コメント\n */\n</code></pre> <pre><code>### 見出しコメント:関数、クラス、テストケースなどを示す ###\n\n# 内容コメント:コードを詳しく説明する\n\n# 複数行の\n# コメント\n</code></pre>","path":["第 0 章   はじめに","0.2   本書の使い方"],"tags":[]},{"location":"chapter_preface/suggestions/#022","level":2,"title":"0.2.2   アニメーション図解で効率よく学ぶ","text":"<p>動画や図は文章に比べて情報密度が高く、構造が分かりやすいため、理解しやすいものです。本書では、重要な内容や難しい内容を、主にアニメーションによる図解で示し、文章で説明や補足を加えています。</p> <p>図 0-2 のようなアニメーション図解がある場合は、図を中心に、文章を補助として、両方を合わせて理解してください。</p> <p></p> <p> 図 0-2   アニメーション図解の例 </p>","path":["第 0 章   はじめに","0.2   本書の使い方"],"tags":[]},{"location":"chapter_preface/suggestions/#023","level":2,"title":"0.2.3   コードの実践で理解を深める","text":"<p>本書のコードは GitHub リポジトリで公開しています。図 0-3 のように、ソースコードにはテストケースが付いており、ワンクリックで実行できます。</p> <p>時間があれば、コードを参考に、自分の手で一度書いてみることを勧めます。時間が限られていても、少なくともすべてのコードを通読し、実行してください。</p> <p>コードを読むだけよりも、自分で書く過程から多くのことを学べます。手を動かして学んでこそ、本当の学びになります。</p> <p></p> <p> 図 0-3   コードを実行する例 </p> <p>コードを実行する準備は、主に三つの手順に分かれます。</p> <p>手順一:ローカルのプログラミング環境をインストールします。付録の手順を参照してください。すでにインストール済みなら、この手順は省略できます。</p> <p>手順二:コードのリポジトリをクローンまたはダウンロードします。GitHub リポジトリを開きます。Git がインストール済みなら、次のコマンドでクローンできます。</p> <pre><code>git clone https://github.com/krahets/hello-algo.git\n</code></pre> <p>もちろん、図 0-4 の位置にある「Download ZIP」をクリックして圧縮ファイルを直接ダウンロードし、ローカルで展開してもかまいません。</p> <p></p> <p> 図 0-4   リポジトリのクローンとコードのダウンロード </p> <p>手順三:ソースコードを実行します。図 0-5 のように、上部にファイル名があるコードブロックは、リポジトリの <code>ja/codes</code> フォルダ内に対応するソースファイルがあります。ワンクリックで実行できるので、不要なデバッグに時間を取られず、学習内容に集中できます。</p> <p></p> <p> 図 0-5   コードブロックと対応するソースファイル </p> <p>ローカルでの実行に加え、Web 版では Python コードの可視化実行にも対応しています(pythontutor を使用)。図 0-6 のように、コードブロックの下の「実行の可視化」をクリックすると表示が開き、アルゴリズムの実行過程を観察できます。「全画面で表示」をクリックすれば、さらに見やすくなります。</p> <p></p> <p> 図 0-6   Python コードの可視化実行 </p>","path":["第 0 章   はじめに","0.2   本書の使い方"],"tags":[]},{"location":"chapter_preface/suggestions/#024","level":2,"title":"0.2.4   質問や議論を通して共に成長する","text":"<p>読む際には、十分に理解できなかった内容を安易に飛ばさないでください。コメント欄での質問を歓迎します。私たちができる限りお答えします。通常は二日以内に返信できます。</p> <p>図 0-7 のように、Web 版の各節の末尾にはコメント欄があります。ぜひその内容にも目を向けてください。他の読者の疑問を知ることで、理解の抜けを補い、さらに深く考えられます。また、他の読者の質問に答え、考えを共有して、学びを支えていただければうれしく思います。</p> <p></p> <p> 図 0-7   コメント欄の例 </p>","path":["第 0 章   はじめに","0.2   本書の使い方"],"tags":[]},{"location":"chapter_preface/suggestions/#025","level":2,"title":"0.2.5   アルゴリズムの学習の道筋","text":"<p>データ構造とアルゴリズムの学習は、大きく三つの段階に分けられます。</p> <ol> <li>段階一:アルゴリズムの入門。さまざまなデータ構造の特徴と使い方を知り、各アルゴリズムの原理、手順、用途、効率などを学びます。</li> <li>段階二:アルゴリズムの問題演習。定番の問題から始め、まずは少なくとも 100 問を解いて、主要な問題形式になじむことを勧めます。最初は知識を忘れることに悩むかもしれませんが、ごく自然なことなので心配はいりません。「エビングハウスの忘却曲線」に沿って復習し、通常 3~5 回繰り返すと、しっかり記憶できます。おすすめの問題集と演習計画は、この GitHub リポジトリを参照してください。</li> <li>段階三:知識体系の構築。学習では、アルゴリズムの解説記事、解法の枠組み、教科書を読んで知識体系を充実させます。演習では、テーマ別に取り組む、一問を複数の方法で解く、一つの方法を複数の問題に応用するなど、発展的な戦略を試せます。関連する経験談は、さまざまなコミュニティで見つかります。</li> </ol> <p>図 0-8 のように、本書は主に「段階一」を扱い、段階二と段階三の学習をより効率よく進めるための支援を目指しています。</p> <p></p> <p> 図 0-8   アルゴリズムの学習の道筋 </p>","path":["第 0 章   はじめに","0.2   本書の使い方"],"tags":[]},{"location":"chapter_preface/summary/","level":1,"title":"0.3   まとめ","text":"","path":["第 0 章   はじめに","0.3   まとめ"],"tags":[]},{"location":"chapter_preface/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>本書は主にアルゴリズムの初学者を対象としています。すでに基礎知識がある読者にとっては、アルゴリズムを体系的に復習する助けとなり、掲載したソースコードは問題演習のための「ツール集」としても利用できます。</li> <li>本書は主に計算量解析、データ構造、アルゴリズムの三部からなり、この分野の大部分のテーマを扱っています。</li> <li>アルゴリズムを初めて学ぶ段階では、入門書を一冊読むことが非常に重要であり、多くの遠回りを避けられます。</li> <li>本書のアニメーション図解では、重要な内容や難しい内容を説明することが多いです。読む際は、これらに特に注意を向けてください。</li> <li>実践はプログラミングを学ぶ最良の方法です。ソースコードを実行し、自分の手でコードを書くことを強く勧めます。</li> <li>本書の Web 版では各節にコメント欄を設けています。疑問や考えをいつでも共有してください。</li> </ul>","path":["第 0 章   はじめに","0.3   まとめ"],"tags":[]},{"location":"chapter_reference/","level":1,"title":"参考文献","text":"<p>[1] Thomas H. Cormen, et al. Introduction to Algorithms (3<sup>rd</sup> Edition).</p> <p>[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1<sup>st</sup> Edition).</p> <p>[3] Robert Sedgewick, et al. Algorithms (4<sup>th</sup> Edition).</p> <p>[4] 严蔚敏. 数据结构(C 语言版).</p> <p>[5] 邓俊辉. 数据结构(C++ 语言版,第三版).</p> <p>[6] 马克 艾伦 维斯 著、陈越 訳. 数据结构与算法分析:Java语言描述(第三版).</p> <p>[7] 程杰. 大话数据结构.</p> <p>[8] 王争. 数据结构与算法之美.</p> <p>[9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6<sup>th</sup> Edition).</p> <p>[10] Aston Zhang, et al. Dive into Deep Learning.</p>","path":["参考文献"],"tags":[]},{"location":"chapter_searching/","level":1,"title":"第 10 章   探索","text":"<p>概要</p> <p>探索は未知への冒険です。神秘的な空間の隅々まで歩く必要があるかもしれず、目的を素早く見つけられるかもしれません。</p> <p>この探し求める旅では、一度の探索が思いもよらない答えをもたらすこともあります。</p>","path":["第 10 章   探索"],"tags":[]},{"location":"chapter_searching/#_1","level":2,"title":"この章の内容","text":"<ul> <li>10.1   二分探索</li> <li>10.2   二分探索で挿入位置を探す</li> <li>10.3   二分探索で境界を探す</li> <li>10.4   ハッシュを使った最適化</li> <li>10.5   探索アルゴリズムを改めて考える</li> <li>10.6   まとめ</li> <li>10.7   練習問題</li> </ul>","path":["第 10 章   探索"],"tags":[]},{"location":"chapter_searching/binary_search/","level":1,"title":"10.1   二分探索","text":"<p>二分探索(binary search)は、分割統治法に基づく効率的な探索アルゴリズムです。データが整列されていることを利用し、各反復で探索範囲を半分に縮め、目的の要素が見つかるか探索区間が空になるまで続けます。</p> <p>問題</p> <p>長さ \\(n\\) の配列 <code>nums</code> が与えられます。要素は昇順に並び、重複しません。要素 <code>target</code> を探し、そのインデックスを返してください。配列にその要素が含まれない場合は \\(-1\\) を返します。例を図 10-1 に示します。</p> <p></p> <p> 図 10-1   二分探索の入力例 </p> <p>図 10-2 のように、まずポインタ \\(i = 0\\) と \\(j = n - 1\\) を初期化し、配列の先頭要素と末尾要素をそれぞれ指すようにします。これらは探索区間 \\([0, n - 1]\\) を表します。角括弧は閉区間を表し、境界値そのものを含むことに注意してください。</p> <p>続いて、次の二つの手順を繰り返します。</p> <ol> <li>中点のインデックス \\(m = \\lfloor {(i + j) / 2} \\rfloor\\) を計算します。ここで \\(\\lfloor \\: \\rfloor\\) は切り捨てを表します。</li> <li><code>nums[m]</code> と <code>target</code> の大小関係を判定します。次の三つの場合があります。<ol> <li><code>nums[m] &lt; target</code> なら、<code>target</code> は区間 \\([m + 1, j]\\) にあるため、\\(i = m + 1\\) とします。</li> <li><code>nums[m] &gt; target</code> なら、<code>target</code> は区間 \\([i, m - 1]\\) にあるため、\\(j = m - 1\\) とします。</li> <li><code>nums[m] = target</code> なら、<code>target</code> が見つかったので、インデックス \\(m\\) を返します。</li> </ol> </li> </ol> <p>配列に目的の要素が含まれない場合、探索区間は最終的に空になります。このときは \\(-1\\) を返します。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 10-2   二分探索の流れ </p> <p>\\(i\\) と \\(j\\) はともに <code>int</code> 型なので、\\(i + j\\) が <code>int</code> 型の値の範囲を超える可能性があることに注意してください。大きな値によるオーバーフローを避けるため、通常は \\(m = \\lfloor {i + (j - i) / 2} \\rfloor\\) という式で中点を計算します。</p> <p>コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search.py<pre><code>def binary_search(nums: list[int], target: int) -&gt; int:\n \"\"\"二分探索(閉区間)\"\"\"\n # 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n i, j = 0, len(nums) - 1\n # 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while i &lt;= j:\n # 理論上、Python の数値は無限に大きくできる(メモリ容量による)ため、大きな数のオーバーフローを考慮する必要がない\n m = (i + j) // 2 # 中央のインデックス m を計算する\n if nums[m] &lt; target:\n i = m + 1 # この場合、target は区間 [m+1, j] に存在する\n elif nums[m] &gt; target:\n j = m - 1 # この場合、target は区間 [i, m-1] に存在する\n else:\n return m # 目的の要素が見つかったので、そのインデックスを返す\n return -1 # 目的の要素が見つからなかったので、-1 を返す\n</code></pre> binary_search.cpp<pre><code>/* 二分探索(閉区間) */\nint binarySearch(vector&lt;int&gt; &amp;nums, int target) {\n // 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n int i = 0, j = nums.size() - 1;\n // 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) // この場合、target は区間 [m+1, j] に存在する\n i = m + 1;\n else if (nums[m] &gt; target) // この場合、target は区間 [i, m-1] に存在する\n j = m - 1;\n else // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.java<pre><code>/* 二分探索(閉区間) */\nint binarySearch(int[] nums, int target) {\n // 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n int i = 0, j = nums.length - 1;\n // 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) // この場合、target は区間 [m+1, j] に存在する\n i = m + 1;\n else if (nums[m] &gt; target) // この場合、target は区間 [i, m-1] に存在する\n j = m - 1;\n else // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.cs<pre><code>/* 二分探索(閉区間) */\nint BinarySearch(int[] nums, int target) {\n // 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n int i = 0, j = nums.Length - 1;\n // 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) // この場合、target は区間 [m+1, j] に存在する\n i = m + 1;\n else if (nums[m] &gt; target) // この場合、target は区間 [i, m-1] に存在する\n j = m - 1;\n else // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.go<pre><code>/* 二分探索(閉区間) */\nfunc binarySearch(nums []int, target int) int {\n // 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n i, j := 0, len(nums)-1\n // 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n for i &lt;= j {\n m := i + (j-i)/2 // 中央のインデックス m を計算する\n if nums[m] &lt; target { // この場合、target は区間 [m+1, j] に存在する\n i = m + 1\n } else if nums[m] &gt; target { // この場合、target は区間 [i, m-1] に存在する\n j = m - 1\n } else { // 目的の要素が見つかったので、そのインデックスを返す\n return m\n }\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1\n}\n</code></pre> binary_search.swift<pre><code>/* 二分探索(閉区間) */\nfunc binarySearch(nums: [Int], target: Int) -&gt; Int {\n // 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n var i = nums.startIndex\n var j = nums.endIndex - 1\n // 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while i &lt;= j {\n let m = i + (j - i) / 2 // 中央のインデックス m を計算する\n if nums[m] &lt; target { // この場合、target は区間 [m+1, j] に存在する\n i = m + 1\n } else if nums[m] &gt; target { // この場合、target は区間 [i, m-1] に存在する\n j = m - 1\n } else { // 目的の要素が見つかったので、そのインデックスを返す\n return m\n }\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1\n}\n</code></pre> binary_search.js<pre><code>/* 二分探索(閉区間) */\nfunction binarySearch(nums, target) {\n // 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n let i = 0,\n j = nums.length - 1;\n // 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while (i &lt;= j) {\n // 中央のインデックス m を計算し、parseInt() で小数点以下を切り捨てる\n const m = parseInt(i + (j - i) / 2);\n if (nums[m] &lt; target)\n // この場合、target は区間 [m+1, j] に存在する\n i = m + 1;\n else if (nums[m] &gt; target)\n // この場合、target は区間 [i, m-1] に存在する\n j = m - 1;\n else return m; // 目的の要素が見つかったので、そのインデックスを返す\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.ts<pre><code>/* 二分探索(閉区間) */\nfunction binarySearch(nums: number[], target: number): number {\n // 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n let i = 0,\n j = nums.length - 1;\n // 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while (i &lt;= j) {\n // 中央のインデックス m を計算する\n const m = Math.floor(i + (j - i) / 2);\n if (nums[m] &lt; target) {\n // この場合、target は区間 [m+1, j] に存在する\n i = m + 1;\n } else if (nums[m] &gt; target) {\n // この場合、target は区間 [i, m-1] に存在する\n j = m - 1;\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n }\n return -1; // 目的の要素が見つからなかったので、-1 を返す\n}\n</code></pre> binary_search.dart<pre><code>/* 二分探索(閉区間) */\nint binarySearch(List&lt;int&gt; nums, int target) {\n // 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n int i = 0, j = nums.length - 1;\n // 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while (i &lt;= j) {\n int m = i + (j - i) ~/ 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n // この場合、target は区間 [m+1, j] に存在する\n i = m + 1;\n } else if (nums[m] &gt; target) {\n // この場合、target は区間 [i, m-1] に存在する\n j = m - 1;\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.rs<pre><code>/* 二分探索(閉区間) */\nfn binary_search(nums: &amp;[i32], target: i32) -&gt; i32 {\n // 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n let mut i = 0;\n let mut j = nums.len() as i32 - 1;\n // 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while i &lt;= j {\n let m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if nums[m as usize] &lt; target {\n // この場合、target は区間 [m+1, j] に存在する\n i = m + 1;\n } else if nums[m as usize] &gt; target {\n // この場合、target は区間 [i, m-1] に存在する\n j = m - 1;\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.c<pre><code>/* 二分探索(閉区間) */\nint binarySearch(int *nums, int len, int target) {\n // 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n int i = 0, j = len - 1;\n // 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) // この場合、target は区間 [m+1, j] に存在する\n i = m + 1;\n else if (nums[m] &gt; target) // この場合、target は区間 [i, m-1] に存在する\n j = m - 1;\n else // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.kt<pre><code>/* 二分探索(閉区間) */\nfun binarySearch(nums: IntArray, target: Int): Int {\n // 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n var i = 0\n var j = nums.size - 1\n // 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while (i &lt;= j) {\n val m = i + (j - i) / 2 // 中央のインデックス m を計算する\n if (nums[m] &lt; target) // この場合、target は区間 [m+1, j] に存在する\n i = m + 1\n else if (nums[m] &gt; target) // この場合、target は区間 [i, m-1] に存在する\n j = m - 1\n else // 目的の要素が見つかったので、そのインデックスを返す\n return m\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1\n}\n</code></pre> binary_search.rb<pre><code>### 二分探索(閉区間) ###\ndef binary_search(nums, target)\n # 閉区間 [0, n-1] を初期化する。i, j は配列の先頭要素と末尾要素をそれぞれ指す\n i, j = 0, nums.length - 1\n\n # 探索区間が空になるまで繰り返す(i &gt; j のとき空になる)\n while i &lt;= j\n # 理論上、Ruby の数値は無限に大きくできる(メモリ容量による)ため、大きな数のオーバーフローを考慮する必要がない\n m = (i + j) / 2 # 中央のインデックス m を計算する\n\n if nums[m] &lt; target\n i = m + 1 # この場合、target は区間 [m+1, j] に存在する\n elsif nums[m] &gt; target\n j = m - 1 # この場合、target は区間 [i, m-1] に存在する\n else\n return m # 目的の要素が見つかったので、そのインデックスを返す\n end\n end\n\n -1 # 目的の要素が見つからなかったので、-1 を返す\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>時間計算量は \\(O(\\log n)\\):二分するループでは各反復で区間が半分になるため、反復回数は \\(\\log_2 n\\) です。</p> <p>空間計算量は \\(O(1)\\):ポインタ \\(i\\) と \\(j\\) が使う領域は定数サイズです。</p>","path":["第 10 章   探索","10.1   二分探索"],"tags":[]},{"location":"chapter_searching/binary_search/#1011","level":2,"title":"10.1.1   区間の表し方","text":"<p>前述の両端を含む閉区間のほか、「左閉右開」の区間もよく使われます。\\([0, n)\\) と表し、左端は含みますが右端は含みません。この表し方では、区間 \\([i, j)\\) は \\(i = j\\) のとき空になります。</p> <p>この表し方でも、同じ機能を持つ二分探索を実装できます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search.py<pre><code>def binary_search_lcro(nums: list[int], target: int) -&gt; int:\n \"\"\"二分探索(左閉右開区間)\"\"\"\n # 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n i, j = 0, len(nums)\n # 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while i &lt; j:\n m = (i + j) // 2 # 中央のインデックス m を計算する\n if nums[m] &lt; target:\n i = m + 1 # この場合、target は区間 [m+1, j) に存在する\n elif nums[m] &gt; target:\n j = m # この場合、target は区間 [i, m) に存在する\n else:\n return m # 目的の要素が見つかったので、そのインデックスを返す\n return -1 # 目的の要素が見つからなかったので、-1 を返す\n</code></pre> binary_search.cpp<pre><code>/* 二分探索(左閉右開区間) */\nint binarySearchLCRO(vector&lt;int&gt; &amp;nums, int target) {\n // 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n int i = 0, j = nums.size();\n // 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while (i &lt; j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) // この場合、target は区間 [m+1, j) に存在する\n i = m + 1;\n else if (nums[m] &gt; target) // この場合、target は区間 [i, m) に存在する\n j = m;\n else // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.java<pre><code>/* 二分探索(左閉右開区間) */\nint binarySearchLCRO(int[] nums, int target) {\n // 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n int i = 0, j = nums.length;\n // 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while (i &lt; j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) // この場合、target は区間 [m+1, j) に存在する\n i = m + 1;\n else if (nums[m] &gt; target) // この場合、target は区間 [i, m) に存在する\n j = m;\n else // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.cs<pre><code>/* 二分探索(左閉右開区間) */\nint BinarySearchLCRO(int[] nums, int target) {\n // 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n int i = 0, j = nums.Length;\n // 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while (i &lt; j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) // この場合、target は区間 [m+1, j) に存在する\n i = m + 1;\n else if (nums[m] &gt; target) // この場合、target は区間 [i, m) に存在する\n j = m;\n else // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.go<pre><code>/* 二分探索(左閉右開区間) */\nfunc binarySearchLCRO(nums []int, target int) int {\n // 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n i, j := 0, len(nums)\n // 探索区間が空になるまで繰り返す(i = j のとき空になる)\n for i &lt; j {\n m := i + (j-i)/2 // 中央のインデックス m を計算する\n if nums[m] &lt; target { // この場合、target は区間 [m+1, j) に存在する\n i = m + 1\n } else if nums[m] &gt; target { // この場合、target は区間 [i, m) に存在する\n j = m\n } else { // 目的の要素が見つかったので、そのインデックスを返す\n return m\n }\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1\n}\n</code></pre> binary_search.swift<pre><code>/* 二分探索(左閉右開区間) */\nfunc binarySearchLCRO(nums: [Int], target: Int) -&gt; Int {\n // 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n var i = nums.startIndex\n var j = nums.endIndex\n // 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while i &lt; j {\n let m = i + (j - i) / 2 // 中央のインデックス m を計算する\n if nums[m] &lt; target { // この場合、target は区間 [m+1, j) に存在する\n i = m + 1\n } else if nums[m] &gt; target { // この場合、target は区間 [i, m) に存在する\n j = m\n } else { // 目的の要素が見つかったので、そのインデックスを返す\n return m\n }\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1\n}\n</code></pre> binary_search.js<pre><code>/* 二分探索(左閉右開区間) */\nfunction binarySearchLCRO(nums, target) {\n // 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n let i = 0,\n j = nums.length;\n // 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while (i &lt; j) {\n // 中央のインデックス m を計算し、parseInt() で小数点以下を切り捨てる\n const m = parseInt(i + (j - i) / 2);\n if (nums[m] &lt; target)\n // この場合、target は区間 [m+1, j) に存在する\n i = m + 1;\n else if (nums[m] &gt; target)\n // この場合、target は区間 [i, m) に存在する\n j = m;\n // 目的の要素が見つかったので、そのインデックスを返す\n else return m;\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.ts<pre><code>/* 二分探索(左閉右開区間) */\nfunction binarySearchLCRO(nums: number[], target: number): number {\n // 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n let i = 0,\n j = nums.length;\n // 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while (i &lt; j) {\n // 中央のインデックス m を計算する\n const m = Math.floor(i + (j - i) / 2);\n if (nums[m] &lt; target) {\n // この場合、target は区間 [m+1, j) に存在する\n i = m + 1;\n } else if (nums[m] &gt; target) {\n // この場合、target は区間 [i, m) に存在する\n j = m;\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n }\n return -1; // 目的の要素が見つからなかったので、-1 を返す\n}\n</code></pre> binary_search.dart<pre><code>/* 二分探索(左閉右開区間) */\nint binarySearchLCRO(List&lt;int&gt; nums, int target) {\n // 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n int i = 0, j = nums.length;\n // 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while (i &lt; j) {\n int m = i + (j - i) ~/ 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n // この場合、target は区間 [m+1, j) に存在する\n i = m + 1;\n } else if (nums[m] &gt; target) {\n // この場合、target は区間 [i, m) に存在する\n j = m;\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.rs<pre><code>/* 二分探索(左閉右開区間) */\nfn binary_search_lcro(nums: &amp;[i32], target: i32) -&gt; i32 {\n // 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n let mut i = 0;\n let mut j = nums.len() as i32;\n // 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while i &lt; j {\n let m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if nums[m as usize] &lt; target {\n // この場合、target は区間 [m+1, j) に存在する\n i = m + 1;\n } else if nums[m as usize] &gt; target {\n // この場合、target は区間 [i, m) に存在する\n j = m;\n } else {\n // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.c<pre><code>/* 二分探索(左閉右開区間) */\nint binarySearchLCRO(int *nums, int len, int target) {\n // 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n int i = 0, j = len;\n // 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while (i &lt; j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) // この場合、target は区間 [m+1, j) に存在する\n i = m + 1;\n else if (nums[m] &gt; target) // この場合、target は区間 [i, m) に存在する\n j = m;\n else // 目的の要素が見つかったので、そのインデックスを返す\n return m;\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1;\n}\n</code></pre> binary_search.kt<pre><code>/* 二分探索(左閉右開区間) */\nfun binarySearchLCRO(nums: IntArray, target: Int): Int {\n // 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n var i = 0\n var j = nums.size\n // 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while (i &lt; j) {\n val m = i + (j - i) / 2 // 中央のインデックス m を計算する\n if (nums[m] &lt; target) // この場合、target は区間 [m+1, j) に存在する\n i = m + 1\n else if (nums[m] &gt; target) // この場合、target は区間 [i, m) に存在する\n j = m\n else // 目的の要素が見つかったので、そのインデックスを返す\n return m\n }\n // 目的の要素が見つからなかったので、-1 を返す\n return -1\n}\n</code></pre> binary_search.rb<pre><code>### 二分探索(左閉右開区間) ###\ndef binary_search_lcro(nums, target)\n # 左閉右開区間 [0, n) を初期化する。i, j は配列の先頭要素と末尾要素の次をそれぞれ指す\n i, j = 0, nums.length\n\n # 探索区間が空になるまで繰り返す(i = j のとき空になる)\n while i &lt; j\n # 中央のインデックス m を計算する\n m = (i + j) / 2\n\n if nums[m] &lt; target\n i = m + 1 # この場合、target は区間 [m+1, j) に存在する\n elsif nums[m] &gt; target\n j = m - 1 # この場合、target は区間 [i, m) に存在する\n else\n return m # 目的の要素が見つかったので、そのインデックスを返す\n end\n end\n\n -1 # 目的の要素が見つからなかったので、-1 を返す\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>図 10-3 のように、区間の表し方が異なると、二分探索の初期化、ループ条件、区間を縮める操作も変わります。</p> <p>両端を含む閉区間では、左右の境界がどちらも含まれるため、ポインタ \\(i\\) と \\(j\\) で区間を縮める操作も対称になります。そのほうが間違いにくいため、一般には両端を含む閉区間で書くことを勧めます。</p> <p></p> <p> 図 10-3   二つの区間の定義 </p>","path":["第 10 章   探索","10.1   二分探索"],"tags":[]},{"location":"chapter_searching/binary_search/#1012","level":2,"title":"10.1.2   長所と制約","text":"<p>二分探索は時間と空間の両面で優れた性能を持ちます。</p> <ul> <li>二分探索は時間効率が高く、データが多い場合、対数時間の計算量が大きな利点となります。例えば、データの大きさが \\(n = 2^{20}\\) のとき、線形探索には \\(2^{20} = 1048576\\) 回の反復が必要ですが、二分探索では \\(\\log_2 2^{20} = 20\\) 回で済みます。</li> <li>二分探索には追加の領域が不要です。ハッシュ探索など、追加領域を必要とする探索アルゴリズムと比べて、領域を節約できます。</li> </ul> <p>ただし、二分探索はすべての場合に適しているわけではありません。主な理由は次のとおりです。</p> <ul> <li>二分探索は整列済みのデータにしか適用できません。入力が未整列の場合、二分探索のためだけにソートすると割に合いません。ソートの時間計算量は通常 \\(O(n \\log n)\\) で、線形探索や二分探索より大きいためです。また、要素を頻繁に挿入する場合は、整列状態を保つために所定の位置へ挿入する必要があり、その時間計算量 \\(O(n)\\) も大きな負担となります。</li> <li>二分探索は配列にしか適用できません。要素に飛び飛びに、つまり非連続にアクセスする必要がありますが、連結リストではその効率が低いため、連結リストやそれに基づくデータ構造には適していません。</li> <li>データが少ない場合は線形探索のほうが高性能です。線形探索は各反復で判定が 1 回だけですが、二分探索では加算 1 回、除算 1 回、判定 1 ~ 3 回、加算(減算)1 回の計 4 ~ 6 回の基本操作が必要です。そのため、データ量 \\(n\\) が小さいと、線形探索のほうが速くなります。</li> </ul>","path":["第 10 章   探索","10.1   二分探索"],"tags":[]},{"location":"chapter_searching/binary_search_edge/","level":1,"title":"10.3   二分探索で境界を探す","text":"","path":["第 10 章   探索","10.3   二分探索で境界を探す"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1031","level":2,"title":"10.3.1   左の境界を探す","text":"<p>問題</p> <p>重複する要素を含む可能性がある、長さ \\(n\\) の整列済み配列 <code>nums</code> が与えられます。配列内で最も左にある要素 <code>target</code> のインデックスを返してください。含まれない場合は \\(-1\\) を返します。</p> <p>二分探索で挿入位置を探す方法を思い出してください。探索後、\\(i\\) は最も左の <code>target</code> を指します。つまり、挿入位置の探索は、本質的には最も左の <code>target</code> のインデックスを探しています。</p> <p>挿入位置を探す関数を使って、左の境界を探すことを考えます。ただし、配列に <code>target</code> が含まれない場合、次の二つの結果になる可能性があります。</p> <ul> <li>挿入位置のインデックス \\(i\\) が範囲外になります。</li> <li>要素 <code>nums[i]</code> が <code>target</code> と等しくありません。</li> </ul> <p>このどちらかに当てはまれば、そのまま \\(-1\\) を返します。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_edge.py<pre><code>def binary_search_left_edge(nums: list[int], target: int) -&gt; int:\n \"\"\"二分探索で最も左にある target を探す\"\"\"\n # target の挿入位置を探すことに等しい\n i = binary_search_insertion(nums, target)\n # target が見つからなかったので、-1 を返す\n if i == len(nums) or nums[i] != target:\n return -1\n # target が見つかったので、インデックス i を返す\n return i\n</code></pre> binary_search_edge.cpp<pre><code>/* 二分探索で最も左にある target を探す */\nint binarySearchLeftEdge(vector&lt;int&gt; &amp;nums, int target) {\n // target の挿入位置を探すことに等しい\n int i = binarySearchInsertion(nums, target);\n // target が見つからなかったので、-1 を返す\n if (i == nums.size() || nums[i] != target) {\n return -1;\n }\n // target が見つかったので、インデックス i を返す\n return i;\n}\n</code></pre> binary_search_edge.java<pre><code>/* 二分探索で最も左にある target を探す */\nint binarySearchLeftEdge(int[] nums, int target) {\n // target の挿入位置を探すことに等しい\n int i = binary_search_insertion.binarySearchInsertion(nums, target);\n // target が見つからなかったので、-1 を返す\n if (i == nums.length || nums[i] != target) {\n return -1;\n }\n // target が見つかったので、インデックス i を返す\n return i;\n}\n</code></pre> binary_search_edge.cs<pre><code>/* 二分探索で最も左にある target を探す */\nint BinarySearchLeftEdge(int[] nums, int target) {\n // target の挿入位置を探すことに等しい\n int i = binary_search_insertion.BinarySearchInsertion(nums, target);\n // target が見つからなかったので、-1 を返す\n if (i == nums.Length || nums[i] != target) {\n return -1;\n }\n // target が見つかったので、インデックス i を返す\n return i;\n}\n</code></pre> binary_search_edge.go<pre><code>/* 二分探索で最も左にある target を探す */\nfunc binarySearchLeftEdge(nums []int, target int) int {\n // target の挿入位置を探すことに等しい\n i := binarySearchInsertion(nums, target)\n // target が見つからなかったので、-1 を返す\n if i == len(nums) || nums[i] != target {\n return -1\n }\n // target が見つかったので、インデックス i を返す\n return i\n}\n</code></pre> binary_search_edge.swift<pre><code>/* 二分探索で最も左にある target を探す */\nfunc binarySearchLeftEdge(nums: [Int], target: Int) -&gt; Int {\n // target の挿入位置を探すことに等しい\n let i = binarySearchInsertion(nums: nums, target: target)\n // target が見つからなかったので、-1 を返す\n if i == nums.endIndex || nums[i] != target {\n return -1\n }\n // target が見つかったので、インデックス i を返す\n return i\n}\n</code></pre> binary_search_edge.js<pre><code>/* 二分探索で最も左にある target を探す */\nfunction binarySearchLeftEdge(nums, target) {\n // target の挿入位置を探すことに等しい\n const i = binarySearchInsertion(nums, target);\n // target が見つからなかったので、-1 を返す\n if (i === nums.length || nums[i] !== target) {\n return -1;\n }\n // target が見つかったので、インデックス i を返す\n return i;\n}\n</code></pre> binary_search_edge.ts<pre><code>/* 二分探索で最も左にある target を探す */\nfunction binarySearchLeftEdge(nums: Array&lt;number&gt;, target: number): number {\n // target の挿入位置を探すことに等しい\n const i = binarySearchInsertion(nums, target);\n // target が見つからなかったので、-1 を返す\n if (i === nums.length || nums[i] !== target) {\n return -1;\n }\n // target が見つかったので、インデックス i を返す\n return i;\n}\n</code></pre> binary_search_edge.dart<pre><code>/* 二分探索で最も左にある target を探す */\nint binarySearchLeftEdge(List&lt;int&gt; nums, int target) {\n // target の挿入位置を探すことに等しい\n int i = binarySearchInsertion(nums, target);\n // target が見つからなかったので、-1 を返す\n if (i == nums.length || nums[i] != target) {\n return -1;\n }\n // target が見つかったので、インデックス i を返す\n return i;\n}\n</code></pre> binary_search_edge.rs<pre><code>/* 二分探索で最も左にある target を探す */\nfn binary_search_left_edge(nums: &amp;[i32], target: i32) -&gt; i32 {\n // target の挿入位置を探すことに等しい\n let i = binary_search_insertion(nums, target);\n // target が見つからなかったので、-1 を返す\n if i == nums.len() as i32 || nums[i as usize] != target {\n return -1;\n }\n // target が見つかったので、インデックス i を返す\n i\n}\n</code></pre> binary_search_edge.c<pre><code>/* 二分探索で最も左にある target を探す */\nint binarySearchLeftEdge(int *nums, int numSize, int target) {\n // target の挿入位置を探すことに等しい\n int i = binarySearchInsertion(nums, numSize, target);\n // target が見つからなかったので、-1 を返す\n if (i == numSize || nums[i] != target) {\n return -1;\n }\n // target が見つかったので、インデックス i を返す\n return i;\n}\n</code></pre> binary_search_edge.kt<pre><code>/* 二分探索で最も左にある target を探す */\nfun binarySearchLeftEdge(nums: IntArray, target: Int): Int {\n // target の挿入位置を探すことに等しい\n val i = binarySearchInsertion(nums, target)\n // target が見つからなかったので、-1 を返す\n if (i == nums.size || nums[i] != target) {\n return -1\n }\n // target が見つかったので、インデックス i を返す\n return i\n}\n</code></pre> binary_search_edge.rb<pre><code>### 二分探索で最も左にある target を探す ###\ndef binary_search_left_edge(nums, target)\n # target の挿入位置を探すことに等しい\n i = binary_search_insertion(nums, target)\n\n # target が見つからなかったので、-1 を返す\n return -1 if i == nums.length || nums[i] != target\n\n i # target が見つかったので、インデックス i を返す\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 10 章   探索","10.3   二分探索で境界を探す"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1032","level":2,"title":"10.3.2   右の境界を探す","text":"<p>では、最も右の <code>target</code> はどう探せばよいでしょうか。最も直接的な方法は、<code>nums[m] == target</code> の場合にポインタで区間を縮める操作を書き換えることです。コードは省略するので、興味があれば実装してみてください。</p> <p>ここでは、さらに工夫した二つの方法を紹介します。</p>","path":["第 10 章   探索","10.3   二分探索で境界を探す"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#1","level":3,"title":"1.   左の境界を探す処理の再利用","text":"<p>実は、最も左の要素を探す関数を使って、最も右の要素を探すこともできます。具体的には、最も右の <code>target</code> を探す問題を、最も左の <code>target + 1</code> を探す問題に置き換えます。</p> <p>図 10-7 のように、探索後のポインタ \\(i\\) は最も左の <code>target + 1</code>(存在する場合)を指し、\\(j\\) は最も右の <code>target</code> を指します。そのため、\\(j\\) を返せばよいのです。</p> <p></p> <p> 図 10-7   右の境界の探索を左の境界の探索に置き換える </p> <p>返される挿入位置は \\(i\\) なので、そこから \\(1\\) を引いて \\(j\\) を得る必要があります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_edge.py<pre><code>def binary_search_right_edge(nums: list[int], target: int) -&gt; int:\n \"\"\"二分探索で最も右にある target を探す\"\"\"\n # 最も左にある target + 1 を探す問題に変換する\n i = binary_search_insertion(nums, target + 1)\n # j は最も右にある target を指し、i は target より大きい最初の要素を指す\n j = i - 1\n # target が見つからなかったので、-1 を返す\n if j == -1 or nums[j] != target:\n return -1\n # target が見つかったので、インデックス j を返す\n return j\n</code></pre> binary_search_edge.cpp<pre><code>/* 二分探索で最も右にある target を探す */\nint binarySearchRightEdge(vector&lt;int&gt; &amp;nums, int target) {\n // 最も左にある target + 1 を探す問題に変換する\n int i = binarySearchInsertion(nums, target + 1);\n // j は最も右にある target を指し、i は target より大きい最初の要素を指す\n int j = i - 1;\n // target が見つからなかったので、-1 を返す\n if (j == -1 || nums[j] != target) {\n return -1;\n }\n // target が見つかったので、インデックス j を返す\n return j;\n}\n</code></pre> binary_search_edge.java<pre><code>/* 二分探索で最も右にある target を探す */\nint binarySearchRightEdge(int[] nums, int target) {\n // 最も左にある target + 1 を探す問題に変換する\n int i = binary_search_insertion.binarySearchInsertion(nums, target + 1);\n // j は最も右にある target を指し、i は target より大きい最初の要素を指す\n int j = i - 1;\n // target が見つからなかったので、-1 を返す\n if (j == -1 || nums[j] != target) {\n return -1;\n }\n // target が見つかったので、インデックス j を返す\n return j;\n}\n</code></pre> binary_search_edge.cs<pre><code>/* 二分探索で最も右にある target を探す */\nint BinarySearchRightEdge(int[] nums, int target) {\n // 最も左にある target + 1 を探す問題に変換する\n int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1);\n // j は最も右にある target を指し、i は target より大きい最初の要素を指す\n int j = i - 1;\n // target が見つからなかったので、-1 を返す\n if (j == -1 || nums[j] != target) {\n return -1;\n }\n // target が見つかったので、インデックス j を返す\n return j;\n}\n</code></pre> binary_search_edge.go<pre><code>/* 二分探索で最も右にある target を探す */\nfunc binarySearchRightEdge(nums []int, target int) int {\n // 最も左にある target + 1 を探す問題に変換する\n i := binarySearchInsertion(nums, target+1)\n // j は最も右にある target を指し、i は target より大きい最初の要素を指す\n j := i - 1\n // target が見つからなかったので、-1 を返す\n if j == -1 || nums[j] != target {\n return -1\n }\n // target が見つかったので、インデックス j を返す\n return j\n}\n</code></pre> binary_search_edge.swift<pre><code>/* 二分探索で最も右にある target を探す */\nfunc binarySearchRightEdge(nums: [Int], target: Int) -&gt; Int {\n // 最も左にある target + 1 を探す問題に変換する\n let i = binarySearchInsertion(nums: nums, target: target + 1)\n // j は最も右にある target を指し、i は target より大きい最初の要素を指す\n let j = i - 1\n // target が見つからなかったので、-1 を返す\n if j == -1 || nums[j] != target {\n return -1\n }\n // target が見つかったので、インデックス j を返す\n return j\n}\n</code></pre> binary_search_edge.js<pre><code>/* 二分探索で最も右にある target を探す */\nfunction binarySearchRightEdge(nums, target) {\n // 最も左にある target + 1 を探す問題に変換する\n const i = binarySearchInsertion(nums, target + 1);\n // j は最も右にある target を指し、i は target より大きい最初の要素を指す\n const j = i - 1;\n // target が見つからなかったので、-1 を返す\n if (j === -1 || nums[j] !== target) {\n return -1;\n }\n // target が見つかったので、インデックス j を返す\n return j;\n}\n</code></pre> binary_search_edge.ts<pre><code>/* 二分探索で最も右にある target を探す */\nfunction binarySearchRightEdge(nums: Array&lt;number&gt;, target: number): number {\n // 最も左にある target + 1 を探す問題に変換する\n const i = binarySearchInsertion(nums, target + 1);\n // j は最も右にある target を指し、i は target より大きい最初の要素を指す\n const j = i - 1;\n // target が見つからなかったので、-1 を返す\n if (j === -1 || nums[j] !== target) {\n return -1;\n }\n // target が見つかったので、インデックス j を返す\n return j;\n}\n</code></pre> binary_search_edge.dart<pre><code>/* 二分探索で最も右にある target を探す */\nint binarySearchRightEdge(List&lt;int&gt; nums, int target) {\n // 最も左にある target + 1 を探す問題に変換する\n int i = binarySearchInsertion(nums, target + 1);\n // j は最も右にある target を指し、i は target より大きい最初の要素を指す\n int j = i - 1;\n // target が見つからなかったので、-1 を返す\n if (j == -1 || nums[j] != target) {\n return -1;\n }\n // target が見つかったので、インデックス j を返す\n return j;\n}\n</code></pre> binary_search_edge.rs<pre><code>/* 二分探索で最も右にある target を探す */\nfn binary_search_right_edge(nums: &amp;[i32], target: i32) -&gt; i32 {\n // 最も左にある target + 1 を探す問題に変換する\n let i = binary_search_insertion(nums, target + 1);\n // j は最も右にある target を指し、i は target より大きい最初の要素を指す\n let j = i - 1;\n // target が見つからなかったので、-1 を返す\n if j == -1 || nums[j as usize] != target {\n return -1;\n }\n // target が見つかったので、インデックス j を返す\n j\n}\n</code></pre> binary_search_edge.c<pre><code>/* 二分探索で最も右にある target を探す */\nint binarySearchRightEdge(int *nums, int numSize, int target) {\n // 最も左にある target + 1 を探す問題に変換する\n int i = binarySearchInsertion(nums, numSize, target + 1);\n // j は最も右にある target を指し、i は target より大きい最初の要素を指す\n int j = i - 1;\n // target が見つからなかったので、-1 を返す\n if (j == -1 || nums[j] != target) {\n return -1;\n }\n // target が見つかったので、インデックス j を返す\n return j;\n}\n</code></pre> binary_search_edge.kt<pre><code>/* 二分探索で最も右にある target を探す */\nfun binarySearchRightEdge(nums: IntArray, target: Int): Int {\n // 最も左にある target + 1 を探す問題に変換する\n val i = binarySearchInsertion(nums, target + 1)\n // j は最も右にある target を指し、i は target より大きい最初の要素を指す\n val j = i - 1\n // target が見つからなかったので、-1 を返す\n if (j == -1 || nums[j] != target) {\n return -1\n }\n // target が見つかったので、インデックス j を返す\n return j\n}\n</code></pre> binary_search_edge.rb<pre><code>### 二分探索で最も右にある target を探す ###\ndef binary_search_right_edge(nums, target)\n # 最も左にある target + 1 を探す問題に変換する\n i = binary_search_insertion(nums, target + 1)\n\n # j は最も右にある target を指し、i は target より大きい最初の要素を指す\n j = i - 1\n\n # target が見つからなかったので、-1 を返す\n return -1 if j == -1 || nums[j] != target\n\n j # target が見つかったので、インデックス j を返す\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 10 章   探索","10.3   二分探索で境界を探す"],"tags":[]},{"location":"chapter_searching/binary_search_edge/#2","level":3,"title":"2.   要素の探索への置き換え","text":"<p>配列に <code>target</code> が含まれない場合、最終的に \\(i\\) は <code>target</code> より大きい最初の要素を、\\(j\\) は <code>target</code> より小さい最も右の要素を指します。</p> <p>したがって、図 10-8 のように、配列に存在しない要素を用意して左右の境界を探すことができます。</p> <ul> <li>最も左の <code>target</code> を探す場合:<code>target - 0.5</code> の探索に置き換え、ポインタ \\(i\\) を返します。</li> <li>最も右の <code>target</code> を探す場合:<code>target + 0.5</code> の探索に置き換え、ポインタ \\(j\\) を返します。</li> </ul> <p></p> <p> 図 10-8   境界の探索を要素の探索に置き換える </p> <p>コードは省略しますが、次の二点に注意してください。</p> <ul> <li>与えられた配列には小数が含まれないため、等しい場合の処理を考える必要はありません。</li> <li>この方法では小数を使うため、関数内の変数 <code>target</code> を浮動小数点数型に変更する必要があります(Python では変更不要です)。</li> </ul>","path":["第 10 章   探索","10.3   二分探索で境界を探す"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/","level":1,"title":"10.2   二分探索で挿入位置を探す","text":"<p>二分探索は目的の要素を探すだけでなく、その挿入位置を探すなど、多くの派生問題にも利用できます。</p>","path":["第 10 章   探索","10.2   二分探索で挿入位置を探す"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/#1021","level":2,"title":"10.2.1   重複する要素がない場合","text":"<p>問題</p> <p>長さ \\(n\\) の整列済み配列 <code>nums</code> と要素 <code>target</code> が与えられます。配列内に重複する要素はありません。整列状態を保ちながら、<code>target</code> を配列 <code>nums</code> に挿入します。すでに <code>target</code> が存在する場合は、その左側に挿入します。挿入後の <code>target</code> のインデックスを返してください。例を図 10-4 に示します。</p> <p></p> <p> 図 10-4   二分探索で挿入位置を探す入力例 </p> <p>前節の二分探索のコードを再利用するには、次の二つの問いに答える必要があります。</p> <p>問い一:配列に <code>target</code> が含まれる場合、挿入位置のインデックスはその要素のインデックスでしょうか。</p> <p>問題では、等しい要素の左に <code>target</code> を挿入するよう求めています。つまり、新しく挿入した <code>target</code> が元の <code>target</code> の位置に入ります。したがって、配列に <code>target</code> が含まれる場合、挿入位置はその <code>target</code> のインデックスです。</p> <p>問い二:配列に <code>target</code> が存在しない場合、挿入位置はどの要素のインデックスでしょうか。</p> <p>二分探索の過程をさらに考えてみましょう。<code>nums[m] &lt; target</code> のとき \\(i\\) が移動するということは、ポインタ \\(i\\) が <code>target</code> 以上の要素へ近づいているということです。同様に、ポインタ \\(j\\) は常に <code>target</code> 以下の要素へ近づいています。</p> <p>したがって、二分探索が終わると、\\(i\\) は <code>target</code> より大きい最初の要素を、\\(j\\) は <code>target</code> より小さい最も右の要素を指します。配列に <code>target</code> が含まれない場合、挿入位置のインデックスは \\(i\\) だと分かります。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_insertion.py<pre><code>def binary_search_insertion_simple(nums: list[int], target: int) -&gt; int:\n \"\"\"二分探索による挿入位置の探索(重複要素がない場合)\"\"\"\n i, j = 0, len(nums) - 1 # 閉区間 [0, n-1] を初期化する\n while i &lt;= j:\n m = (i + j) // 2 # 中央のインデックス m を計算する\n if nums[m] &lt; target:\n i = m + 1 # target は区間 [m+1, j] に存在する\n elif nums[m] &gt; target:\n j = m - 1 # target は区間 [i, m-1] に存在する\n else:\n return m # target が見つかったので、挿入位置 m を返す\n # target が見つからなかったので、挿入位置 i を返す\n return i\n</code></pre> binary_search_insertion.cpp<pre><code>/* 二分探索による挿入位置の探索(重複要素がない場合) */\nint binarySearchInsertionSimple(vector&lt;int&gt; &amp;nums, int target) {\n int i = 0, j = nums.size() - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n return m; // target が見つかったので、挿入位置 m を返す\n }\n }\n // target が見つからなかったので、挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.java<pre><code>/* 二分探索による挿入位置の探索(重複要素がない場合) */\nint binarySearchInsertionSimple(int[] nums, int target) {\n int i = 0, j = nums.length - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n return m; // target が見つかったので、挿入位置 m を返す\n }\n }\n // target が見つからなかったので、挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.cs<pre><code>/* 二分探索による挿入位置の探索(重複要素がない場合) */\nint BinarySearchInsertionSimple(int[] nums, int target) {\n int i = 0, j = nums.Length - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n return m; // target が見つかったので、挿入位置 m を返す\n }\n }\n // target が見つからなかったので、挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.go<pre><code>/* 二分探索による挿入位置の探索(重複要素がない場合) */\nfunc binarySearchInsertionSimple(nums []int, target int) int {\n // 閉区間 [0, n-1] を初期化する\n i, j := 0, len(nums)-1\n for i &lt;= j {\n // 中央のインデックス m を計算する\n m := i + (j-i)/2\n if nums[m] &lt; target {\n // target は区間 [m+1, j] に存在する\n i = m + 1\n } else if nums[m] &gt; target {\n // target は区間 [i, m-1] に存在する\n j = m - 1\n } else {\n // target が見つかったので、挿入位置 m を返す\n return m\n }\n }\n // target が見つからなかったので、挿入位置 i を返す\n return i\n}\n</code></pre> binary_search_insertion.swift<pre><code>/* 二分探索による挿入位置の探索(重複要素がない場合) */\nfunc binarySearchInsertionSimple(nums: [Int], target: Int) -&gt; Int {\n // 閉区間 [0, n-1] を初期化する\n var i = nums.startIndex\n var j = nums.endIndex - 1\n while i &lt;= j {\n let m = i + (j - i) / 2 // 中央のインデックス m を計算する\n if nums[m] &lt; target {\n i = m + 1 // target は区間 [m+1, j] に存在する\n } else if nums[m] &gt; target {\n j = m - 1 // target は区間 [i, m-1] に存在する\n } else {\n return m // target が見つかったので、挿入位置 m を返す\n }\n }\n // target が見つからなかったので、挿入位置 i を返す\n return i\n}\n</code></pre> binary_search_insertion.js<pre><code>/* 二分探索による挿入位置の探索(重複要素がない場合) */\nfunction binarySearchInsertionSimple(nums, target) {\n let i = 0,\n j = nums.length - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n const m = Math.floor(i + (j - i) / 2); // 中央のインデックス m を計算し、Math.floor() で小数点以下を切り捨てる\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n return m; // target が見つかったので、挿入位置 m を返す\n }\n }\n // target が見つからなかったので、挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.ts<pre><code>/* 二分探索による挿入位置の探索(重複要素がない場合) */\nfunction binarySearchInsertionSimple(\n nums: Array&lt;number&gt;,\n target: number\n): number {\n let i = 0,\n j = nums.length - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n const m = Math.floor(i + (j - i) / 2); // 中央のインデックス m を計算し、Math.floor() で小数点以下を切り捨てる\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n return m; // target が見つかったので、挿入位置 m を返す\n }\n }\n // target が見つからなかったので、挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.dart<pre><code>/* 二分探索による挿入位置の探索(重複要素がない場合) */\nint binarySearchInsertionSimple(List&lt;int&gt; nums, int target) {\n int i = 0, j = nums.length - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n int m = i + (j - i) ~/ 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n return m; // target が見つかったので、挿入位置 m を返す\n }\n }\n // target が見つからなかったので、挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.rs<pre><code>/* 二分探索による挿入位置の探索(重複要素がない場合) */\nfn binary_search_insertion_simple(nums: &amp;[i32], target: i32) -&gt; i32 {\n let (mut i, mut j) = (0, nums.len() as i32 - 1); // 閉区間 [0, n-1] を初期化する\n while i &lt;= j {\n let m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if nums[m as usize] &lt; target {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if nums[m as usize] &gt; target {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n return m;\n }\n }\n // target が見つからなかったので、挿入位置 i を返す\n i\n}\n</code></pre> binary_search_insertion.c<pre><code>/* 二分探索による挿入位置の探索(重複要素がない場合) */\nint binarySearchInsertionSimple(int *nums, int numSize, int target) {\n int i = 0, j = numSize - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n return m; // target が見つかったので、挿入位置 m を返す\n }\n }\n // target が見つからなかったので、挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.kt<pre><code>/* 二分探索による挿入位置の探索(重複要素がない場合) */\nfun binarySearchInsertionSimple(nums: IntArray, target: Int): Int {\n var i = 0\n var j = nums.size - 1 // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n val m = i + (j - i) / 2 // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1 // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1 // target は区間 [i, m-1] に存在する\n } else {\n return m // target が見つかったので、挿入位置 m を返す\n }\n }\n // target が見つからなかったので、挿入位置 i を返す\n return i\n}\n</code></pre> binary_search_insertion.rb<pre><code>### 二分探索による挿入位置の探索(重複要素がない場合) ###\ndef binary_search_insertion_simple(nums, target)\n # 閉区間 [0, n-1] を初期化する\n i, j = 0, nums.length - 1\n\n while i &lt;= j\n # 中央のインデックス m を計算する\n m = (i + j) / 2\n\n if nums[m] &lt; target\n i = m + 1 # target は区間 [m+1, j] に存在する\n elsif nums[m] &gt; target\n j = m - 1 # target は区間 [i, m-1] に存在する\n else\n return m # target が見つかったので、挿入位置 m を返す\n end\n end\n\n i # target が見つからなかったので、挿入位置 i を返す\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 10 章   探索","10.2   二分探索で挿入位置を探す"],"tags":[]},{"location":"chapter_searching/binary_search_insertion/#1022","level":2,"title":"10.2.2   重複する要素がある場合","text":"<p>問題</p> <p>前の問題で、配列に重複する要素が含まれる可能性があるものとし、ほかの条件は変えません。</p> <p>配列に複数の <code>target</code> があると、通常の二分探索ではそのうち一つのインデックスしか返せず、その左右にいくつの <code>target</code> があるかは分かりません。</p> <p>問題では同じ要素の最も左に挿入するよう求めているため、配列内で最も左の <code>target</code> のインデックスを探す必要があります。まず、図 10-5 の手順を考えてみます。</p> <ol> <li>二分探索でいずれかの <code>target</code> のインデックスを求め、\\(k\\) とします。</li> <li>インデックス \\(k\\) から左へ線形に走査し、最も左の <code>target</code> が見つかったら返します。</li> </ol> <p></p> <p> 図 10-5   線形探索で重複する要素の挿入位置を探す </p> <p>この方法でも求められますが、線形探索を含むため時間計算量は \\(O(n)\\) です。配列に <code>target</code> が多数重複している場合、効率が低くなります。</p> <p>そこで、二分探索のコードを拡張します。図 10-6 のように全体の流れは変えず、各反復で中点のインデックス \\(m\\) を計算し、<code>target</code> と <code>nums[m]</code> の大小関係を判定します。次の場合に分かれます。</p> <ul> <li><code>nums[m] &lt; target</code> または <code>nums[m] &gt; target</code> の場合、まだ <code>target</code> が見つかっていないので、通常の二分探索と同じ操作で区間を縮めます。これにより、ポインタ \\(i\\) と \\(j\\) が <code>target</code> へ近づきます。</li> <li><code>nums[m] == target</code> の場合、<code>target</code> より小さい要素は区間 \\([i, m - 1]\\) にあるので、\\(j = m - 1\\) として区間を縮めます。これにより、ポインタ \\(j\\) が <code>target</code> より小さい要素へ近づきます。</li> </ul> <p>ループが終わると、\\(i\\) は最も左の <code>target</code> を、\\(j\\) は <code>target</code> より小さい最も右の要素を指します。したがって、インデックス \\(i\\) が挿入位置です。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 10-6   二分探索で重複する要素の挿入位置を探す手順 </p> <p>以下のコードを見ると、条件分岐 <code>nums[m] &gt; target</code> と <code>nums[m] == target</code> では同じ操作を行うため、二つをまとめられます。</p> <p>それでも、条件を分けたままにしておけば、論理が明確で読みやすくなります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_insertion.py<pre><code>def binary_search_insertion(nums: list[int], target: int) -&gt; int:\n \"\"\"二分探索による挿入位置の探索(重複要素がある場合)\"\"\"\n i, j = 0, len(nums) - 1 # 閉区間 [0, n-1] を初期化する\n while i &lt;= j:\n m = (i + j) // 2 # 中央のインデックス m を計算する\n if nums[m] &lt; target:\n i = m + 1 # target は区間 [m+1, j] に存在する\n elif nums[m] &gt; target:\n j = m - 1 # target は区間 [i, m-1] に存在する\n else:\n j = m - 1 # target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n # 挿入位置 i を返す\n return i\n</code></pre> binary_search_insertion.cpp<pre><code>/* 二分探索による挿入位置の探索(重複要素がある場合) */\nint binarySearchInsertion(vector&lt;int&gt; &amp;nums, int target) {\n int i = 0, j = nums.size() - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n j = m - 1; // target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n }\n }\n // 挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.java<pre><code>/* 二分探索による挿入位置の探索(重複要素がある場合) */\nint binarySearchInsertion(int[] nums, int target) {\n int i = 0, j = nums.length - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n j = m - 1; // target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n }\n }\n // 挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.cs<pre><code>/* 二分探索による挿入位置の探索(重複要素がある場合) */\nint BinarySearchInsertion(int[] nums, int target) {\n int i = 0, j = nums.Length - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n j = m - 1; // target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n }\n }\n // 挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.go<pre><code>/* 二分探索による挿入位置の探索(重複要素がある場合) */\nfunc binarySearchInsertion(nums []int, target int) int {\n // 閉区間 [0, n-1] を初期化する\n i, j := 0, len(nums)-1\n for i &lt;= j {\n // 中央のインデックス m を計算する\n m := i + (j-i)/2\n if nums[m] &lt; target {\n // target は区間 [m+1, j] に存在する\n i = m + 1\n } else if nums[m] &gt; target {\n // target は区間 [i, m-1] に存在する\n j = m - 1\n } else {\n // target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n j = m - 1\n }\n }\n // 挿入位置 i を返す\n return i\n}\n</code></pre> binary_search_insertion.swift<pre><code>/* 二分探索による挿入位置の探索(重複要素がある場合) */\nfunc binarySearchInsertion(nums: [Int], target: Int) -&gt; Int {\n // 閉区間 [0, n-1] を初期化する\n var i = nums.startIndex\n var j = nums.endIndex - 1\n while i &lt;= j {\n let m = i + (j - i) / 2 // 中央のインデックス m を計算する\n if nums[m] &lt; target {\n i = m + 1 // target は区間 [m+1, j] に存在する\n } else if nums[m] &gt; target {\n j = m - 1 // target は区間 [i, m-1] に存在する\n } else {\n j = m - 1 // target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n }\n }\n // 挿入位置 i を返す\n return i\n}\n</code></pre> binary_search_insertion.js<pre><code>/* 二分探索による挿入位置の探索(重複要素がある場合) */\nfunction binarySearchInsertion(nums, target) {\n let i = 0,\n j = nums.length - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n const m = Math.floor(i + (j - i) / 2); // 中央のインデックス m を計算し、Math.floor() で小数点以下を切り捨てる\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n j = m - 1; // target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n }\n }\n // 挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.ts<pre><code>/* 二分探索による挿入位置の探索(重複要素がある場合) */\nfunction binarySearchInsertion(nums: Array&lt;number&gt;, target: number): number {\n let i = 0,\n j = nums.length - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n const m = Math.floor(i + (j - i) / 2); // 中央のインデックス m を計算し、Math.floor() で小数点以下を切り捨てる\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n j = m - 1; // target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n }\n }\n // 挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.dart<pre><code>/* 二分探索による挿入位置の探索(重複要素がある場合) */\nint binarySearchInsertion(List&lt;int&gt; nums, int target) {\n int i = 0, j = nums.length - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n int m = i + (j - i) ~/ 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n j = m - 1; // target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n }\n }\n // 挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.rs<pre><code>/* 二分探索による挿入位置の探索(重複要素がある場合) */\npub fn binary_search_insertion(nums: &amp;[i32], target: i32) -&gt; i32 {\n let (mut i, mut j) = (0, nums.len() as i32 - 1); // 閉区間 [0, n-1] を初期化する\n while i &lt;= j {\n let m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if nums[m as usize] &lt; target {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if nums[m as usize] &gt; target {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n j = m - 1; // target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n }\n }\n // 挿入位置 i を返す\n i\n}\n</code></pre> binary_search_insertion.c<pre><code>/* 二分探索による挿入位置の探索(重複要素がある場合) */\nint binarySearchInsertion(int *nums, int numSize, int target) {\n int i = 0, j = numSize - 1; // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n int m = i + (j - i) / 2; // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1; // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1; // target は区間 [i, m-1] に存在する\n } else {\n j = m - 1; // target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n }\n }\n // 挿入位置 i を返す\n return i;\n}\n</code></pre> binary_search_insertion.kt<pre><code>/* 二分探索による挿入位置の探索(重複要素がある場合) */\nfun binarySearchInsertion(nums: IntArray, target: Int): Int {\n var i = 0\n var j = nums.size - 1 // 閉区間 [0, n-1] を初期化する\n while (i &lt;= j) {\n val m = i + (j - i) / 2 // 中央のインデックス m を計算する\n if (nums[m] &lt; target) {\n i = m + 1 // target は区間 [m+1, j] に存在する\n } else if (nums[m] &gt; target) {\n j = m - 1 // target は区間 [i, m-1] に存在する\n } else {\n j = m - 1 // target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n }\n }\n // 挿入位置 i を返す\n return i\n}\n</code></pre> binary_search_insertion.rb<pre><code>### 二分探索による挿入位置の探索(重複要素がある場合) ###\ndef binary_search_insertion(nums, target)\n # 閉区間 [0, n-1] を初期化する\n i, j = 0, nums.length - 1\n\n while i &lt;= j\n # 中央のインデックス m を計算する\n m = (i + j) / 2\n\n if nums[m] &lt; target\n i = m + 1 # target は区間 [m+1, j] に存在する\n elsif nums[m] &gt; target\n j = m - 1 # target は区間 [i, m-1] に存在する\n else\n j = m - 1 # target より小さい要素のうち最も右にあるものは、区間 [i, m-1] に存在する\n end\n end\n\n i # 挿入位置 i を返す\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>ヒント</p> <p>この節のコードはすべて両端を含む閉区間で書いています。興味があれば、左閉右開の書き方でも実装してみてください。</p> <p>全体として見ると、二分探索は、ポインタ \\(i\\) と \\(j\\) にそれぞれ探索目標を定めるものにすぎません。目標は特定の要素(例えば <code>target</code>)である場合も、要素の範囲(例えば <code>target</code> より小さい要素)である場合もあります。</p> <p>二分を繰り返す中で、ポインタ \\(i\\) と \\(j\\) はあらかじめ定めた目標に近づいていきます。最終的には答えを見つけるか、境界を越えて停止します。</p>","path":["第 10 章   探索","10.2   二分探索で挿入位置を探す"],"tags":[]},{"location":"chapter_searching/exercises/","level":1,"title":"10.7   練習問題","text":"","path":["第 10 章   探索","10.7   練習問題"],"tags":[]},{"location":"chapter_searching/exercises/#1071","level":2,"title":"10.7.1   知識の確認","text":"","path":["第 10 章   探索","10.7   練習問題"],"tags":[]},{"location":"chapter_searching/exercises/#1","level":3,"title":"1.   二分探索で探索区間を狭めるには","text":"<p>整列済み配列 <code>[2, 5, 8, 12, 16, 23, 38]</code> から 16 を探します。 両端を含む閉区間 <code>[i, j]</code> を使い、中点は \\(m=i+(j-i)/2\\)(切り捨て)とします。</p> <p>見つかるまで、各反復の <code>(i, j, m)</code>、中点の要素、 次に区間をどう狭めるかを書いてください。</p> 解答例 <p>各反復の探索過程は、次のとおりです。</p> 反復 <code>(i, j, m)</code> 中点の要素 次の操作 1 <code>(0, 6, 3)</code> 12 <code>12 &lt; 16</code> なので <code>i = 4</code> とします 2 <code>(4, 6, 5)</code> 23 <code>23 &gt; 16</code> なので <code>j = 4</code> とします 3 <code>(4, 4, 4)</code> 16 見つかったので、インデックス 4 を返します <p>配列は整列済みなので、中点の値が目的の値より小さければ、中点とその左側を除外できます。 中点の値が目的の値より大きければ、中点とその右側を除外できます。</p>","path":["第 10 章   探索","10.7   練習問題"],"tags":[]},{"location":"chapter_searching/exercises/#2","level":3,"title":"2.   重複する要素の左右の境界","text":"<p>配列 <code>[1, 2, 2, 2, 4, 6]</code> から 2 を探します。 ある生徒が二分探索でインデックス 2 に目的の値を見つけ、すぐに返して、 「インデックス 2 が、2 の左境界だ」と言いました。</p> <ol> <li>この生徒の発言は正しいですか。2 の左右の境界はそれぞれどこですか。理由も説明してください。</li> <li>左境界を探す場合、中点の要素が目的の値と等しかったら、次はどちら側を探し続けますか。</li> <li>右境界を探す場合は、どちら側を探し続けますか。方向だけを説明し、探索過程全体を書く必要はありません。</li> </ol> 解答例 <ol> <li> <p>この生徒の発言は正しくありません。一つの 2 を見つけてすぐに返すと、どれか一つの 2 を見つけたことしか保証できず、最も左や右の 2 を見つけたとは限りません。 この問題の左境界はインデックス 1、右境界はインデックス 3 です。</p> </li> <li> <p>左境界を探すときは、中点の要素が 2 と等しくても、左側を探し続けます。 例えば、両端を含む閉区間を使う場合は <code>j = m - 1</code> とします。</p> </li> <li> <p>右境界を探すときは、中点の要素が 2 と等しかったら、右側を探し続けます。 例えば <code>i = m + 1</code> とします。</p> </li> </ol>","path":["第 10 章   探索","10.7   練習問題"],"tags":[]},{"location":"chapter_searching/exercises/#3","level":3,"title":"3.   データに合った探索方法を選ぶ","text":"<p>次の各場面に適した方法を「線形探索、二分探索、ハッシュテーブル」から選び、理由を説明してください。</p> <ol> <li>整列済みで今後変化しない \\(10^7\\) 個の整数を繰り返し探索します。ほかのデータ構造は追加で作りません。</li> <li>挿入と削除が頻繁なデータ集合で、キーの有無を繰り返し調べます。整列状態を保つ必要はなく、範囲探索もしません。</li> <li>未整列配列から、ある値を一回だけ探します。</li> </ol> 解答例 <ol> <li>二分探索です。データが整列済みで変化せず、\\(O(\\log n)\\) 時間で探索でき、追加の領域も不要です。</li> <li>ハッシュテーブルです。ハッシュ関数がキーを各バケットへ比較的均等に分散できる場合、挿入、削除、キーによる探索の平均時間計算量は、いずれも \\(O(1)\\) です。</li> <li> <p>先頭から末尾まで直接走査します。一回しか探さない場合、ソートやハッシュテーブルの構築では、先に配列全体を処理する必要があり、 その一回の作業の総量は減りません。</p> <p>選択は、データが整列済みか、追加の構造を作れるか、照会回数はいくつか、どの操作が必要かによって決まります。</p> </li> </ol>","path":["第 10 章   探索","10.7   練習問題"],"tags":[]},{"location":"chapter_searching/exercises/#1072","level":2,"title":"10.7.2   プログラミング演習","text":"","path":["第 10 章   探索","10.7   練習問題"],"tags":[]},{"location":"chapter_searching/exercises/#1_1","level":3,"title":"1.   整列済み配列の二分探索","text":"<p>狭義の昇順に並んだ整数配列 <code>nums</code> と目的の値 <code>target</code> が与えられます。二分探索で <code>target</code> を探し、存在すれば配列内のインデックスを、存在しなければ -1 を返してください。</p> 解くためのヒント <ol> <li>初期区間は left = 0、right = n - 1 で、区間が空でない条件は left &lt;= right です</li> <li>mid = left + (right - left) // 2 で中点を計算します</li> <li><code>nums[mid]</code> が <code>target</code> より小さければ左境界を <code>mid + 1</code> へ、<code>nums[mid]</code> が <code>target</code> より大きければ右境界を <code>mid - 1</code> へ移します。等しければ、すぐに返します</li> </ol> <p>LeetCode</p>","path":["第 10 章   探索","10.7   練習問題"],"tags":[]},{"location":"chapter_searching/exercises/#2_1","level":3,"title":"2.   整列済み配列への挿入位置","text":"<p>狭義の昇順に並んだ整数配列 <code>nums</code> と目的の値 <code>target</code> が与えられます。</p> <p><code>target</code> が配列にあれば、そのインデックスを返してください。なければ、<code>target</code> を挿入しても狭義の昇順を保てる位置を返してください。答えは 0 の場合も、配列の長さと等しい場合もあります。二分探索を使ってください。</p> 解くためのヒント <ol> <li>答えは 0 の場合も、配列の長さ n の場合もあります</li> <li>両端を含む閉区間を使う場合、<code>nums[mid]</code> が <code>target</code> 以上なら <code>right = mid - 1</code> として、さらに左を調べます。そうでなければ <code>left = mid + 1</code> とします</li> <li>ループが終わったときの left が挿入位置です</li> </ol> <p>LeetCode</p>","path":["第 10 章   探索","10.7   練習問題"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/","level":1,"title":"10.4   ハッシュを使った最適化","text":"<p>アルゴリズムの問題では、線形探索をハッシュ探索に置き換えることで、時間計算量を減らす方法をよく使います。一つの問題を通して理解を深めましょう。</p> <p>問題</p> <p>整数配列 <code>nums</code> と目標要素 <code>target</code> が与えられます。配列内から和が <code>target</code> になる二つの要素を探し、それぞれのインデックスを返してください。解はいずれか一つでかまいません。</p>","path":["第 10 章   探索","10.4   ハッシュを使った最適化"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/#1041","level":2,"title":"10.4.1   線形探索:時間を使って領域を節約する","text":"<p>すべての組み合わせを直接調べることを考えます。図 10-9 のように二重ループを使い、各反復で二つの整数の和が <code>target</code> かどうかを判定し、そうであればインデックスを返します。</p> <p></p> <p> 図 10-9   線形探索で二数の和を求める </p> <p>コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby two_sum.py<pre><code>def two_sum_brute_force(nums: list[int], target: int) -&gt; list[int]:\n \"\"\"方法 1:総当たり列挙\"\"\"\n # 二重ループのため、時間計算量は O(n^2)\n for i in range(len(nums) - 1):\n for j in range(i + 1, len(nums)):\n if nums[i] + nums[j] == target:\n return [i, j]\n return []\n</code></pre> two_sum.cpp<pre><code>/* 方法 1:総当たり列挙 */\nvector&lt;int&gt; twoSumBruteForce(vector&lt;int&gt; &amp;nums, int target) {\n int size = nums.size();\n // 二重ループのため、時間計算量は O(n^2)\n for (int i = 0; i &lt; size - 1; i++) {\n for (int j = i + 1; j &lt; size; j++) {\n if (nums[i] + nums[j] == target)\n return {i, j};\n }\n }\n return {};\n}\n</code></pre> two_sum.java<pre><code>/* 方法 1:総当たり列挙 */\nint[] twoSumBruteForce(int[] nums, int target) {\n int size = nums.length;\n // 二重ループのため、時間計算量は O(n^2)\n for (int i = 0; i &lt; size - 1; i++) {\n for (int j = i + 1; j &lt; size; j++) {\n if (nums[i] + nums[j] == target)\n return new int[] { i, j };\n }\n }\n return new int[0];\n}\n</code></pre> two_sum.cs<pre><code>/* 方法 1:総当たり列挙 */\nint[] TwoSumBruteForce(int[] nums, int target) {\n int size = nums.Length;\n // 二重ループのため、時間計算量は O(n^2)\n for (int i = 0; i &lt; size - 1; i++) {\n for (int j = i + 1; j &lt; size; j++) {\n if (nums[i] + nums[j] == target)\n return [i, j];\n }\n }\n return [];\n}\n</code></pre> two_sum.go<pre><code>/* 方法 1:総当たり列挙 */\nfunc twoSumBruteForce(nums []int, target int) []int {\n size := len(nums)\n // 二重ループのため、時間計算量は O(n^2)\n for i := 0; i &lt; size-1; i++ {\n for j := i + 1; j &lt; size; j++ {\n if nums[i]+nums[j] == target {\n return []int{i, j}\n }\n }\n }\n return nil\n}\n</code></pre> two_sum.swift<pre><code>/* 方法 1:総当たり列挙 */\nfunc twoSumBruteForce(nums: [Int], target: Int) -&gt; [Int] {\n // 二重ループのため、時間計算量は O(n^2)\n for i in nums.indices.dropLast() {\n for j in nums.indices.dropFirst(i + 1) {\n if nums[i] + nums[j] == target {\n return [i, j]\n }\n }\n }\n return [0]\n}\n</code></pre> two_sum.js<pre><code>/* 方法 1:総当たり列挙 */\nfunction twoSumBruteForce(nums, target) {\n const n = nums.length;\n // 二重ループのため、時間計算量は O(n^2)\n for (let i = 0; i &lt; n; i++) {\n for (let j = i + 1; j &lt; n; j++) {\n if (nums[i] + nums[j] === target) {\n return [i, j];\n }\n }\n }\n return [];\n}\n</code></pre> two_sum.ts<pre><code>/* 方法 1:総当たり列挙 */\nfunction twoSumBruteForce(nums: number[], target: number): number[] {\n const n = nums.length;\n // 二重ループのため、時間計算量は O(n^2)\n for (let i = 0; i &lt; n; i++) {\n for (let j = i + 1; j &lt; n; j++) {\n if (nums[i] + nums[j] === target) {\n return [i, j];\n }\n }\n }\n return [];\n}\n</code></pre> two_sum.dart<pre><code>/* 方法 1:総当たり列挙 */\nList&lt;int&gt; twoSumBruteForce(List&lt;int&gt; nums, int target) {\n int size = nums.length;\n // 二重ループのため、時間計算量は O(n^2)\n for (var i = 0; i &lt; size - 1; i++) {\n for (var j = i + 1; j &lt; size; j++) {\n if (nums[i] + nums[j] == target) return [i, j];\n }\n }\n return [0];\n}\n</code></pre> two_sum.rs<pre><code>/* 方法 1:総当たり列挙 */\npub fn two_sum_brute_force(nums: &amp;Vec&lt;i32&gt;, target: i32) -&gt; Option&lt;Vec&lt;i32&gt;&gt; {\n let size = nums.len();\n // 二重ループのため、時間計算量は O(n^2)\n for i in 0..size - 1 {\n for j in i + 1..size {\n if nums[i] + nums[j] == target {\n return Some(vec![i as i32, j as i32]);\n }\n }\n }\n None\n}\n</code></pre> two_sum.c<pre><code>/* 方法 1:総当たり列挙 */\nint *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) {\n for (int i = 0; i &lt; numsSize; ++i) {\n for (int j = i + 1; j &lt; numsSize; ++j) {\n if (nums[i] + nums[j] == target) {\n int *res = malloc(sizeof(int) * 2);\n res[0] = i, res[1] = j;\n *returnSize = 2;\n return res;\n }\n }\n }\n *returnSize = 0;\n return NULL;\n}\n</code></pre> two_sum.kt<pre><code>/* 方法 1:総当たり列挙 */\nfun twoSumBruteForce(nums: IntArray, target: Int): IntArray {\n val size = nums.size\n // 二重ループのため、時間計算量は O(n^2)\n for (i in 0..&lt;size - 1) {\n for (j in i + 1..&lt;size) {\n if (nums[i] + nums[j] == target) return intArrayOf(i, j)\n }\n }\n return IntArray(0)\n}\n</code></pre> two_sum.rb<pre><code>### 方法 1:総当たり列挙 ###\ndef two_sum_brute_force(nums, target)\n # 二重ループのため、時間計算量は O(n^2)\n for i in 0...(nums.length - 1)\n for j in (i + 1)...nums.length\n return [i, j] if nums[i] + nums[j] == target\n end\n end\n\n []\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>この方法の時間計算量は \\(O(n^2)\\)、空間計算量は \\(O(1)\\) であり、データが多いと非常に時間がかかります。</p>","path":["第 10 章   探索","10.4   ハッシュを使った最適化"],"tags":[]},{"location":"chapter_searching/replace_linear_by_hashing/#1042","level":2,"title":"10.4.2   ハッシュ探索:領域を使って時間を節約する","text":"<p>配列の要素をキー、そのインデックスを値とするハッシュテーブルを使うことを考えます。配列をループで走査し、各反復で図 10-10 の手順を実行します。</p> <ol> <li>数値 <code>target - nums[i]</code> がハッシュテーブルにあるか調べ、あれば二つの要素のインデックスをそのまま返します。</li> <li><code>nums[i]</code> とインデックス <code>i</code> のペアをハッシュテーブルに追加します。</li> </ol> &lt;1&gt;&lt;2&gt;&lt;3&gt; <p></p> <p></p> <p></p> <p> 図 10-10   補助ハッシュテーブルで二数の和を求める </p> <p>実装は次のとおりで、一重のループだけで済みます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby two_sum.py<pre><code>def two_sum_hash_table(nums: list[int], target: int) -&gt; list[int]:\n \"\"\"方法 2:補助ハッシュテーブル\"\"\"\n # 補助ハッシュテーブルの空間計算量は O(n)\n dic = {}\n # 一重ループのため、時間計算量は O(n)\n for i in range(len(nums)):\n if target - nums[i] in dic:\n return [dic[target - nums[i]], i]\n dic[nums[i]] = i\n return []\n</code></pre> two_sum.cpp<pre><code>/* 方法 2:補助ハッシュテーブル */\nvector&lt;int&gt; twoSumHashTable(vector&lt;int&gt; &amp;nums, int target) {\n int size = nums.size();\n // 補助ハッシュテーブルの空間計算量は O(n)\n unordered_map&lt;int, int&gt; dic;\n // 一重ループのため、時間計算量は O(n)\n for (int i = 0; i &lt; size; i++) {\n if (dic.find(target - nums[i]) != dic.end()) {\n return {dic[target - nums[i]], i};\n }\n dic.emplace(nums[i], i);\n }\n return {};\n}\n</code></pre> two_sum.java<pre><code>/* 方法 2:補助ハッシュテーブル */\nint[] twoSumHashTable(int[] nums, int target) {\n int size = nums.length;\n // 補助ハッシュテーブルの空間計算量は O(n)\n Map&lt;Integer, Integer&gt; dic = new HashMap&lt;&gt;();\n // 一重ループのため、時間計算量は O(n)\n for (int i = 0; i &lt; size; i++) {\n if (dic.containsKey(target - nums[i])) {\n return new int[] { dic.get(target - nums[i]), i };\n }\n dic.put(nums[i], i);\n }\n return new int[0];\n}\n</code></pre> two_sum.cs<pre><code>/* 方法 2:補助ハッシュテーブル */\nint[] TwoSumHashTable(int[] nums, int target) {\n int size = nums.Length;\n // 補助ハッシュテーブルの空間計算量は O(n)\n Dictionary&lt;int, int&gt; dic = [];\n // 一重ループのため、時間計算量は O(n)\n for (int i = 0; i &lt; size; i++) {\n if (dic.ContainsKey(target - nums[i])) {\n return [dic[target - nums[i]], i];\n }\n dic.Add(nums[i], i);\n }\n return [];\n}\n</code></pre> two_sum.go<pre><code>/* 方法 2:補助ハッシュテーブル */\nfunc twoSumHashTable(nums []int, target int) []int {\n // 補助ハッシュテーブルの空間計算量は O(n)\n hashTable := map[int]int{}\n // 一重ループのため、時間計算量は O(n)\n for idx, val := range nums {\n if preIdx, ok := hashTable[target-val]; ok {\n return []int{preIdx, idx}\n }\n hashTable[val] = idx\n }\n return nil\n}\n</code></pre> two_sum.swift<pre><code>/* 方法 2:補助ハッシュテーブル */\nfunc twoSumHashTable(nums: [Int], target: Int) -&gt; [Int] {\n // 補助ハッシュテーブルの空間計算量は O(n)\n var dic: [Int: Int] = [:]\n // 一重ループのため、時間計算量は O(n)\n for i in nums.indices {\n if let j = dic[target - nums[i]] {\n return [j, i]\n }\n dic[nums[i]] = i\n }\n return [0]\n}\n</code></pre> two_sum.js<pre><code>/* 方法 2:補助ハッシュテーブル */\nfunction twoSumHashTable(nums, target) {\n // 補助ハッシュテーブルの空間計算量は O(n)\n let m = {};\n // 一重ループのため、時間計算量は O(n)\n for (let i = 0; i &lt; nums.length; i++) {\n if (m[target - nums[i]] !== undefined) {\n return [m[target - nums[i]], i];\n } else {\n m[nums[i]] = i;\n }\n }\n return [];\n}\n</code></pre> two_sum.ts<pre><code>/* 方法 2:補助ハッシュテーブル */\nfunction twoSumHashTable(nums: number[], target: number): number[] {\n // 補助ハッシュテーブルの空間計算量は O(n)\n let m: Map&lt;number, number&gt; = new Map();\n // 一重ループのため、時間計算量は O(n)\n for (let i = 0; i &lt; nums.length; i++) {\n let index = m.get(target - nums[i]);\n if (index !== undefined) {\n return [index, i];\n } else {\n m.set(nums[i], i);\n }\n }\n return [];\n}\n</code></pre> two_sum.dart<pre><code>/* 方法 2:補助ハッシュテーブル */\nList&lt;int&gt; twoSumHashTable(List&lt;int&gt; nums, int target) {\n int size = nums.length;\n // 補助ハッシュテーブルの空間計算量は O(n)\n Map&lt;int, int&gt; dic = HashMap();\n // 一重ループのため、時間計算量は O(n)\n for (var i = 0; i &lt; size; i++) {\n if (dic.containsKey(target - nums[i])) {\n return [dic[target - nums[i]]!, i];\n }\n dic.putIfAbsent(nums[i], () =&gt; i);\n }\n return [0];\n}\n</code></pre> two_sum.rs<pre><code>/* 方法 2:補助ハッシュテーブル */\npub fn two_sum_hash_table(nums: &amp;Vec&lt;i32&gt;, target: i32) -&gt; Option&lt;Vec&lt;i32&gt;&gt; {\n // 補助ハッシュテーブルの空間計算量は O(n)\n let mut dic = HashMap::new();\n // 一重ループのため、時間計算量は O(n)\n for (i, num) in nums.iter().enumerate() {\n match dic.get(&amp;(target - num)) {\n Some(v) =&gt; return Some(vec![*v as i32, i as i32]),\n None =&gt; dic.insert(num, i as i32),\n };\n }\n None\n}\n</code></pre> two_sum.c<pre><code>/* ハッシュテーブル */\ntypedef struct {\n int key;\n int val;\n UT_hash_handle hh; // uthash.h を用いて実装する\n} HashTable;\n\n/* ハッシュテーブルから取得する */\nHashTable *find(HashTable *h, int key) {\n HashTable *tmp;\n HASH_FIND_INT(h, &amp;key, tmp);\n return tmp;\n}\n\n/* ハッシュテーブルに要素を挿入する */\nvoid insert(HashTable **h, int key, int val) {\n HashTable *t = find(*h, key);\n if (t == NULL) {\n HashTable *tmp = malloc(sizeof(HashTable));\n tmp-&gt;key = key, tmp-&gt;val = val;\n HASH_ADD_INT(*h, key, tmp);\n } else {\n t-&gt;val = val;\n }\n}\n\n/* 方法 2:補助ハッシュテーブル */\nint *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) {\n HashTable *hashtable = NULL;\n for (int i = 0; i &lt; numsSize; i++) {\n HashTable *t = find(hashtable, target - nums[i]);\n if (t != NULL) {\n int *res = malloc(sizeof(int) * 2);\n res[0] = t-&gt;val, res[1] = i;\n *returnSize = 2;\n return res;\n }\n insert(&amp;hashtable, nums[i], i);\n }\n *returnSize = 0;\n return NULL;\n}\n</code></pre> two_sum.kt<pre><code>/* 方法 2:補助ハッシュテーブル */\nfun twoSumHashTable(nums: IntArray, target: Int): IntArray {\n val size = nums.size\n // 補助ハッシュテーブルの空間計算量は O(n)\n val dic = HashMap&lt;Int, Int&gt;()\n // 一重ループのため、時間計算量は O(n)\n for (i in 0..&lt;size) {\n if (dic.containsKey(target - nums[i])) {\n return intArrayOf(dic[target - nums[i]]!!, i)\n }\n dic[nums[i]] = i\n }\n return IntArray(0)\n}\n</code></pre> two_sum.rb<pre><code>### 方法 2:補助ハッシュテーブル ###\ndef two_sum_hash_table(nums, target)\n # 補助ハッシュテーブルの空間計算量は O(n)\n dic = {}\n # 一重ループのため、時間計算量は O(n)\n for i in 0...nums.length\n return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i])\n\n dic[nums[i]] = i\n end\n\n []\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>この方法では、ハッシュ探索によって時間計算量を \\(O(n^2)\\) から \\(O(n)\\) に減らし、実行効率を大きく向上させています。</p> <p>追加のハッシュテーブルを維持する必要があるため、空間計算量は \\(O(n)\\) です。それでも、時間と空間の効率のバランスがよいため、この問題の最適な解法となります。</p>","path":["第 10 章   探索","10.4   ハッシュを使った最適化"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/","level":1,"title":"10.5   探索アルゴリズムを改めて考える","text":"<p>探索アルゴリズム(searching algorithm)は、配列、連結リスト、木、グラフなどのデータ構造から、特定の条件を満たす一つまたは複数の要素を探すために使います。</p> <p>探索アルゴリズムは、実装の考え方によって次の二種類に分けられます。</p> <ul> <li>データ構造を走査して目的の要素を見つける方法です。配列、連結リスト、木、グラフの走査などが該当します。</li> <li>データの構造や既知の情報を利用して、効率よく要素を探す方法です。二分探索、ハッシュ探索、二分探索木による探索などが該当します。</li> </ul> <p>これらはすでに前の章で紹介した内容なので、探索アルゴリズムは私たちにとってなじみのないものではありません。この節では、より体系的な視点から探索アルゴリズムを改めて考えます。</p>","path":["第 10 章   探索","10.5   探索アルゴリズムを改めて考える"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1051","level":2,"title":"10.5.1   全探索","text":"<p>全探索は、データ構造の各要素を走査して目的の要素を見つけます。</p> <ul> <li>「線形探索」は、配列や連結リストなどの線形データ構造に適しています。一方の端から要素を順に訪問し、目的の要素を見つけるか、見つからないままもう一方の端に達するまで続けます。</li> <li>「幅優先探索」と「深さ優先探索」は、グラフや木を走査する二つの戦略です。幅優先探索は開始ノードから層ごとに探索し、近いノードから遠いノードへと訪問します。深さ優先探索は開始ノードから一本の経路を行き止まりまで進み、戻って別の経路を試し、データ構造全体を走査するまで続けます。</li> </ul> <p>全探索の長所は単純で汎用性が高く、データの前処理や追加のデータ構造が不要なことです。</p> <p>一方、時間計算量は \\(O(n)\\) です。\\(n\\) は要素数なので、データが多い場合は性能が低くなります。</p>","path":["第 10 章   探索","10.5   探索アルゴリズムを改めて考える"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1052","level":2,"title":"10.5.2   データの特性に応じた探索","text":"<p>データ固有の性質(整列済みであることなど)を利用して探索過程を最適化し、目的の要素をより効率よく見つける方法です。</p> <ul> <li>「二分探索」は、データが整列済みであることを利用して効率よく探索します。配列にのみ適用できます。</li> <li>「ハッシュ探索」は、ハッシュテーブルを利用し、検索するデータと目的のデータをキーと値のペアとして対応付けて照会します。</li> <li>「木探索」は、二分探索木などの特定の木構造でノードの値を比較し、候補となるノードを素早く除外して目的の要素を見つけます。</li> </ul> <p>これらのアルゴリズムの長所は効率が高く、時間計算量が \\(O(\\log n)\\)、さらには \\(O(1)\\) にもなることです。</p> <p>ただし、利用にはデータの前処理が必要となることが多いです。例えば、二分探索では配列をあらかじめソートする必要があり、ハッシュ探索と木探索では追加のデータ構造が必要です。それらの維持にも時間と領域が余分にかかります。</p> <p>ヒント</p> <p>データの特性に応じた探索アルゴリズムは、検索アルゴリズムとも呼ばれ、主に特定のデータ構造から目的の要素を素早く取得するために使います。</p>","path":["第 10 章   探索","10.5   探索アルゴリズムを改めて考える"],"tags":[]},{"location":"chapter_searching/searching_algorithm_revisited/#1053","level":2,"title":"10.5.3   探索方法の選択","text":"<p>大きさ \\(n\\) のデータ集合から目的の要素を探すには、線形探索、二分探索、木探索、ハッシュ探索などの方法が使えます。それぞれの仕組みを図 10-11 に示します。</p> <p></p> <p> 図 10-11   さまざまな探索戦略 </p> <p>これらの方法の操作効率と特徴を表 10-1 に示します。</p> <p> 表 10-1   探索アルゴリズムの効率の比較 </p> 線形探索 二分探索 木探索 ハッシュ探索 要素の探索 \\(O(n)\\) \\(O(\\log n)\\) \\(O(\\log n)\\) \\(O(1)\\) 要素の挿入 \\(O(1)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\) 要素の削除 \\(O(n)\\) \\(O(n)\\) \\(O(\\log n)\\) \\(O(1)\\) 追加領域 \\(O(1)\\) \\(O(1)\\) \\(O(n)\\) \\(O(n)\\) データの前処理 / ソート \\(O(n \\log n)\\) 木の構築 \\(O(n \\log n)\\) ハッシュテーブルの構築 \\(O(n)\\) データの整列状態 未整列 整列済み 整列済み 未整列 <p>探索アルゴリズムの選択は、規模、必要な探索性能、データの照会と更新の頻度などにも左右されます。</p> <p>線形探索</p> <ul> <li>汎用性が高く、データの前処理が不要です。照会が一回だけなら、ほかの三つの方法では前処理の時間が線形探索の時間より長くなります。</li> <li>データが少ない場合に適しています。この場合、時間計算量が効率に与える影響は小さくなります。</li> <li>データを追加で管理する必要がないため、更新頻度が高い場合に適しています。</li> </ul> <p>二分探索</p> <ul> <li>データが多い場合に適しており、効率が安定しています。最悪時間計算量は \\(O(\\log n)\\) です。</li> <li>配列の格納には連続したメモリ領域が必要なので、データが多すぎる場合には適しません。</li> <li>整列済み配列の維持にかかる負担が大きいため、データを頻繁に追加・削除する場合には適しません。</li> </ul> <p>ハッシュ探索</p> <ul> <li>平均時間計算量が \\(O(1)\\) なので、高い照会性能が求められる場合に適しています。</li> <li>ハッシュテーブルはデータの順序を維持できないため、整列済みデータや範囲検索が必要な場合には適しません。</li> <li>ハッシュ関数とハッシュ衝突への対処方法に大きく依存し、性能が悪化するリスクが比較的高くなります。</li> <li>衝突をできるだけ減らして照会性能を高く保つために追加領域が必要なので、データが多すぎる場合には適しません。</li> </ul> <p>木探索</p> <ul> <li>木のノードはメモリ内に分散して格納されるため、膨大な量のデータにも適しています。</li> <li>データの順序の維持や範囲検索が必要な場合に適しています。</li> <li>ノードの追加・削除を続けるうちに二分探索木が偏り、時間計算量が \\(O(n)\\) に悪化する可能性があります。</li> <li>AVL 木や赤黒木を使えば、各操作を安定して \\(O(\\log n)\\) で実行できますが、平衡を維持するための追加の処理が必要になります。</li> </ul>","path":["第 10 章   探索","10.5   探索アルゴリズムを改めて考える"],"tags":[]},{"location":"chapter_searching/summary/","level":1,"title":"10.6   まとめ","text":"","path":["第 10 章   探索","10.6   まとめ"],"tags":[]},{"location":"chapter_searching/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>二分探索はデータが整列されていることを利用し、ループで探索区間を半分ずつ縮めながら探索します。入力データが整列済みであることが必要であり、配列または配列に基づくデータ構造にのみ適用できます。</li> <li>全探索はデータ構造を走査してデータを見つけます。線形探索は配列や連結リストに、幅優先探索と深さ優先探索はグラフや木に適しています。これらは汎用性が高く、データの前処理も不要ですが、時間計算量 \\(O(n)\\) は大きいです。</li> <li>ハッシュ探索、木探索、二分探索は効率的な探索方法であり、特定のデータ構造の中から目的の要素を素早く見つけられます。時間計算量は \\(O(\\log n)\\)、さらには \\(O(1)\\) にもなりますが、通常は補助的なデータ構造が必要となります。</li> <li>実際には、データの規模、求められる探索性能、データの照会や更新の頻度などを具体的に分析して、適切な探索方法を選ぶ必要があります。</li> <li>線形探索は小規模または頻繁に更新されるデータに、二分探索は大規模で整列済みのデータに適しています。ハッシュ探索は照会の効率が求められ、範囲検索を必要としないデータに、木探索は順序の維持と範囲検索が必要な大規模で動的なデータに適しています。</li> <li>線形探索をハッシュ探索に置き換えることは、実行時間を改善するためによく使われる戦略であり、時間計算量を \\(O(n)\\) から \\(O(1)\\) に減らせます。</li> </ul>","path":["第 10 章   探索","10.6   まとめ"],"tags":[]},{"location":"chapter_sorting/","level":1,"title":"第 11 章   ソート","text":"<p>概要</p> <p>ソートは混沌を秩序へと変える魔法の鍵のようなものであり、データをより効率的に理解し、処理できるようにします。</p> <p>単純な昇順であれ、複雑な分類と並べ替えであれ、ソートはデータの調和の美しさを見せてくれます。</p>","path":["第 11 章   ソート"],"tags":[]},{"location":"chapter_sorting/#_1","level":2,"title":"この章の内容","text":"<ul> <li>11.1   ソートアルゴリズム</li> <li>11.2   選択ソート</li> <li>11.3   バブルソート</li> <li>11.4   挿入ソート</li> <li>11.5   クイックソート</li> <li>11.6   マージソート</li> <li>11.7   ヒープソート</li> <li>11.8   バケットソート</li> <li>11.9   カウントソート</li> <li>11.10   基数ソート</li> <li>11.11   まとめ</li> <li>11.12   練習問題</li> </ul>","path":["第 11 章   ソート"],"tags":[]},{"location":"chapter_sorting/bubble_sort/","level":1,"title":"11.3   バブルソート","text":"<p>バブルソート(bubble sort)は、隣り合う要素の比較と交換を繰り返してソートします。この過程が泡が下から上へ浮かぶ様子に似ていることから、この名前が付いています。</p> <p>図 11-4 のように、要素の交換で泡の動きを再現できます。配列の左端から右へ走査し、隣り合う要素を順に比較して、「左の要素 &gt; 右の要素」なら交換します。走査が終わると、最大の要素が配列の右端に移ります。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 11-4   要素の交換で泡の動きを再現する </p>","path":["第 11 章   ソート","11.3   バブルソート"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1131","level":2,"title":"11.3.1   アルゴリズムの流れ","text":"<p>配列の長さを \\(n\\) とすると、バブルソートの手順は図 11-5 のとおりです。</p> <ol> <li>まず、\\(n\\) 個の要素に対して泡を浮かべる操作を行い、最大の要素を正しい位置へ移します。</li> <li>続いて、残りの \\(n - 1\\) 個の要素に同じ操作を行い、2 番目に大きい要素を正しい位置へ移します。</li> <li>同様に \\(n - 1\\) 回繰り返すと、大きいほうから \\(n - 1\\) 個の要素がすべて正しい位置へ移ります。</li> <li>残った一つの要素は必ず最小要素なので、ソートは不要です。これで配列全体のソートが完了します。</li> </ol> <p></p> <p> 図 11-5   バブルソートの流れ </p> <p>コード例は次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bubble_sort.py<pre><code>def bubble_sort(nums: list[int]):\n \"\"\"バブルソート\"\"\"\n n = len(nums)\n # 外側のループ:未ソート区間は [0, i]\n for i in range(n - 1, 0, -1):\n # 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j in range(i):\n if nums[j] &gt; nums[j + 1]:\n # nums[j] と nums[j + 1] を交換する\n nums[j], nums[j + 1] = nums[j + 1], nums[j]\n</code></pre> bubble_sort.cpp<pre><code>/* バブルソート */\nvoid bubbleSort(vector&lt;int&gt; &amp;nums) {\n // 外側のループ:未ソート区間は [0, i]\n for (int i = nums.size() - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n // ここでは std::swap() 関数を使う\n swap(nums[j], nums[j + 1]);\n }\n }\n }\n}\n</code></pre> bubble_sort.java<pre><code>/* バブルソート */\nvoid bubbleSort(int[] nums) {\n // 外側のループ:未ソート区間は [0, i]\n for (int i = nums.length - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n }\n }\n }\n}\n</code></pre> bubble_sort.cs<pre><code>/* バブルソート */\nvoid BubbleSort(int[] nums) {\n // 外側のループ:未ソート区間は [0, i]\n for (int i = nums.Length - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n }\n }\n }\n}\n</code></pre> bubble_sort.go<pre><code>/* バブルソート */\nfunc bubbleSort(nums []int) {\n // 外側のループ:未ソート区間は [0, i]\n for i := len(nums) - 1; i &gt; 0; i-- {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j := 0; j &lt; i; j++ {\n if nums[j] &gt; nums[j+1] {\n // nums[j] と nums[j + 1] を交換する\n nums[j], nums[j+1] = nums[j+1], nums[j]\n }\n }\n }\n}\n</code></pre> bubble_sort.swift<pre><code>/* バブルソート */\nfunc bubbleSort(nums: inout [Int]) {\n // 外側のループ:未ソート区間は [0, i]\n for i in nums.indices.dropFirst().reversed() {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j in 0 ..&lt; i {\n if nums[j] &gt; nums[j + 1] {\n // nums[j] と nums[j + 1] を交換する\n nums.swapAt(j, j + 1)\n }\n }\n }\n}\n</code></pre> bubble_sort.js<pre><code>/* バブルソート */\nfunction bubbleSort(nums) {\n // 外側のループ:未ソート区間は [0, i]\n for (let i = nums.length - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (let j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n }\n }\n }\n}\n</code></pre> bubble_sort.ts<pre><code>/* バブルソート */\nfunction bubbleSort(nums: number[]): void {\n // 外側のループ:未ソート区間は [0, i]\n for (let i = nums.length - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (let j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n }\n }\n }\n}\n</code></pre> bubble_sort.dart<pre><code>/* バブルソート */\nvoid bubbleSort(List&lt;int&gt; nums) {\n // 外側のループ:未ソート区間は [0, i]\n for (int i = nums.length - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n }\n }\n }\n}\n</code></pre> bubble_sort.rs<pre><code>/* バブルソート */\nfn bubble_sort(nums: &amp;mut [i32]) {\n // 外側のループ:未ソート区間は [0, i]\n for i in (1..nums.len()).rev() {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j in 0..i {\n if nums[j] &gt; nums[j + 1] {\n // nums[j] と nums[j + 1] を交換する\n nums.swap(j, j + 1);\n }\n }\n }\n}\n</code></pre> bubble_sort.c<pre><code>/* バブルソート */\nvoid bubbleSort(int nums[], int size) {\n // 外側のループ:未ソート区間は [0, i]\n for (int i = size - 1; i &gt; 0; i--) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n int temp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = temp;\n }\n }\n }\n}\n</code></pre> bubble_sort.kt<pre><code>/* バブルソート */\nfun bubbleSort(nums: IntArray) {\n // 外側のループ:未ソート区間は [0, i]\n for (i in nums.size - 1 downTo 1) {\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (j in 0..&lt;i) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n val temp = nums[j]\n nums[j] = nums[j + 1]\n nums[j + 1] = temp\n }\n }\n }\n}\n</code></pre> bubble_sort.rb<pre><code>### バブルソート ###\ndef bubble_sort(nums)\n n = nums.length\n # 外側のループ:未ソート区間は [0, i]\n for i in (n - 1).downto(1)\n # 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j in 0...i\n if nums[j] &gt; nums[j + 1]\n # nums[j] と nums[j + 1] を交換する\n nums[j], nums[j + 1] = nums[j + 1], nums[j]\n end\n end\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.3   バブルソート"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1132","level":2,"title":"11.3.2   効率の改善","text":"<p>ある反復で一度も交換しなかった場合、配列はすでに整列済みなので、そのまま結果を返せます。そこで、フラグ <code>flag</code> を追加してこの状況を検知し、該当したら直ちに返すようにします。</p> <p>この改善後も、最悪時間計算量と平均時間計算量は \\(O(n^2)\\) のままです。ただし、入力配列が完全に整列済みなら、最良時間計算量 \\(O(n)\\) を達成できます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bubble_sort.py<pre><code>def bubble_sort_with_flag(nums: list[int]):\n \"\"\"バブルソート(フラグによる最適化)\"\"\"\n n = len(nums)\n # 外側のループ:未ソート区間は [0, i]\n for i in range(n - 1, 0, -1):\n flag = False # フラグを初期化する\n # 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j in range(i):\n if nums[j] &gt; nums[j + 1]:\n # nums[j] と nums[j + 1] を交換する\n nums[j], nums[j + 1] = nums[j + 1], nums[j]\n flag = True # 要素を交換したことを記録する\n if not flag:\n break # 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n</code></pre> bubble_sort.cpp<pre><code>/* バブルソート(フラグによる最適化)*/\nvoid bubbleSortWithFlag(vector&lt;int&gt; &amp;nums) {\n // 外側のループ:未ソート区間は [0, i]\n for (int i = nums.size() - 1; i &gt; 0; i--) {\n bool flag = false; // フラグを初期化する\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n // ここでは std::swap() 関数を使う\n swap(nums[j], nums[j + 1]);\n flag = true; // 要素を交換したことを記録する\n }\n }\n if (!flag)\n break; // 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n }\n}\n</code></pre> bubble_sort.java<pre><code>/* バブルソート(フラグによる最適化) */\nvoid bubbleSortWithFlag(int[] nums) {\n // 外側のループ:未ソート区間は [0, i]\n for (int i = nums.length - 1; i &gt; 0; i--) {\n boolean flag = false; // フラグを初期化する\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n flag = true; // 要素を交換したことを記録する\n }\n }\n if (!flag)\n break; // 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n }\n}\n</code></pre> bubble_sort.cs<pre><code>/* バブルソート(フラグによる最適化)*/\nvoid BubbleSortWithFlag(int[] nums) {\n // 外側のループ:未ソート区間は [0, i]\n for (int i = nums.Length - 1; i &gt; 0; i--) {\n bool flag = false; // フラグを初期化する\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);\n flag = true; // 要素を交換したことを記録する\n }\n }\n if (!flag) break; // 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n }\n}\n</code></pre> bubble_sort.go<pre><code>/* バブルソート(フラグによる最適化)*/\nfunc bubbleSortWithFlag(nums []int) {\n // 外側のループ:未ソート区間は [0, i]\n for i := len(nums) - 1; i &gt; 0; i-- {\n flag := false // フラグを初期化する\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j := 0; j &lt; i; j++ {\n if nums[j] &gt; nums[j+1] {\n // nums[j] と nums[j + 1] を交換する\n nums[j], nums[j+1] = nums[j+1], nums[j]\n flag = true // 要素を交換したことを記録する\n }\n }\n if flag == false { // 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n break\n }\n }\n}\n</code></pre> bubble_sort.swift<pre><code>/* バブルソート(フラグによる最適化)*/\nfunc bubbleSortWithFlag(nums: inout [Int]) {\n // 外側のループ:未ソート区間は [0, i]\n for i in nums.indices.dropFirst().reversed() {\n var flag = false // フラグを初期化する\n for j in 0 ..&lt; i {\n if nums[j] &gt; nums[j + 1] {\n // nums[j] と nums[j + 1] を交換する\n nums.swapAt(j, j + 1)\n flag = true // 要素を交換したことを記録する\n }\n }\n if !flag { // 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n break\n }\n }\n}\n</code></pre> bubble_sort.js<pre><code>/* バブルソート(フラグによる最適化)*/\nfunction bubbleSortWithFlag(nums) {\n // 外側のループ:未ソート区間は [0, i]\n for (let i = nums.length - 1; i &gt; 0; i--) {\n let flag = false; // フラグを初期化する\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (let j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n flag = true; // 要素を交換したことを記録する\n }\n }\n if (!flag) break; // 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n }\n}\n</code></pre> bubble_sort.ts<pre><code>/* バブルソート(フラグによる最適化)*/\nfunction bubbleSortWithFlag(nums: number[]): void {\n // 外側のループ:未ソート区間は [0, i]\n for (let i = nums.length - 1; i &gt; 0; i--) {\n let flag = false; // フラグを初期化する\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (let j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n let tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n flag = true; // 要素を交換したことを記録する\n }\n }\n if (!flag) break; // 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n }\n}\n</code></pre> bubble_sort.dart<pre><code>/* バブルソート(フラグによる最適化)*/\nvoid bubbleSortWithFlag(List&lt;int&gt; nums) {\n // 外側のループ:未ソート区間は [0, i]\n for (int i = nums.length - 1; i &gt; 0; i--) {\n bool flag = false; // フラグを初期化する\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n int tmp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = tmp;\n flag = true; // 要素を交換したことを記録する\n }\n }\n if (!flag) break; // 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n }\n}\n</code></pre> bubble_sort.rs<pre><code>/* バブルソート(フラグによる最適化) */\nfn bubble_sort_with_flag(nums: &amp;mut [i32]) {\n // 外側のループ:未ソート区間は [0, i]\n for i in (1..nums.len()).rev() {\n let mut flag = false; // フラグを初期化する\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j in 0..i {\n if nums[j] &gt; nums[j + 1] {\n // nums[j] と nums[j + 1] を交換する\n nums.swap(j, j + 1);\n flag = true; // 要素を交換したことを記録する\n }\n }\n if !flag {\n break; // 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n };\n }\n}\n</code></pre> bubble_sort.c<pre><code>/* バブルソート(フラグによる最適化)*/\nvoid bubbleSortWithFlag(int nums[], int size) {\n // 外側のループ:未ソート区間は [0, i]\n for (int i = size - 1; i &gt; 0; i--) {\n bool flag = false;\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (int j = 0; j &lt; i; j++) {\n if (nums[j] &gt; nums[j + 1]) {\n int temp = nums[j];\n nums[j] = nums[j + 1];\n nums[j + 1] = temp;\n flag = true;\n }\n }\n if (!flag)\n break;\n }\n}\n</code></pre> bubble_sort.kt<pre><code>/* バブルソート(フラグによる最適化) */\nfun bubbleSortWithFlag(nums: IntArray) {\n // 外側のループ:未ソート区間は [0, i]\n for (i in nums.size - 1 downTo 1) {\n var flag = false // フラグを初期化する\n // 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for (j in 0..&lt;i) {\n if (nums[j] &gt; nums[j + 1]) {\n // nums[j] と nums[j + 1] を交換する\n val temp = nums[j]\n nums[j] = nums[j + 1]\n nums[j + 1] = temp\n flag = true // 要素を交換したことを記録する\n }\n }\n if (!flag) break // 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n }\n}\n</code></pre> bubble_sort.rb<pre><code>### バブルソート(フラグによる最適化)###\ndef bubble_sort_with_flag(nums)\n n = nums.length\n # 外側のループ:未ソート区間は [0, i]\n for i in (n - 1).downto(1)\n flag = false # フラグを初期化する\n\n # 内側のループ:未ソート区間 [0, i] の最大要素を区間の右端に移す\n for j in 0...i\n if nums[j] &gt; nums[j + 1]\n # nums[j] と nums[j + 1] を交換する\n nums[j], nums[j + 1] = nums[j + 1], nums[j]\n flag = true # 要素を交換したことを記録する\n end\n end\n\n break unless flag # 今回の「バブル」で要素を一度も交換しなかった場合、直ちに終了する\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.3   バブルソート"],"tags":[]},{"location":"chapter_sorting/bubble_sort/#1133","level":2,"title":"11.3.3   アルゴリズムの特徴","text":"<ul> <li>時間計算量は \\(O(n^2)\\)、適応的ソート:各反復で走査する配列の長さは順に \\(n - 1\\)、\\(n - 2\\)、\\(\\dots\\)、\\(2\\)、\\(1\\) で、合計は \\((n - 1) n / 2\\) です。<code>flag</code> による改善後は、最良時間計算量が \\(O(n)\\) になります。</li> <li>空間計算量は \\(O(1)\\)、インプレースソート:ポインタ \\(i\\) と \\(j\\) は定数サイズの追加領域を使います。</li> <li>安定ソート:泡を浮かべる操作では、等しい要素を交換しないためです。</li> </ul>","path":["第 11 章   ソート","11.3   バブルソート"],"tags":[]},{"location":"chapter_sorting/bucket_sort/","level":1,"title":"11.8   バケットソート","text":"<p>ここまでのソートアルゴリズムは、要素の大小を比較する「比較ソート」です。この種類の最悪時間計算量の下界は \\(\\Omega(n \\log n)\\) です。ここからは、時間計算量を線形にできる「非比較ソート」をいくつか見ていきます。</p> <p>バケットソート(bucket sort)は、分割統治法の典型的な応用です。大小の順序を持ち、それぞれが一定のデータ範囲に対応するバケットを用意して、データを均等に振り分けます。次に各バケット内でソートし、最後にバケットの順序に従ってすべてのデータを結合します。</p>","path":["第 11 章   ソート","11.8   バケットソート"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1181","level":2,"title":"11.8.1   アルゴリズムの流れ","text":"<p>長さ \\(n\\) の配列で、要素が範囲 \\([0, 1)\\) の浮動小数点数である場合を考えます。バケットソートの流れは図 11-13 のとおりです。</p> <ol> <li>\\(k\\) 個のバケットを初期化し、\\(n\\) 個の要素を \\(k\\) 個のバケットへ振り分けます。</li> <li>各バケットをそれぞれソートします(ここでは言語の組み込みソート関数を使います)。</li> <li>バケットの小さいほうから順に結果を結合します。</li> </ol> <p></p> <p> 図 11-13   バケットソートの流れ </p> <p>コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby bucket_sort.py<pre><code>def bucket_sort(nums: list[float]):\n \"\"\"バケットソート\"\"\"\n # k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n k = len(nums) // 2\n buckets = [[] for _ in range(k)]\n # 1. 配列の要素を各バケットに振り分ける\n for num in nums:\n # 入力データの範囲は [0, 1) なので、num * k によりインデックス範囲 [0, k-1] に対応付ける\n i = int(num * k)\n # num をバケット i に追加する\n buckets[i].append(num)\n # 2. 各バケットをソートする\n for bucket in buckets:\n # 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい\n bucket.sort()\n # 3. バケットを走査して結果を結合する\n i = 0\n for bucket in buckets:\n for num in bucket:\n nums[i] = num\n i += 1\n</code></pre> bucket_sort.cpp<pre><code>/* バケットソート */\nvoid bucketSort(vector&lt;float&gt; &amp;nums) {\n // k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n int k = nums.size() / 2;\n vector&lt;vector&lt;float&gt;&gt; buckets(k);\n // 1. 配列の要素を各バケットに振り分ける\n for (float num : nums) {\n // 入力データの範囲は [0, 1) なので、num * k によりインデックス範囲 [0, k-1] に対応付ける\n int i = num * k;\n // num をバケット bucket_idx に追加する\n buckets[i].push_back(num);\n }\n // 2. 各バケットをソートする\n for (vector&lt;float&gt; &amp;bucket : buckets) {\n // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい\n sort(bucket.begin(), bucket.end());\n }\n // 3. バケットを走査して結果を結合する\n int i = 0;\n for (vector&lt;float&gt; &amp;bucket : buckets) {\n for (float num : bucket) {\n nums[i++] = num;\n }\n }\n}\n</code></pre> bucket_sort.java<pre><code>/* バケットソート */\nvoid bucketSort(float[] nums) {\n // k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n int k = nums.length / 2;\n List&lt;List&lt;Float&gt;&gt; buckets = new ArrayList&lt;&gt;();\n for (int i = 0; i &lt; k; i++) {\n buckets.add(new ArrayList&lt;&gt;());\n }\n // 1. 配列の要素を各バケットに振り分ける\n for (float num : nums) {\n // 入力データの範囲は [0, 1) なので、num * k によりインデックス範囲 [0, k-1] に対応付ける\n int i = (int) (num * k);\n // num をバケット i に追加する\n buckets.get(i).add(num);\n }\n // 2. 各バケットをソートする\n for (List&lt;Float&gt; bucket : buckets) {\n // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい\n Collections.sort(bucket);\n }\n // 3. バケットを走査して結果を結合する\n int i = 0;\n for (List&lt;Float&gt; bucket : buckets) {\n for (float num : bucket) {\n nums[i++] = num;\n }\n }\n}\n</code></pre> bucket_sort.cs<pre><code>/* バケットソート */\nvoid BucketSort(float[] nums) {\n // k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n int k = nums.Length / 2;\n List&lt;List&lt;float&gt;&gt; buckets = [];\n for (int i = 0; i &lt; k; i++) {\n buckets.Add([]);\n }\n // 1. 配列の要素を各バケットに振り分ける\n foreach (float num in nums) {\n // 入力データの範囲は [0, 1) なので、num * k によりインデックス範囲 [0, k-1] に対応付ける\n int i = (int)(num * k);\n // num をバケット i に追加する\n buckets[i].Add(num);\n }\n // 2. 各バケットをソートする\n foreach (List&lt;float&gt; bucket in buckets) {\n // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい\n bucket.Sort();\n }\n // 3. バケットを走査して結果を結合する\n int j = 0;\n foreach (List&lt;float&gt; bucket in buckets) {\n foreach (float num in bucket) {\n nums[j++] = num;\n }\n }\n}\n</code></pre> bucket_sort.go<pre><code>/* バケットソート */\nfunc bucketSort(nums []float64) {\n // k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n k := len(nums) / 2\n buckets := make([][]float64, k)\n for i := 0; i &lt; k; i++ {\n buckets[i] = make([]float64, 0)\n }\n // 1. 配列の要素を各バケットに振り分ける\n for _, num := range nums {\n // 入力データの範囲は [0, 1) なので、num * k によりインデックス範囲 [0, k-1] に対応付ける\n i := int(num * float64(k))\n // num をバケット i に追加する\n buckets[i] = append(buckets[i], num)\n }\n // 2. 各バケットをソートする\n for i := 0; i &lt; k; i++ {\n // 組み込みのスライスのソート関数を使う。他のソートアルゴリズムに置き換えてもよい\n sort.Float64s(buckets[i])\n }\n // 3. バケットを走査して結果を結合する\n i := 0\n for _, bucket := range buckets {\n for _, num := range bucket {\n nums[i] = num\n i++\n }\n }\n}\n</code></pre> bucket_sort.swift<pre><code>/* バケットソート */\nfunc bucketSort(nums: inout [Double]) {\n // k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n let k = nums.count / 2\n var buckets = (0 ..&lt; k).map { _ in [Double]() }\n // 1. 配列の要素を各バケットに振り分ける\n for num in nums {\n // 入力データの範囲は [0, 1) なので、num * k によりインデックス範囲 [0, k-1] に対応付ける\n let i = Int(num * Double(k))\n // num をバケット i に追加する\n buckets[i].append(num)\n }\n // 2. 各バケットをソートする\n for i in buckets.indices {\n // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい\n buckets[i].sort()\n }\n // 3. バケットを走査して結果を結合する\n var i = nums.startIndex\n for bucket in buckets {\n for num in bucket {\n nums[i] = num\n i += 1\n }\n }\n}\n</code></pre> bucket_sort.js<pre><code>/* バケットソート */\nfunction bucketSort(nums) {\n // k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n const k = nums.length / 2;\n const buckets = [];\n for (let i = 0; i &lt; k; i++) {\n buckets.push([]);\n }\n // 1. 配列の要素を各バケットに振り分ける\n for (const num of nums) {\n // 入力データの範囲は [0, 1) なので、num * k によりインデックス範囲 [0, k-1] に対応付ける\n const i = Math.floor(num * k);\n // num をバケット i に追加する\n buckets[i].push(num);\n }\n // 2. 各バケットをソートする\n for (const bucket of buckets) {\n // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい\n bucket.sort((a, b) =&gt; a - b);\n }\n // 3. バケットを走査して結果を結合する\n let i = 0;\n for (const bucket of buckets) {\n for (const num of bucket) {\n nums[i++] = num;\n }\n }\n}\n</code></pre> bucket_sort.ts<pre><code>/* バケットソート */\nfunction bucketSort(nums: number[]): void {\n // k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n const k = nums.length / 2;\n const buckets: number[][] = [];\n for (let i = 0; i &lt; k; i++) {\n buckets.push([]);\n }\n // 1. 配列の要素を各バケットに振り分ける\n for (const num of nums) {\n // 入力データの範囲は [0, 1) なので、num * k によりインデックス範囲 [0, k-1] に対応付ける\n const i = Math.floor(num * k);\n // num をバケット i に追加する\n buckets[i].push(num);\n }\n // 2. 各バケットをソートする\n for (const bucket of buckets) {\n // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい\n bucket.sort((a, b) =&gt; a - b);\n }\n // 3. バケットを走査して結果を結合する\n let i = 0;\n for (const bucket of buckets) {\n for (const num of bucket) {\n nums[i++] = num;\n }\n }\n}\n</code></pre> bucket_sort.dart<pre><code>/* バケットソート */\nvoid bucketSort(List&lt;double&gt; nums) {\n // k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n int k = nums.length ~/ 2;\n List&lt;List&lt;double&gt;&gt; buckets = List.generate(k, (index) =&gt; []);\n\n // 1. 配列の要素を各バケットに振り分ける\n for (double _num in nums) {\n // 入力データの範囲は [0, 1) なので、_num * k によりインデックス範囲 [0, k-1] に対応付ける\n int i = (_num * k).toInt();\n // _num をバケット bucket_idx に追加する\n buckets[i].add(_num);\n }\n // 2. 各バケットをソートする\n for (List&lt;double&gt; bucket in buckets) {\n bucket.sort();\n }\n // 3. バケットを走査して結果を結合する\n int i = 0;\n for (List&lt;double&gt; bucket in buckets) {\n for (double _num in bucket) {\n nums[i++] = _num;\n }\n }\n}\n</code></pre> bucket_sort.rs<pre><code>/* バケットソート */\nfn bucket_sort(nums: &amp;mut [f64]) {\n // k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n let k = nums.len() / 2;\n let mut buckets = vec![vec![]; k];\n // 1. 配列の要素を各バケットに振り分ける\n for &amp;num in nums.iter() {\n // 入力データの範囲は [0, 1) なので、num * k によりインデックス範囲 [0, k-1] に対応付ける\n let i = (num * k as f64) as usize;\n // num をバケット i に追加する\n buckets[i].push(num);\n }\n // 2. 各バケットをソートする\n for bucket in &amp;mut buckets {\n // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい\n bucket.sort_by(|a, b| a.partial_cmp(b).unwrap());\n }\n // 3. バケットを走査して結果を結合する\n let mut i = 0;\n for bucket in buckets.iter() {\n for &amp;num in bucket.iter() {\n nums[i] = num;\n i += 1;\n }\n }\n}\n</code></pre> bucket_sort.c<pre><code>/* バケットソート */\nvoid bucketSort(float nums[], int n) {\n int k = n / 2; // k = n/2 個のバケットを初期化する\n int *sizes = malloc(k * sizeof(int)); // 各バケットのサイズを記録する\n float **buckets = malloc(k * sizeof(float *)); // 動的配列の配列(バケット)\n // 各バケットに十分な領域をあらかじめ確保する\n for (int i = 0; i &lt; k; ++i) {\n buckets[i] = (float *)malloc(n * sizeof(float));\n sizes[i] = 0;\n }\n // 1. 配列の要素を各バケットに振り分ける\n for (int i = 0; i &lt; n; ++i) {\n int idx = (int)(nums[i] * k);\n buckets[idx][sizes[idx]++] = nums[i];\n }\n // 2. 各バケットをソートする\n for (int i = 0; i &lt; k; ++i) {\n qsort(buckets[i], sizes[i], sizeof(float), compare);\n }\n // 3. ソートしたバケットを結合する\n int idx = 0;\n for (int i = 0; i &lt; k; ++i) {\n for (int j = 0; j &lt; sizes[i]; ++j) {\n nums[idx++] = buckets[i][j];\n }\n // メモリを解放する\n free(buckets[i]);\n }\n}\n</code></pre> bucket_sort.kt<pre><code>/* バケットソート */\nfun bucketSort(nums: FloatArray) {\n // k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n val k = nums.size / 2\n val buckets = mutableListOf&lt;MutableList&lt;Float&gt;&gt;()\n for (i in 0..&lt;k) {\n buckets.add(mutableListOf())\n }\n // 1. 配列の要素を各バケットに振り分ける\n for (num in nums) {\n // 入力データの範囲は [0, 1) なので、num * k によりインデックス範囲 [0, k-1] に対応付ける\n val i = (num * k).toInt()\n // num をバケット i に追加する\n buckets[i].add(num)\n }\n // 2. 各バケットをソートする\n for (bucket in buckets) {\n // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい\n bucket.sort()\n }\n // 3. バケットを走査して結果を結合する\n var i = 0\n for (bucket in buckets) {\n for (num in bucket) {\n nums[i++] = num\n }\n }\n}\n</code></pre> bucket_sort.rb<pre><code>### バケットソート ###\ndef bucket_sort(nums)\n # k = n/2 個のバケットを初期化し、各バケットに平均 2 個の要素を割り当てることを想定する\n k = nums.length / 2\n buckets = Array.new(k) { [] }\n\n # 1. 配列の要素を各バケットに振り分ける\n nums.each do |num|\n # 入力データの範囲は [0, 1) なので、num * k によりインデックス範囲 [0, k-1] に対応付ける\n i = (num * k).to_i\n # num をバケット i に追加する\n buckets[i] &lt;&lt; num\n end\n\n # 2. 各バケットをソートする\n buckets.each do |bucket|\n # 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい\n bucket.sort!\n end\n\n # 3. バケットを走査して結果を結合する\n i = 0\n buckets.each do |bucket|\n bucket.each do |num|\n nums[i] = num\n i += 1\n end\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.8   バケットソート"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1182","level":2,"title":"11.8.2   アルゴリズムの特徴","text":"<p>バケットソートは大量のデータを扱うのに適しています。例えば、100 万個の入力要素があり、容量の制約で一度にすべてをメモリに読み込めないとします。この場合、1000 個のバケットに分けて個別にソートし、最後に結果を結合できます。</p> <ul> <li>時間計算量は \\(O(n + k)\\):要素が各バケットに均等に分かれるとすると、一つのバケットの要素数は \\(\\frac{n}{k}\\) です。一つをソートする時間を \\(O(\\frac{n}{k} \\log\\frac{n}{k})\\) とすると、すべてのソートには \\(O(n \\log\\frac{n}{k})\\) 時間かかります。バケット数 \\(k\\) が十分大きいと、時間計算量は \\(O(n)\\) に近づきます。結果の結合ではすべてのバケットと要素を走査するため、\\(O(n + k)\\) 時間かかります。最悪の場合、すべてのデータが一つのバケットに入り、そのソートに \\(O(n^2)\\) 時間かかります。</li> <li>空間計算量は \\(O(n + k)\\)、非インプレースソート:\\(k\\) 個のバケットと、合計 \\(n\\) 個の要素を格納する追加領域が必要です。</li> <li>バケットソートの安定性は、各バケット内で使うソートアルゴリズムの安定性に依存します。</li> </ul>","path":["第 11 章   ソート","11.8   バケットソート"],"tags":[]},{"location":"chapter_sorting/bucket_sort/#1183","level":2,"title":"11.8.3   均等に振り分けるには","text":"<p>バケットソートの時間計算量は理論上 \\(O(n)\\) にできますが、要素を各バケットへ均等に振り分けることが鍵です。実際のデータは均等に分布しているとは限らないためです。例えば、淘宝の全商品を価格帯で 10 個のバケットに均等に分けたいとしても、100 元未満の商品は多く、1000 元超は少ないなど、価格分布には偏りがあります。価格帯を等間隔に 10 分割すると、バケットごとの商品数は大きく異なります。</p> <p>均等に振り分けるには、まずおおよその境界を決め、データを大まかに 3 個のバケットへ分けます。その後、商品が多いバケットをさらに 3 分割し、すべてのバケットの要素数がおおむね等しくなるまで続けます。</p> <p>図 11-14 のように、この方法は本質的に再帰木を作っており、葉ノードの値をできるだけ均等にすることを目指しています。毎回 3 個に分ける必要はなく、データの特徴に応じて柔軟に選べます。</p> <p></p> <p> 図 11-14   バケットの再帰的な分割 </p> <p>商品価格の確率分布があらかじめ分かっていれば、その分布に合わせてバケットごとの価格の境界を設定できます。分布は必ずしも実測する必要はなく、データの特徴に応じた確率モデルで近似することもできます。</p> <p>図 11-15 では、商品価格が正規分布に従うと仮定しています。これにより価格帯を適切に設定し、商品を各バケットへ均等に振り分けられます。</p> <p></p> <p> 図 11-15   確率分布に基づくバケットの分割 </p>","path":["第 11 章   ソート","11.8   バケットソート"],"tags":[]},{"location":"chapter_sorting/counting_sort/","level":1,"title":"11.9   カウントソート","text":"<p>カウントソート(counting sort)は要素の個数を数えてソートする方法で、通常は整数配列に使います。</p>","path":["第 11 章   ソート","11.9   カウントソート"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1191","level":2,"title":"11.9.1   単純な実装","text":"<p>まず簡単な例を見ましょう。長さ \\(n\\) の配列 <code>nums</code> があり、要素はすべて非負整数です。カウントソート全体の流れを図 11-16 に示します。</p> <ol> <li>配列を走査して最大の数値 \\(m\\) を求め、長さ \\(m + 1\\) の補助配列 <code>counter</code> を作ります。</li> <li><code>counter</code> を使って、<code>nums</code> 内の各数値の出現回数を数えます。<code>counter[num]</code> は数値 <code>num</code> の出現回数です。数え方は簡単で、<code>nums</code> を走査し、現在の数値を <code>num</code> として、毎回 <code>counter[num]</code> を \\(1\\) 増やすだけです。</li> <li><code>counter</code> のインデックスはもともと順に並んでいるので、すべての数値がすでにソートされたのと同じです。あとは <code>counter</code> を走査し、各数値の出現回数に従って、小さい順に <code>nums</code> へ書き込めば完了です。</li> </ol> <p></p> <p> 図 11-16   カウントソートの流れ </p> <p>コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby counting_sort.py<pre><code>def counting_sort_naive(nums: list[int]):\n \"\"\"カウントソート\"\"\"\n # 簡易実装。オブジェクトのソートには使えない\n # 1. 配列の最大要素 m を求める\n m = max(nums)\n # 2. 各数値の出現回数を数える\n # counter[num] は num の出現回数を表す\n counter = [0] * (m + 1)\n for num in nums:\n counter[num] += 1\n # 3. counter を走査し、各要素を元の配列 nums に入れる\n i = 0\n for num in range(m + 1):\n for _ in range(counter[num]):\n nums[i] = num\n i += 1\n</code></pre> counting_sort.cpp<pre><code>/* カウントソート */\n// 簡易実装。オブジェクトのソートには使えない\nvoid countingSortNaive(vector&lt;int&gt; &amp;nums) {\n // 1. 配列の最大要素 m を求める\n int m = 0;\n for (int num : nums) {\n m = max(m, num);\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n vector&lt;int&gt; counter(m + 1, 0);\n for (int num : nums) {\n counter[num]++;\n }\n // 3. counter を走査し、各要素を元の配列 nums に入れる\n int i = 0;\n for (int num = 0; num &lt; m + 1; num++) {\n for (int j = 0; j &lt; counter[num]; j++, i++) {\n nums[i] = num;\n }\n }\n}\n</code></pre> counting_sort.java<pre><code>/* カウントソート */\n// 簡易実装。オブジェクトのソートには使えない\nvoid countingSortNaive(int[] nums) {\n // 1. 配列の最大要素 m を求める\n int m = 0;\n for (int num : nums) {\n m = Math.max(m, num);\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n int[] counter = new int[m + 1];\n for (int num : nums) {\n counter[num]++;\n }\n // 3. counter を走査し、各要素を元の配列 nums に入れる\n int i = 0;\n for (int num = 0; num &lt; m + 1; num++) {\n for (int j = 0; j &lt; counter[num]; j++, i++) {\n nums[i] = num;\n }\n }\n}\n</code></pre> counting_sort.cs<pre><code>/* カウントソート */\n// 簡易実装。オブジェクトのソートには使えない\nvoid CountingSortNaive(int[] nums) {\n // 1. 配列の最大要素 m を求める\n int m = 0;\n foreach (int num in nums) {\n m = Math.Max(m, num);\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n int[] counter = new int[m + 1];\n foreach (int num in nums) {\n counter[num]++;\n }\n // 3. counter を走査し、各要素を元の配列 nums に入れる\n int i = 0;\n for (int num = 0; num &lt; m + 1; num++) {\n for (int j = 0; j &lt; counter[num]; j++, i++) {\n nums[i] = num;\n }\n }\n}\n</code></pre> counting_sort.go<pre><code>/* カウントソート */\n// 簡易実装。オブジェクトのソートには使えない\nfunc countingSortNaive(nums []int) {\n // 1. 配列の最大要素 m を求める\n m := 0\n for _, num := range nums {\n if num &gt; m {\n m = num\n }\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n counter := make([]int, m+1)\n for _, num := range nums {\n counter[num]++\n }\n // 3. counter を走査し、各要素を元の配列 nums に入れる\n for i, num := 0, 0; num &lt; m+1; num++ {\n for j := 0; j &lt; counter[num]; j++ {\n nums[i] = num\n i++\n }\n }\n}\n</code></pre> counting_sort.swift<pre><code>/* カウントソート */\n// 簡易実装。オブジェクトのソートには使えない\nfunc countingSortNaive(nums: inout [Int]) {\n // 1. 配列の最大要素 m を求める\n let m = nums.max()!\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n var counter = Array(repeating: 0, count: m + 1)\n for num in nums {\n counter[num] += 1\n }\n // 3. counter を走査し、各要素を元の配列 nums に入れる\n var i = 0\n for num in 0 ..&lt; m + 1 {\n for _ in 0 ..&lt; counter[num] {\n nums[i] = num\n i += 1\n }\n }\n}\n</code></pre> counting_sort.js<pre><code>/* カウントソート */\n// 簡易実装。オブジェクトのソートには使えない\nfunction countingSortNaive(nums) {\n // 1. 配列の最大要素 m を求める\n let m = Math.max(...nums);\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n const counter = new Array(m + 1).fill(0);\n for (const num of nums) {\n counter[num]++;\n }\n // 3. counter を走査し、各要素を元の配列 nums に入れる\n let i = 0;\n for (let num = 0; num &lt; m + 1; num++) {\n for (let j = 0; j &lt; counter[num]; j++, i++) {\n nums[i] = num;\n }\n }\n}\n</code></pre> counting_sort.ts<pre><code>/* カウントソート */\n// 簡易実装。オブジェクトのソートには使えない\nfunction countingSortNaive(nums: number[]): void {\n // 1. 配列の最大要素 m を求める\n let m: number = Math.max(...nums);\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n const counter: number[] = new Array&lt;number&gt;(m + 1).fill(0);\n for (const num of nums) {\n counter[num]++;\n }\n // 3. counter を走査し、各要素を元の配列 nums に入れる\n let i = 0;\n for (let num = 0; num &lt; m + 1; num++) {\n for (let j = 0; j &lt; counter[num]; j++, i++) {\n nums[i] = num;\n }\n }\n}\n</code></pre> counting_sort.dart<pre><code>/* カウントソート */\n// 簡易実装。オブジェクトのソートには使えない\nvoid countingSortNaive(List&lt;int&gt; nums) {\n // 1. 配列の最大要素 m を求める\n int m = 0;\n for (int _num in nums) {\n m = max(m, _num);\n }\n // 2. 各数値の出現回数を数える\n // counter[_num] は _num の出現回数を表す\n List&lt;int&gt; counter = List.filled(m + 1, 0);\n for (int _num in nums) {\n counter[_num]++;\n }\n // 3. counter を走査し、各要素を元の配列 nums に入れる\n int i = 0;\n for (int _num = 0; _num &lt; m + 1; _num++) {\n for (int j = 0; j &lt; counter[_num]; j++, i++) {\n nums[i] = _num;\n }\n }\n}\n</code></pre> counting_sort.rs<pre><code>/* カウントソート */\n// 簡易実装。オブジェクトのソートには使えない\nfn counting_sort_naive(nums: &amp;mut [i32]) {\n // 1. 配列の最大要素 m を求める\n let m = *nums.iter().max().unwrap();\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n let mut counter = vec![0; m as usize + 1];\n for &amp;num in nums.iter() {\n counter[num as usize] += 1;\n }\n // 3. counter を走査し、各要素を元の配列 nums に入れる\n let mut i = 0;\n for num in 0..m + 1 {\n for _ in 0..counter[num as usize] {\n nums[i] = num;\n i += 1;\n }\n }\n}\n</code></pre> counting_sort.c<pre><code>/* カウントソート */\n// 簡易実装。オブジェクトのソートには使えない\nvoid countingSortNaive(int nums[], int size) {\n // 1. 配列の最大要素 m を求める\n int m = 0;\n for (int i = 0; i &lt; size; i++) {\n if (nums[i] &gt; m) {\n m = nums[i];\n }\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n int *counter = calloc(m + 1, sizeof(int));\n for (int i = 0; i &lt; size; i++) {\n counter[nums[i]]++;\n }\n // 3. counter を走査し、各要素を元の配列 nums に入れる\n int i = 0;\n for (int num = 0; num &lt; m + 1; num++) {\n for (int j = 0; j &lt; counter[num]; j++, i++) {\n nums[i] = num;\n }\n }\n // 4. メモリを解放する\n free(counter);\n}\n</code></pre> counting_sort.kt<pre><code>/* カウントソート */\n// 簡易実装。オブジェクトのソートには使えない\nfun countingSortNaive(nums: IntArray) {\n // 1. 配列の最大要素 m を求める\n var m = 0\n for (num in nums) {\n m = max(m, num)\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n val counter = IntArray(m + 1)\n for (num in nums) {\n counter[num]++\n }\n // 3. counter を走査し、各要素を元の配列 nums に入れる\n var i = 0\n for (num in 0..&lt;m + 1) {\n var j = 0\n while (j &lt; counter[num]) {\n nums[i] = num\n j++\n i++\n }\n }\n}\n</code></pre> counting_sort.rb<pre><code>### カウントソート ###\ndef counting_sort_naive(nums)\n # 簡易実装。オブジェクトのソートには使えない\n # 1. 配列の最大要素 m を求める\n m = 0\n nums.each { |num| m = [m, num].max }\n # 2. 各数値の出現回数を数える\n # counter[num] は num の出現回数を表す\n counter = Array.new(m + 1, 0)\n nums.each { |num| counter[num] += 1 }\n # 3. counter を走査し、各要素を元の配列 nums に入れる\n i = 0\n for num in 0...(m + 1)\n (0...counter[num]).each do\n nums[i] = num\n i += 1\n end\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>カウントソートとバケットソートの関係</p> <p>バケットソートの視点では、カウント配列 <code>counter</code> の各インデックスを一つのバケット、個数を数える過程を各要素を対応するバケットに入れる過程と見なせます。本質的に、カウントソートは整数データにおけるバケットソートの特殊な場合です。</p>","path":["第 11 章   ソート","11.9   カウントソート"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1192","level":2,"title":"11.9.2   完全な実装","text":"<p>注意深い読者は、入力がオブジェクトの場合、上の手順 <code>3.</code> が使えないことに気付いたかもしれません。例えば、商品のオブジェクトを価格(クラスのメンバー変数)でソートしたくても、上の方法で得られるのは価格を並べた結果だけです。</p> <p>では、元のデータをソートした結果を得るにはどうすればよいでしょうか。まず、<code>counter</code> の「累積和」を計算します。名前のとおり、インデックス <code>i</code> の累積和 <code>prefix[i]</code> は、配列の先頭から <code>i</code> 個の要素の和です。</p> \\[ \\text{prefix}[i] = \\sum_{j=0}^i \\text{counter[j]} \\] <p>累積和には明確な意味があります。<code>prefix[num] - 1</code> は、結果配列 <code>res</code> に要素 <code>num</code> が最後に現れるインデックスを表します。これは各要素を結果配列のどこに置くかを示す重要な情報です。続いて、元の配列 <code>nums</code> の各要素 <code>num</code> を逆順に走査し、各反復で次の二つを行います。</p> <ol> <li>配列 <code>res</code> のインデックス <code>prefix[num] - 1</code> に <code>num</code> を入れます。</li> <li>累積和 <code>prefix[num]</code> を \\(1\\) 減らし、次に <code>num</code> を置くインデックスを得ます。</li> </ol> <p>走査が終わると、配列 <code>res</code> がソート済みの結果になります。最後に <code>res</code> で元の配列 <code>nums</code> を上書きします。図 11-17 にカウントソートの全手順を示します。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 11-17   カウントソートの手順 </p> <p>カウントソートの実装は次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby counting_sort.py<pre><code>def counting_sort(nums: list[int]):\n \"\"\"カウントソート\"\"\"\n # 完全な実装。オブジェクトをソートでき、安定ソートである\n # 1. 配列の最大要素 m を求める\n m = max(nums)\n # 2. 各数値の出現回数を数える\n # counter[num] は num の出現回数を表す\n counter = [0] * (m + 1)\n for num in nums:\n counter[num] += 1\n # 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n # つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n for i in range(m):\n counter[i + 1] += counter[i]\n # 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n # 結果を記録する配列 res を初期化する\n n = len(nums)\n res = [0] * n\n for i in range(n - 1, -1, -1):\n num = nums[i]\n res[counter[num] - 1] = num # 対応するインデックスに num を配置する\n counter[num] -= 1 # 累積和を 1 減らし、次に num を配置するインデックスを求める\n # 結果配列 res で元の配列 nums を上書きする\n for i in range(n):\n nums[i] = res[i]\n</code></pre> counting_sort.cpp<pre><code>/* カウントソート */\n// 完全な実装。オブジェクトをソートでき、安定ソートである\nvoid countingSort(vector&lt;int&gt; &amp;nums) {\n // 1. 配列の最大要素 m を求める\n int m = 0;\n for (int num : nums) {\n m = max(m, num);\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n vector&lt;int&gt; counter(m + 1, 0);\n for (int num : nums) {\n counter[num]++;\n }\n // 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n // つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n for (int i = 0; i &lt; m; i++) {\n counter[i + 1] += counter[i];\n }\n // 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n // 結果を記録する配列 res を初期化する\n int n = nums.size();\n vector&lt;int&gt; res(n);\n for (int i = n - 1; i &gt;= 0; i--) {\n int num = nums[i];\n res[counter[num] - 1] = num; // 対応するインデックスに num を配置する\n counter[num]--; // 累積和を 1 減らし、次に num を配置するインデックスを求める\n }\n // 結果配列 res で元の配列 nums を上書きする\n nums = res;\n}\n</code></pre> counting_sort.java<pre><code>/* カウントソート */\n// 完全な実装。オブジェクトをソートでき、安定ソートである\nvoid countingSort(int[] nums) {\n // 1. 配列の最大要素 m を求める\n int m = 0;\n for (int num : nums) {\n m = Math.max(m, num);\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n int[] counter = new int[m + 1];\n for (int num : nums) {\n counter[num]++;\n }\n // 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n // つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n for (int i = 0; i &lt; m; i++) {\n counter[i + 1] += counter[i];\n }\n // 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n // 結果を記録する配列 res を初期化する\n int n = nums.length;\n int[] res = new int[n];\n for (int i = n - 1; i &gt;= 0; i--) {\n int num = nums[i];\n res[counter[num] - 1] = num; // 対応するインデックスに num を配置する\n counter[num]--; // 累積和を 1 減らし、次に num を配置するインデックスを求める\n }\n // 結果配列 res で元の配列 nums を上書きする\n for (int i = 0; i &lt; n; i++) {\n nums[i] = res[i];\n }\n}\n</code></pre> counting_sort.cs<pre><code>/* カウントソート */\n// 完全な実装。オブジェクトをソートでき、安定ソートである\nvoid CountingSort(int[] nums) {\n // 1. 配列の最大要素 m を求める\n int m = 0;\n foreach (int num in nums) {\n m = Math.Max(m, num);\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n int[] counter = new int[m + 1];\n foreach (int num in nums) {\n counter[num]++;\n }\n // 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n // つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n for (int i = 0; i &lt; m; i++) {\n counter[i + 1] += counter[i];\n }\n // 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n // 結果を記録する配列 res を初期化する\n int n = nums.Length;\n int[] res = new int[n];\n for (int i = n - 1; i &gt;= 0; i--) {\n int num = nums[i];\n res[counter[num] - 1] = num; // 対応するインデックスに num を配置する\n counter[num]--; // 累積和を 1 減らし、次に num を配置するインデックスを求める\n }\n // 結果配列 res で元の配列 nums を上書きする\n for (int i = 0; i &lt; n; i++) {\n nums[i] = res[i];\n }\n}\n</code></pre> counting_sort.go<pre><code>/* カウントソート */\n// 完全な実装。オブジェクトをソートでき、安定ソートである\nfunc countingSort(nums []int) {\n // 1. 配列の最大要素 m を求める\n m := 0\n for _, num := range nums {\n if num &gt; m {\n m = num\n }\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n counter := make([]int, m+1)\n for _, num := range nums {\n counter[num]++\n }\n // 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n // つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n for i := 0; i &lt; m; i++ {\n counter[i+1] += counter[i]\n }\n // 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n // 結果を記録する配列 res を初期化する\n n := len(nums)\n res := make([]int, n)\n for i := n - 1; i &gt;= 0; i-- {\n num := nums[i]\n // 対応するインデックスに num を配置する\n res[counter[num]-1] = num\n // 累積和を 1 減らし、次に num を配置するインデックスを求める\n counter[num]--\n }\n // 結果配列 res で元の配列 nums を上書きする\n copy(nums, res)\n}\n</code></pre> counting_sort.swift<pre><code>/* カウントソート */\n// 完全な実装。オブジェクトをソートでき、安定ソートである\nfunc countingSort(nums: inout [Int]) {\n // 1. 配列の最大要素 m を求める\n let m = nums.max()!\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n var counter = Array(repeating: 0, count: m + 1)\n for num in nums {\n counter[num] += 1\n }\n // 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n // つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n for i in 0 ..&lt; m {\n counter[i + 1] += counter[i]\n }\n // 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n // 結果を記録する配列 res を初期化する\n var res = Array(repeating: 0, count: nums.count)\n for i in nums.indices.reversed() {\n let num = nums[i]\n res[counter[num] - 1] = num // 対応するインデックスに num を配置する\n counter[num] -= 1 // 累積和を 1 減らし、次に num を配置するインデックスを求める\n }\n // 結果配列 res で元の配列 nums を上書きする\n for i in nums.indices {\n nums[i] = res[i]\n }\n}\n</code></pre> counting_sort.js<pre><code>/* カウントソート */\n// 完全な実装。オブジェクトをソートでき、安定ソートである\nfunction countingSort(nums) {\n // 1. 配列の最大要素 m を求める\n let m = Math.max(...nums);\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n const counter = new Array(m + 1).fill(0);\n for (const num of nums) {\n counter[num]++;\n }\n // 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n // つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n for (let i = 0; i &lt; m; i++) {\n counter[i + 1] += counter[i];\n }\n // 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n // 結果を記録する配列 res を初期化する\n const n = nums.length;\n const res = new Array(n);\n for (let i = n - 1; i &gt;= 0; i--) {\n const num = nums[i];\n res[counter[num] - 1] = num; // 対応するインデックスに num を配置する\n counter[num]--; // 累積和を 1 減らし、次に num を配置するインデックスを求める\n }\n // 結果配列 res で元の配列 nums を上書きする\n for (let i = 0; i &lt; n; i++) {\n nums[i] = res[i];\n }\n}\n</code></pre> counting_sort.ts<pre><code>/* カウントソート */\n// 完全な実装。オブジェクトをソートでき、安定ソートである\nfunction countingSort(nums: number[]): void {\n // 1. 配列の最大要素 m を求める\n let m: number = Math.max(...nums);\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n const counter: number[] = new Array&lt;number&gt;(m + 1).fill(0);\n for (const num of nums) {\n counter[num]++;\n }\n // 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n // つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n for (let i = 0; i &lt; m; i++) {\n counter[i + 1] += counter[i];\n }\n // 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n // 結果を記録する配列 res を初期化する\n const n = nums.length;\n const res: number[] = new Array&lt;number&gt;(n);\n for (let i = n - 1; i &gt;= 0; i--) {\n const num = nums[i];\n res[counter[num] - 1] = num; // 対応するインデックスに num を配置する\n counter[num]--; // 累積和を 1 減らし、次に num を配置するインデックスを求める\n }\n // 結果配列 res で元の配列 nums を上書きする\n for (let i = 0; i &lt; n; i++) {\n nums[i] = res[i];\n }\n}\n</code></pre> counting_sort.dart<pre><code>/* カウントソート */\n// 完全な実装。オブジェクトをソートでき、安定ソートである\nvoid countingSort(List&lt;int&gt; nums) {\n // 1. 配列の最大要素 m を求める\n int m = 0;\n for (int _num in nums) {\n m = max(m, _num);\n }\n // 2. 各数値の出現回数を数える\n // counter[_num] は _num の出現回数を表す\n List&lt;int&gt; counter = List.filled(m + 1, 0);\n for (int _num in nums) {\n counter[_num]++;\n }\n // 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n // つまり、counter[_num]-1 は res 内で _num が最後に現れるインデックスである\n for (int i = 0; i &lt; m; i++) {\n counter[i + 1] += counter[i];\n }\n // 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n // 結果を記録する配列 res を初期化する\n int n = nums.length;\n List&lt;int&gt; res = List.filled(n, 0);\n for (int i = n - 1; i &gt;= 0; i--) {\n int _num = nums[i];\n res[counter[_num] - 1] = _num; // 対応するインデックスに _num を配置する\n counter[_num]--; // 累積和を 1 減らし、次に _num を配置するインデックスを求める\n }\n // 結果配列 res で元の配列 nums を上書きする\n nums.setAll(0, res);\n}\n</code></pre> counting_sort.rs<pre><code>/* カウントソート */\n// 完全な実装。オブジェクトをソートでき、安定ソートである\nfn counting_sort(nums: &amp;mut [i32]) {\n // 1. 配列の最大要素 m を求める\n let m = *nums.iter().max().unwrap() as usize;\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n let mut counter = vec![0; m + 1];\n for &amp;num in nums.iter() {\n counter[num as usize] += 1;\n }\n // 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n // つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n for i in 0..m {\n counter[i + 1] += counter[i];\n }\n // 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n // 結果を記録する配列 res を初期化する\n let n = nums.len();\n let mut res = vec![0; n];\n for i in (0..n).rev() {\n let num = nums[i];\n res[counter[num as usize] - 1] = num; // 対応するインデックスに num を配置する\n counter[num as usize] -= 1; // 累積和を 1 減らし、次に num を配置するインデックスを求める\n }\n // 結果配列 res で元の配列 nums を上書きする\n nums.copy_from_slice(&amp;res)\n}\n</code></pre> counting_sort.c<pre><code>/* カウントソート */\n// 完全な実装。オブジェクトをソートでき、安定ソートである\nvoid countingSort(int nums[], int size) {\n // 1. 配列の最大要素 m を求める\n int m = 0;\n for (int i = 0; i &lt; size; i++) {\n if (nums[i] &gt; m) {\n m = nums[i];\n }\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n int *counter = calloc(m, sizeof(int));\n for (int i = 0; i &lt; size; i++) {\n counter[nums[i]]++;\n }\n // 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n // つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n for (int i = 0; i &lt; m; i++) {\n counter[i + 1] += counter[i];\n }\n // 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n // 結果を記録する配列 res を初期化する\n int *res = malloc(sizeof(int) * size);\n for (int i = size - 1; i &gt;= 0; i--) {\n int num = nums[i];\n res[counter[num] - 1] = num; // 対応するインデックスに num を配置する\n counter[num]--; // 累積和を 1 減らし、次に num を配置するインデックスを求める\n }\n // 結果配列 res で元の配列 nums を上書きする\n memcpy(nums, res, size * sizeof(int));\n // 5. メモリを解放する\n free(res);\n free(counter);\n}\n</code></pre> counting_sort.kt<pre><code>/* カウントソート */\n// 完全な実装。オブジェクトをソートでき、安定ソートである\nfun countingSort(nums: IntArray) {\n // 1. 配列の最大要素 m を求める\n var m = 0\n for (num in nums) {\n m = max(m, num)\n }\n // 2. 各数値の出現回数を数える\n // counter[num] は num の出現回数を表す\n val counter = IntArray(m + 1)\n for (num in nums) {\n counter[num]++\n }\n // 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n // つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n for (i in 0..&lt;m) {\n counter[i + 1] += counter[i]\n }\n // 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n // 結果を記録する配列 res を初期化する\n val n = nums.size\n val res = IntArray(n)\n for (i in n - 1 downTo 0) {\n val num = nums[i]\n res[counter[num] - 1] = num // 対応するインデックスに num を配置する\n counter[num]-- // 累積和を 1 減らし、次に num を配置するインデックスを求める\n }\n // 結果配列 res で元の配列 nums を上書きする\n for (i in 0..&lt;n) {\n nums[i] = res[i]\n }\n}\n</code></pre> counting_sort.rb<pre><code>### カウントソート ###\ndef counting_sort(nums)\n # 完全な実装。オブジェクトをソートでき、安定ソートである\n # 1. 配列の最大要素 m を求める\n m = nums.max\n # 2. 各数値の出現回数を数える\n # counter[num] は num の出現回数を表す\n counter = Array.new(m + 1, 0)\n nums.each { |num| counter[num] += 1 }\n # 3. counter の累積和を求め、「出現回数」を「末尾のインデックス」に変換する\n # つまり、counter[num]-1 は res 内で num が最後に現れるインデックスである\n (0...m).each { |i| counter[i + 1] += counter[i] }\n # 4. nums を逆順に走査し、各要素を結果配列 res に入れる\n # 結果を記録する配列 res を初期化する\n n = nums.length\n res = Array.new(n, 0)\n (n - 1).downto(0).each do |i|\n num = nums[i]\n res[counter[num] - 1] = num # 対応するインデックスに num を配置する\n counter[num] -= 1 # 累積和を 1 減らし、次に num を配置するインデックスを求める\n end\n # 結果配列 res で元の配列 nums を上書きする\n (0...n).each { |i| nums[i] = res[i] }\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.9   カウントソート"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1193","level":2,"title":"11.9.3   アルゴリズムの特徴","text":"<ul> <li>時間計算量は \\(O(n + m)\\)、非適応的ソート:<code>nums</code> と <code>counter</code> の走査はいずれも線形時間です。通常は \\(n \\gg m\\) なので、時間計算量は \\(O(n)\\) に近づきます。</li> <li>空間計算量は \\(O(n + m)\\)、非インプレースソート:それぞれ長さ \\(n\\) と \\(m\\) の配列 <code>res</code> と <code>counter</code> を使います。</li> <li>安定ソート:<code>res</code> には右から左へ要素を入れるため、<code>nums</code> を逆順に走査すれば、等しい要素の相対的な位置を変えずに済みます。実際には <code>nums</code> を順方向に走査しても大小の順序は正しくなりますが、安定性は失われます。</li> </ul>","path":["第 11 章   ソート","11.9   カウントソート"],"tags":[]},{"location":"chapter_sorting/counting_sort/#1194","level":2,"title":"11.9.4   制約","text":"<p>ここまで読むと、個数を数えるだけで効率よくソートできるカウントソートを巧妙だと感じるかもしれません。ただし、利用の前提条件は比較的厳しいものです。</p> <p>カウントソートは非負整数にしか適用できません。ほかの型に使うには、要素間の大小関係を変えずに非負整数へ変換できる必要があります。例えば、負数を含む整数配列では、すべての数に同じ定数を加えて正数にし、ソート後に元へ戻せます。</p> <p>カウントソートは、データ量は多くても値の範囲が狭い場合に適しています。例えば、上の例で \\(m\\) が大きすぎると、領域を使いすぎます。また、\\(n \\ll m\\) の場合は \\(O(m)\\) 時間がかかり、\\(O(n \\log n)\\) のソートより遅くなる可能性があります。</p>","path":["第 11 章   ソート","11.9   カウントソート"],"tags":[]},{"location":"chapter_sorting/exercises/","level":1,"title":"11.12   練習問題","text":"","path":["第 11 章   ソート","11.12   練習問題"],"tags":[]},{"location":"chapter_sorting/exercises/#11121","level":2,"title":"11.12.1   知識の確認","text":"","path":["第 11 章   ソート","11.12   練習問題"],"tags":[]},{"location":"chapter_sorting/exercises/#1","level":3,"title":"1.   選択ソートとバブルソートの最初の反復","text":"<p>配列 <code>[4, 2, 5, 1, 3]</code> が与えられます。以下では、いずれも昇順にソートします。</p> <ol> <li>選択ソートの最初の二回の反復をたどり、各反復後の配列と、その時点で確定した位置を書いてください。</li> <li>バブルソートの最初の一回の反復をたどり、配列の状態、交換回数、確定した位置を書いてください。</li> </ol> 解答例 <ol> <li> <p>最初の二回の反復は、次のとおりです。</p> 反復 配列の状態 説明 1 <code>[1, 2, 5, 4, 3]</code> 最小要素 1 と先頭を交換します 2 <code>[1, 2, 5, 4, 3]</code> 2 はすでにインデックス 1 にあるため、交換は不要です <p>これで最初の二つの位置が確定し、以降は <code>[5, 4, 3]</code> から最小要素を選び続けます。</p> </li> <li> <p>隣り合う要素を順に比較します。4 と 2 は交換し、4 と 5 は交換せず、5 と 1、5 と 3 を交換すると、 <code>[2, 4, 1, 3, 5]</code> になります。交換は計 3 回です。最大要素 5 が末尾に移動したので、最後の位置が確定します。</p> </li> </ol>","path":["第 11 章   ソート","11.12   練習問題"],"tags":[]},{"location":"chapter_sorting/exercises/#2","level":3,"title":"2.   等しい要素の順序は変わるか","text":"<p>配列 \\([2_a, 2_b, 1]\\) の \\(2_a\\) と \\(2_b\\) は値が等しく、添字で元の順序を示しています。</p> <ol> <li>選択ソートの一回目の反復後の配列を書いてください。\\(2_a\\) と \\(2_b\\) の相対的な順序は変わりますか。</li> <li>バブルソートの一回目の反復後の配列を書いてください。\\(2_a\\) と \\(2_b\\) の相対的な順序は変わりますか。</li> <li>前の二問から、等しい要素の元の順序を保つという点で、二つのソートがどう違うかを説明してください。</li> </ol> 解答例 <ol> <li> <p>選択ソートの一回目では、最小要素 1 を選び、先頭の \\(2_a\\) と交換するので、 \\([1, 2_b, 2_a]\\) となります。\\(2_a\\) が \\(2_b\\) の後ろに移り、相対的な順序が変わります。</p> </li> <li> <p>バブルソートでは、まず \\(2_a\\) と \\(2_b\\) を比較します。等しいので交換せず、次に \\(2_b\\) と 1 を比較して交換するため、 一回目の反復後は \\([2_a, 1, 2_b]\\) です。\\(2_a\\) は引き続き \\(2_b\\) より前にあり、相対的な順序は変わりません。</p> </li> <li> <p>この例では、選択ソートは等しい要素の元の順序を変えます。一方、バブルソートは、左の要素が右の要素より大きい場合にだけ 隣り合う要素を交換します。等しい要素同士は交換しないため、元の順序を保てます。</p> </li> </ol>","path":["第 11 章   ソート","11.12   練習問題"],"tags":[]},{"location":"chapter_sorting/exercises/#3","level":3,"title":"3.   カウントソートと基数ソートの比較","text":"<p>学校で、桁数が 8 桁に固定された多数の学籍番号をソートします。次の問いに答えてください。</p> <ol> <li>最下位桁から始める基数ソートでは、何回の反復が必要ですか。</li> <li>学籍番号をそのまま整数としてカウントソートすると、使われないカウント位置を大量に用意することになるのはなぜですか。</li> <li>前の二問から、多数の 8 桁の学籍番号を処理するためにカウントソートと基数ソートのどちらかを選ぶなら、どちらにしますか。理由も説明してください。</li> </ol> 解答例 <ol> <li> <p>学籍番号は 8 桁なので、最下位桁から最上位桁まで計 8 回処理します。各回は 0~9 による分類だけを行います。</p> </li> <li> <p>直接カウントするには、8 桁で取り得るすべての値に位置を用意する必要があります。しかし、実際の生徒が使うのはそのごく一部で、 ほとんどのカウント位置は 0 のままです。</p> </li> <li> <p>基数ソートを選びます。「桁数が固定され、各桁に 10 種類の値しかない」という特徴を利用し、安定な分類を 8 回繰り返すだけで済みます。 カウントソートで 8 桁の番号全体を整数のインデックスとして使うと、実際には現れない多数の値にカウント位置を用意しなければなりません。</p> </li> </ol>","path":["第 11 章   ソート","11.12   練習問題"],"tags":[]},{"location":"chapter_sorting/exercises/#11122","level":2,"title":"11.12.2   プログラミング演習","text":"","path":["第 11 章   ソート","11.12   練習問題"],"tags":[]},{"location":"chapter_sorting/exercises/#1_1","level":3,"title":"1.   マージソートで配列を並べる","text":"<p>整数配列 <code>nums</code> が与えられます。マージソートを自分で実装し、要素を非減少順に並べて返してください。言語に組み込まれたソート関数は呼び出さないでください。</p> 解くためのヒント <ol> <li>区間の長さが 1 以下なら、すでに整列済みです</li> <li>中点で区間を二つに分け、それぞれを再帰的にソートします</li> <li>二つのポインタで整列済みの半区間をマージし、結果を元の配列に書き戻します</li> </ol> <p>LeetCode</p>","path":["第 11 章   ソート","11.12   練習問題"],"tags":[]},{"location":"chapter_sorting/exercises/#2_1","level":3,"title":"2.   カウントソートで整数配列を並べる","text":"<p>整数配列 <code>nums</code> と非負整数 \\(K\\) が与えられます。配列の各要素は \\(0\\) 以上 \\(K\\) 以下です。</p> <p>カウントソートを実装し、結果を非減少順に <code>nums</code> へ書き戻して、<code>nums</code> を返してください。 要素間の大小比較で順序を決めたり、言語に組み込まれたソート関数を呼び出したりしてはいけません。</p> 解くためのヒント <ol> <li>各要素は 0 以上 K 以下なので、値をそのままカウント配列のインデックスに使えます</li> <li>一回目の走査では nums を調べ、対応する位置の個数を 1 増やします</li> <li>次にカウント配列を 0 から K まで走査します。値 x が出現した回数だけ、nums に x を連続して書き込みます</li> </ol>","path":["第 11 章   ソート","11.12   練習問題"],"tags":[]},{"location":"chapter_sorting/heap_sort/","level":1,"title":"11.7   ヒープソート","text":"<p>ヒント</p> <p>この節を読む前に、「ヒープ」の章を学習しておいてください。</p> <p>ヒープソート(heap sort)は、ヒープというデータ構造に基づく効率的なソートアルゴリズムです。すでに学んだヒープの構築と要素の取り出しを使って実装できます。</p> <ol> <li>配列を入力して最小ヒープを構築します。この時点で最小要素がヒープの先頭にあります。</li> <li>取り出しを繰り返し、取り出した要素を順に記録すると、昇順に並んだ列が得られます。</li> </ol> <p>この方法でも実現できますが、取り出した要素を保存する追加の配列が必要で、領域を多く使います。実際には、通常、よりすっきりした実装方法を使います。</p>","path":["第 11 章   ソート","11.7   ヒープソート"],"tags":[]},{"location":"chapter_sorting/heap_sort/#1171","level":2,"title":"11.7.1   アルゴリズムの流れ","text":"<p>配列の長さを \\(n\\) とすると、ヒープソートの流れは図 11-12 のとおりです。</p> <ol> <li>配列を入力して最大ヒープを構築します。すると、最大要素がヒープの先頭にあります。</li> <li>ヒープの先頭要素(最初の要素)と末尾要素(最後の要素)を交換します。その後、ヒープの長さを \\(1\\) 減らし、整列済み要素数を \\(1\\) 増やします。</li> <li>先頭要素から下向きにヒープ化(sift down)します。これにより、ヒープの性質が復元されます。</li> <li>手順 <code>2.</code> と <code>3.</code> を繰り返します。\\(n - 1\\) 回反復すると、配列のソートが完了します。</li> </ol> <p>ヒント</p> <p>実は、ヒープから要素を取り出す操作にも手順 <code>2.</code> と <code>3.</code> が含まれています。違いは、要素を取り除く手順が追加されるだけです。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt;&lt;11&gt;&lt;12&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 11-12   ヒープソートの手順 </p> <p>実装では「ヒープ」の章と同じ、下向きにヒープ化する関数 <code>sift_down()</code> を使います。ただし、最大要素を取り出すたびにヒープの長さが縮むので、現在の有効な長さを指定する引数 \\(n\\) を <code>sift_down()</code> に追加する必要があります。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby heap_sort.py<pre><code>def sift_down(nums: list[int], n: int, i: int):\n \"\"\"長さ n のヒープを、ノード i から下向きにヒープ化する\"\"\"\n while True:\n # ノード i, l, r のうち値が最大のノードを求め、ma とする\n l = 2 * i + 1\n r = 2 * i + 2\n ma = i\n if l &lt; n and nums[l] &gt; nums[ma]:\n ma = l\n if r &lt; n and nums[r] &gt; nums[ma]:\n ma = r\n # ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if ma == i:\n break\n # 2 つのノードを交換する\n nums[i], nums[ma] = nums[ma], nums[i]\n # 下向きのヒープ化を繰り返す\n i = ma\n\ndef heap_sort(nums: list[int]):\n \"\"\"ヒープソート\"\"\"\n # ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for i in range(len(nums) // 2 - 1, -1, -1):\n sift_down(nums, len(nums), i)\n # ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for i in range(len(nums) - 1, 0, -1):\n # 根と最右の葉を交換(先頭要素と末尾要素を交換)\n nums[0], nums[i] = nums[i], nums[0]\n # 根ノードから下向きにヒープ化する\n sift_down(nums, i, 0)\n</code></pre> heap_sort.cpp<pre><code>/* 長さ n のヒープを、ノード i から下向きにヒープ化する */\nvoid siftDown(vector&lt;int&gt; &amp;nums, int n, int i) {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n int l = 2 * i + 1;\n int r = 2 * i + 2;\n int ma = i;\n if (l &lt; n &amp;&amp; nums[l] &gt; nums[ma])\n ma = l;\n if (r &lt; n &amp;&amp; nums[r] &gt; nums[ma])\n ma = r;\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma == i) {\n break;\n }\n // 2 つのノードを交換する\n swap(nums[i], nums[ma]);\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n\n/* ヒープソート */\nvoid heapSort(vector&lt;int&gt; &amp;nums) {\n // ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for (int i = nums.size() / 2 - 1; i &gt;= 0; --i) {\n siftDown(nums, nums.size(), i);\n }\n // ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for (int i = nums.size() - 1; i &gt; 0; --i) {\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n swap(nums[0], nums[i]);\n // 根ノードから下向きにヒープ化する\n siftDown(nums, i, 0);\n }\n}\n</code></pre> heap_sort.java<pre><code>/* 長さ n のヒープを、ノード i から下向きにヒープ化する */\nvoid siftDown(int[] nums, int n, int i) {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n int l = 2 * i + 1;\n int r = 2 * i + 2;\n int ma = i;\n if (l &lt; n &amp;&amp; nums[l] &gt; nums[ma])\n ma = l;\n if (r &lt; n &amp;&amp; nums[r] &gt; nums[ma])\n ma = r;\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma == i)\n break;\n // 2 つのノードを交換する\n int temp = nums[i];\n nums[i] = nums[ma];\n nums[ma] = temp;\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n\n/* ヒープソート */\nvoid heapSort(int[] nums) {\n // ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for (int i = nums.length / 2 - 1; i &gt;= 0; i--) {\n siftDown(nums, nums.length, i);\n }\n // ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for (int i = nums.length - 1; i &gt; 0; i--) {\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n int tmp = nums[0];\n nums[0] = nums[i];\n nums[i] = tmp;\n // 根ノードから下向きにヒープ化する\n siftDown(nums, i, 0);\n }\n}\n</code></pre> heap_sort.cs<pre><code>/* 長さ n のヒープを、ノード i から下向きにヒープ化する */\nvoid SiftDown(int[] nums, int n, int i) {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n int l = 2 * i + 1;\n int r = 2 * i + 2;\n int ma = i;\n if (l &lt; n &amp;&amp; nums[l] &gt; nums[ma])\n ma = l;\n if (r &lt; n &amp;&amp; nums[r] &gt; nums[ma])\n ma = r;\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma == i)\n break;\n // 2 つのノードを交換する\n (nums[ma], nums[i]) = (nums[i], nums[ma]);\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n\n/* ヒープソート */\nvoid HeapSort(int[] nums) {\n // ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for (int i = nums.Length / 2 - 1; i &gt;= 0; i--) {\n SiftDown(nums, nums.Length, i);\n }\n // ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for (int i = nums.Length - 1; i &gt; 0; i--) {\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n (nums[i], nums[0]) = (nums[0], nums[i]);\n // 根ノードから下向きにヒープ化する\n SiftDown(nums, i, 0);\n }\n}\n</code></pre> heap_sort.go<pre><code>/* 長さ n のヒープを、ノード i から下向きにヒープ化する */\nfunc siftDown(nums *[]int, n, i int) {\n for true {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n l := 2*i + 1\n r := 2*i + 2\n ma := i\n if l &lt; n &amp;&amp; (*nums)[l] &gt; (*nums)[ma] {\n ma = l\n }\n if r &lt; n &amp;&amp; (*nums)[r] &gt; (*nums)[ma] {\n ma = r\n }\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if ma == i {\n break\n }\n // 2 つのノードを交換する\n (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i]\n // 下向きのヒープ化を繰り返す\n i = ma\n }\n}\n\n/* ヒープソート */\nfunc heapSort(nums *[]int) {\n // ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for i := len(*nums)/2 - 1; i &gt;= 0; i-- {\n siftDown(nums, len(*nums), i)\n }\n // ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for i := len(*nums) - 1; i &gt; 0; i-- {\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0]\n // 根ノードから下向きにヒープ化する\n siftDown(nums, i, 0)\n }\n}\n</code></pre> heap_sort.swift<pre><code>/* 長さ n のヒープを、ノード i から下向きにヒープ化する */\nfunc siftDown(nums: inout [Int], n: Int, i: Int) {\n var i = i\n while true {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n let l = 2 * i + 1\n let r = 2 * i + 2\n var ma = i\n if l &lt; n, nums[l] &gt; nums[ma] {\n ma = l\n }\n if r &lt; n, nums[r] &gt; nums[ma] {\n ma = r\n }\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if ma == i {\n break\n }\n // 2 つのノードを交換する\n nums.swapAt(i, ma)\n // 下向きのヒープ化を繰り返す\n i = ma\n }\n}\n\n/* ヒープソート */\nfunc heapSort(nums: inout [Int]) {\n // ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) {\n siftDown(nums: &amp;nums, n: nums.count, i: i)\n }\n // ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for i in nums.indices.dropFirst().reversed() {\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n nums.swapAt(0, i)\n // 根ノードから下向きにヒープ化する\n siftDown(nums: &amp;nums, n: i, i: 0)\n }\n}\n</code></pre> heap_sort.js<pre><code>/* 長さ n のヒープを、ノード i から下向きにヒープ化する */\nfunction siftDown(nums, n, i) {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n let l = 2 * i + 1;\n let r = 2 * i + 2;\n let ma = i;\n if (l &lt; n &amp;&amp; nums[l] &gt; nums[ma]) {\n ma = l;\n }\n if (r &lt; n &amp;&amp; nums[r] &gt; nums[ma]) {\n ma = r;\n }\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma === i) {\n break;\n }\n // 2 つのノードを交換する\n [nums[i], nums[ma]] = [nums[ma], nums[i]];\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n\n/* ヒープソート */\nfunction heapSort(nums) {\n // ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for (let i = Math.floor(nums.length / 2) - 1; i &gt;= 0; i--) {\n siftDown(nums, nums.length, i);\n }\n // ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for (let i = nums.length - 1; i &gt; 0; i--) {\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n [nums[0], nums[i]] = [nums[i], nums[0]];\n // 根ノードから下向きにヒープ化する\n siftDown(nums, i, 0);\n }\n}\n</code></pre> heap_sort.ts<pre><code>/* 長さ n のヒープを、ノード i から下向きにヒープ化する */\nfunction siftDown(nums: number[], n: number, i: number): void {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n let l = 2 * i + 1;\n let r = 2 * i + 2;\n let ma = i;\n if (l &lt; n &amp;&amp; nums[l] &gt; nums[ma]) {\n ma = l;\n }\n if (r &lt; n &amp;&amp; nums[r] &gt; nums[ma]) {\n ma = r;\n }\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma === i) {\n break;\n }\n // 2 つのノードを交換する\n [nums[i], nums[ma]] = [nums[ma], nums[i]];\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n\n/* ヒープソート */\nfunction heapSort(nums: number[]): void {\n // ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for (let i = Math.floor(nums.length / 2) - 1; i &gt;= 0; i--) {\n siftDown(nums, nums.length, i);\n }\n // ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for (let i = nums.length - 1; i &gt; 0; i--) {\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n [nums[0], nums[i]] = [nums[i], nums[0]];\n // 根ノードから下向きにヒープ化する\n siftDown(nums, i, 0);\n }\n}\n</code></pre> heap_sort.dart<pre><code>/* 長さ n のヒープを、ノード i から下向きにヒープ化する */\nvoid siftDown(List&lt;int&gt; nums, int n, int i) {\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n int l = 2 * i + 1;\n int r = 2 * i + 2;\n int ma = i;\n if (l &lt; n &amp;&amp; nums[l] &gt; nums[ma]) ma = l;\n if (r &lt; n &amp;&amp; nums[r] &gt; nums[ma]) ma = r;\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma == i) break;\n // 2 つのノードを交換する\n int temp = nums[i];\n nums[i] = nums[ma];\n nums[ma] = temp;\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n\n/* ヒープソート */\nvoid heapSort(List&lt;int&gt; nums) {\n // ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for (int i = nums.length ~/ 2 - 1; i &gt;= 0; i--) {\n siftDown(nums, nums.length, i);\n }\n // ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for (int i = nums.length - 1; i &gt; 0; i--) {\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n int tmp = nums[0];\n nums[0] = nums[i];\n nums[i] = tmp;\n // 根ノードから下向きにヒープ化する\n siftDown(nums, i, 0);\n }\n}\n</code></pre> heap_sort.rs<pre><code>/* 長さ n のヒープを、ノード i から下向きにヒープ化する */\nfn sift_down(nums: &amp;mut [i32], n: usize, mut i: usize) {\n loop {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n let l = 2 * i + 1;\n let r = 2 * i + 2;\n let mut ma = i;\n if l &lt; n &amp;&amp; nums[l] &gt; nums[ma] {\n ma = l;\n }\n if r &lt; n &amp;&amp; nums[r] &gt; nums[ma] {\n ma = r;\n }\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if ma == i {\n break;\n }\n // 2 つのノードを交換する\n nums.swap(i, ma);\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n\n/* ヒープソート */\nfn heap_sort(nums: &amp;mut [i32]) {\n // ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for i in (0..nums.len() / 2).rev() {\n sift_down(nums, nums.len(), i);\n }\n // ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for i in (1..nums.len()).rev() {\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n nums.swap(0, i);\n // 根ノードから下向きにヒープ化する\n sift_down(nums, i, 0);\n }\n}\n</code></pre> heap_sort.c<pre><code>/* 長さ n のヒープを、ノード i から下向きにヒープ化する */\nvoid siftDown(int nums[], int n, int i) {\n while (1) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n int l = 2 * i + 1;\n int r = 2 * i + 2;\n int ma = i;\n if (l &lt; n &amp;&amp; nums[l] &gt; nums[ma])\n ma = l;\n if (r &lt; n &amp;&amp; nums[r] &gt; nums[ma])\n ma = r;\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma == i) {\n break;\n }\n // 2 つのノードを交換する\n int temp = nums[i];\n nums[i] = nums[ma];\n nums[ma] = temp;\n // 下向きのヒープ化を繰り返す\n i = ma;\n }\n}\n\n/* ヒープソート */\nvoid heapSort(int nums[], int n) {\n // ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for (int i = n / 2 - 1; i &gt;= 0; --i) {\n siftDown(nums, n, i);\n }\n // ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for (int i = n - 1; i &gt; 0; --i) {\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n int tmp = nums[0];\n nums[0] = nums[i];\n nums[i] = tmp;\n // 根ノードから下向きにヒープ化する\n siftDown(nums, i, 0);\n }\n}\n</code></pre> heap_sort.kt<pre><code>/* 長さ n のヒープを、ノード i から下向きにヒープ化する */\nfun siftDown(nums: IntArray, n: Int, li: Int) {\n var i = li\n while (true) {\n // ノード i, l, r のうち値が最大のノードを求め、ma とする\n val l = 2 * i + 1\n val r = 2 * i + 2\n var ma = i\n if (l &lt; n &amp;&amp; nums[l] &gt; nums[ma]) \n ma = l\n if (r &lt; n &amp;&amp; nums[r] &gt; nums[ma]) \n ma = r\n // ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n if (ma == i) \n break\n // 2 つのノードを交換する\n val temp = nums[i]\n nums[i] = nums[ma]\n nums[ma] = temp\n // 下向きのヒープ化を繰り返す\n i = ma\n }\n}\n\n/* ヒープソート */\nfun heapSort(nums: IntArray) {\n // ヒープの構築:葉ノード以外の全ノードをヒープ化する\n for (i in nums.size / 2 - 1 downTo 0) {\n siftDown(nums, nums.size, i)\n }\n // ヒープから最大要素を取り出す処理を n-1 回繰り返す\n for (i in nums.size - 1 downTo 1) {\n // 根と最右の葉を交換(先頭要素と末尾要素を交換)\n val temp = nums[0]\n nums[0] = nums[i]\n nums[i] = temp\n // 根ノードから下向きにヒープ化する\n siftDown(nums, i, 0)\n }\n}\n</code></pre> heap_sort.rb<pre><code>### 長さ n のヒープを、ノード i から下向きにヒープ化する ###\ndef sift_down(nums, n, i)\n while true\n # ノード i, l, r のうち値が最大のノードを求め、ma とする\n l = 2 * i + 1\n r = 2 * i + 2\n ma = i\n ma = l if l &lt; n &amp;&amp; nums[l] &gt; nums[ma]\n ma = r if r &lt; n &amp;&amp; nums[r] &gt; nums[ma]\n # ノード i が最大か添字 l, r が範囲外なら、ヒープ化は不要なので終了\n break if ma == i\n # 2 つのノードを交換する\n nums[i], nums[ma] = nums[ma], nums[i]\n # 下向きのヒープ化を繰り返す\n i = ma\n end\nend\n\n### ヒープソート ###\ndef heap_sort(nums)\n # ヒープの構築:葉ノード以外の全ノードをヒープ化する\n (nums.length / 2 - 1).downto(0) do |i|\n sift_down(nums, nums.length, i)\n end\n # ヒープから最大要素を取り出す処理を n-1 回繰り返す\n (nums.length - 1).downto(1) do |i|\n # 根と最右の葉を交換(先頭要素と末尾要素を交換)\n nums[0], nums[i] = nums[i], nums[0]\n # 根ノードから下向きにヒープ化する\n sift_down(nums, i, 0)\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.7   ヒープソート"],"tags":[]},{"location":"chapter_sorting/heap_sort/#1172","level":2,"title":"11.7.2   アルゴリズムの特徴","text":"<ul> <li>時間計算量は \\(O(n \\log n)\\)、非適応的ソート:ヒープの構築に \\(O(n)\\) 時間かかります。最大要素の取り出しは \\(O(\\log n)\\) で、これを \\(n - 1\\) 回繰り返します。</li> <li>空間計算量は \\(O(1)\\)、インプレースソート:いくつかのポインタ変数が \\(O(1)\\) の領域を使います。要素の交換もヒープ化も、元の配列上で行います。</li> <li>不安定ソート:先頭要素と末尾要素を交換する際、等しい要素の相対的な位置が変わる可能性があります。</li> </ul>","path":["第 11 章   ソート","11.7   ヒープソート"],"tags":[]},{"location":"chapter_sorting/insertion_sort/","level":1,"title":"11.4   挿入ソート","text":"<p>挿入ソート(insertion sort)は単純なソートアルゴリズムで、その仕組みは手作業でトランプを並べる過程によく似ています。</p> <p>具体的には、未整列区間から基準となる要素を一つ選び、左側の整列済み区間の要素と順に比較して、正しい位置へ挿入します。</p> <p>図 11-6 は配列に要素を挿入する操作の流れです。基準の要素を <code>base</code> とすると、挿入先のインデックスから <code>base</code> までの要素をすべて一つ右へ移動し、挿入先のインデックスに <code>base</code> を代入します。</p> <p></p> <p> 図 11-6   一回の挿入操作 </p>","path":["第 11 章   ソート","11.4   挿入ソート"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1141","level":2,"title":"11.4.1   アルゴリズムの流れ","text":"<p>挿入ソート全体の流れを図 11-7 に示します。</p> <ol> <li>初期状態では、配列の 1 番目の要素は整列済みとします。</li> <li>2 番目の要素を <code>base</code> として正しい位置に挿入すると、先頭の 2 要素が整列済みになります。</li> <li>3 番目の要素を <code>base</code> として正しい位置に挿入すると、先頭の 3 要素が整列済みになります。</li> <li>同様に繰り返し、最後の要素を <code>base</code> として正しい位置に挿入すると、すべての要素が整列済みになります。</li> </ol> <p></p> <p> 図 11-7   挿入ソートの流れ </p> <p>コード例は次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby insertion_sort.py<pre><code>def insertion_sort(nums: list[int]):\n \"\"\"挿入ソート\"\"\"\n # 外側のループ:ソート済み区間は [0, i-1]\n for i in range(1, len(nums)):\n base = nums[i]\n j = i - 1\n # 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while j &gt;= 0 and nums[j] &gt; base:\n nums[j + 1] = nums[j] # nums[j] を右に 1 つずらす\n j -= 1\n nums[j + 1] = base # 正しい位置に base を代入する\n</code></pre> insertion_sort.cpp<pre><code>/* 挿入ソート */\nvoid insertionSort(vector&lt;int&gt; &amp;nums) {\n // 外側のループ:ソート済み区間は [0, i-1]\n for (int i = 1; i &lt; nums.size(); i++) {\n int base = nums[i], j = i - 1;\n // 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while (j &gt;= 0 &amp;&amp; nums[j] &gt; base) {\n nums[j + 1] = nums[j]; // nums[j] を右に 1 つずらす\n j--;\n }\n nums[j + 1] = base; // 正しい位置に base を代入する\n }\n}\n</code></pre> insertion_sort.java<pre><code>/* 挿入ソート */\nvoid insertionSort(int[] nums) {\n // 外側のループ:ソート済み区間は [0, i-1]\n for (int i = 1; i &lt; nums.length; i++) {\n int base = nums[i], j = i - 1;\n // 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while (j &gt;= 0 &amp;&amp; nums[j] &gt; base) {\n nums[j + 1] = nums[j]; // nums[j] を右に 1 つずらす\n j--;\n }\n nums[j + 1] = base; // 正しい位置に base を代入する\n }\n}\n</code></pre> insertion_sort.cs<pre><code>/* 挿入ソート */\nvoid InsertionSort(int[] nums) {\n // 外側のループ:ソート済み区間は [0, i-1]\n for (int i = 1; i &lt; nums.Length; i++) {\n int bas = nums[i], j = i - 1;\n // 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while (j &gt;= 0 &amp;&amp; nums[j] &gt; bas) {\n nums[j + 1] = nums[j]; // nums[j] を右に 1 つずらす\n j--;\n }\n nums[j + 1] = bas; // 正しい位置に base を代入する\n }\n}\n</code></pre> insertion_sort.go<pre><code>/* 挿入ソート */\nfunc insertionSort(nums []int) {\n // 外側のループ:ソート済み区間は [0, i-1]\n for i := 1; i &lt; len(nums); i++ {\n base := nums[i]\n j := i - 1\n // 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n for j &gt;= 0 &amp;&amp; nums[j] &gt; base {\n nums[j+1] = nums[j] // nums[j] を右に 1 つずらす\n j--\n }\n nums[j+1] = base // 正しい位置に base を代入する\n }\n}\n</code></pre> insertion_sort.swift<pre><code>/* 挿入ソート */\nfunc insertionSort(nums: inout [Int]) {\n // 外側のループ:ソート済み区間は [0, i-1]\n for i in nums.indices.dropFirst() {\n let base = nums[i]\n var j = i - 1\n // 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while j &gt;= 0, nums[j] &gt; base {\n nums[j + 1] = nums[j] // nums[j] を右に 1 つずらす\n j -= 1\n }\n nums[j + 1] = base // 正しい位置に base を代入する\n }\n}\n</code></pre> insertion_sort.js<pre><code>/* 挿入ソート */\nfunction insertionSort(nums) {\n // 外側のループ:ソート済み区間は [0, i-1]\n for (let i = 1; i &lt; nums.length; i++) {\n let base = nums[i],\n j = i - 1;\n // 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while (j &gt;= 0 &amp;&amp; nums[j] &gt; base) {\n nums[j + 1] = nums[j]; // nums[j] を右に 1 つずらす\n j--;\n }\n nums[j + 1] = base; // 正しい位置に base を代入する\n }\n}\n</code></pre> insertion_sort.ts<pre><code>/* 挿入ソート */\nfunction insertionSort(nums: number[]): void {\n // 外側のループ:ソート済み区間は [0, i-1]\n for (let i = 1; i &lt; nums.length; i++) {\n const base = nums[i];\n let j = i - 1;\n // 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while (j &gt;= 0 &amp;&amp; nums[j] &gt; base) {\n nums[j + 1] = nums[j]; // nums[j] を右に 1 つずらす\n j--;\n }\n nums[j + 1] = base; // 正しい位置に base を代入する\n }\n}\n</code></pre> insertion_sort.dart<pre><code>/* 挿入ソート */\nvoid insertionSort(List&lt;int&gt; nums) {\n // 外側のループ:ソート済み区間は [0, i-1]\n for (int i = 1; i &lt; nums.length; i++) {\n int base = nums[i], j = i - 1;\n // 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while (j &gt;= 0 &amp;&amp; nums[j] &gt; base) {\n nums[j + 1] = nums[j]; // nums[j] を右に 1 つずらす\n j--;\n }\n nums[j + 1] = base; // 正しい位置に base を代入する\n }\n}\n</code></pre> insertion_sort.rs<pre><code>/* 挿入ソート */\nfn insertion_sort(nums: &amp;mut [i32]) {\n // 外側のループ:ソート済み区間は [0, i-1]\n for i in 1..nums.len() {\n let (base, mut j) = (nums[i], (i - 1) as i32);\n // 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while j &gt;= 0 &amp;&amp; nums[j as usize] &gt; base {\n nums[(j + 1) as usize] = nums[j as usize]; // nums[j] を右に 1 つずらす\n j -= 1;\n }\n nums[(j + 1) as usize] = base; // 正しい位置に base を代入する\n }\n}\n</code></pre> insertion_sort.c<pre><code>/* 挿入ソート */\nvoid insertionSort(int nums[], int size) {\n // 外側のループ:ソート済み区間は [0, i-1]\n for (int i = 1; i &lt; size; i++) {\n int base = nums[i], j = i - 1;\n // 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while (j &gt;= 0 &amp;&amp; nums[j] &gt; base) {\n // nums[j] を右に 1 つずらす\n nums[j + 1] = nums[j];\n j--;\n }\n // 正しい位置に base を代入する\n nums[j + 1] = base;\n }\n}\n</code></pre> insertion_sort.kt<pre><code>/* 挿入ソート */\nfun insertionSort(nums: IntArray) {\n //外側のループ:ソート済みの要素は 1, 2, ..., n\n for (i in nums.indices) {\n val base = nums[i]\n var j = i - 1\n // 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while (j &gt;= 0 &amp;&amp; nums[j] &gt; base) {\n nums[j + 1] = nums[j] // nums[j] を右に 1 つずらす\n j--\n }\n nums[j + 1] = base // 正しい位置に base を代入する\n }\n}\n</code></pre> insertion_sort.rb<pre><code>### 挿入ソート ###\ndef insertion_sort(nums)\n n = nums.length\n # 外側のループ:ソート済み区間は [0, i-1]\n for i in 1...n\n base = nums[i]\n j = i - 1\n # 内側のループ:ソート済み区間 [0, i-1] の正しい位置に base を挿入する\n while j &gt;= 0 &amp;&amp; nums[j] &gt; base\n nums[j + 1] = nums[j] # nums[j] を右に 1 つずらす\n j -= 1\n end\n nums[j + 1] = base # 正しい位置に base を代入する\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.4   挿入ソート"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1142","level":2,"title":"11.4.2   アルゴリズムの特徴","text":"<ul> <li>時間計算量は \\(O(n^2)\\)、適応的ソート:最悪の場合、各挿入操作で必要な反復は \\(n - 1\\)、\\(n-2\\)、\\(\\dots\\)、\\(2\\)、\\(1\\) 回となり、合計は \\((n - 1) n / 2\\) なので、時間計算量は \\(O(n^2)\\) です。整列済みのデータでは挿入操作が早く終了します。入力配列が完全に整列済みなら、最良時間計算量 \\(O(n)\\) となります。</li> <li>空間計算量は \\(O(1)\\)、インプレースソート:ポインタ \\(i\\) と \\(j\\) は定数サイズの追加領域を使います。</li> <li>安定ソート:挿入操作では等しい要素の右側に挿入するため、それらの順序は変わりません。</li> </ul>","path":["第 11 章   ソート","11.4   挿入ソート"],"tags":[]},{"location":"chapter_sorting/insertion_sort/#1143","level":2,"title":"11.4.3   挿入ソートの利点","text":"<p>挿入ソートの時間計算量は \\(O(n^2)\\) で、これから学ぶクイックソートは \\(O(n \\log n)\\) です。挿入ソートのほうが時間計算量は大きいものの、データが少ない場合は、通常、挿入ソートのほうが速くなります。</p> <p>これは、線形探索と二分探索の適した状況についての結論と似ています。クイックソートのような \\(O(n \\log n)\\) のアルゴリズムは分割統治法に基づき、基本的な計算操作を多く含む傾向があります。データが少ないと \\(n^2\\) と \\(n \\log n\\) の値は近く、計算量よりも各反復の基本操作数が決定的な役割を果たします。</p> <p>実際、Java など多くのプログラミング言語の組み込みソート関数は挿入ソートを採用しています。おおむね、長い配列にはクイックソートなど分割統治法に基づく方法を使い、短い配列には挿入ソートを直接使います。</p> <p>バブルソート、選択ソート、挿入ソートはいずれも時間計算量が \\(O(n^2)\\) ですが、実際には挿入ソートがほかの二つよりはるかによく使われます。主な理由は次のとおりです。</p> <ul> <li>バブルソートは要素の交換を使うため、一時変数が必要で、基本操作を 3 回行います。一方、挿入ソートは要素への代入を使うため、基本操作は 1 回だけです。したがって、通常、バブルソートの計算負荷は挿入ソートより大きくなります。</li> <li>選択ソートの時間計算量は、どのような場合も \\(O(n^2)\\) です。一部が整列済みのデータでは、通常、挿入ソートのほうが選択ソートより効率的です。</li> <li>選択ソートは不安定なので、複数の基準で段階的にソートする用途には使えません。</li> </ul>","path":["第 11 章   ソート","11.4   挿入ソート"],"tags":[]},{"location":"chapter_sorting/merge_sort/","level":1,"title":"11.6   マージソート","text":"<p>マージソート(merge sort)は分割統治法に基づくソートアルゴリズムで、図 11-10 の「分割」と「結合」の段階からなります。</p> <ol> <li>分割の段階:再帰的に配列を中点で分け続け、長い配列のソート問題を短い配列のソート問題へ変換します。</li> <li>結合の段階:部分配列の長さが 1 になったら分割を終了して結合を始めます。左右の短い整列済み配列を一つの長い整列済み配列へ結合し、最後まで続けます。</li> </ol> <p></p> <p> 図 11-10   マージソートの分割と結合の段階 </p>","path":["第 11 章   ソート","11.6   マージソート"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1161","level":2,"title":"11.6.1   アルゴリズムの流れ","text":"<p>図 11-11 のように、分割の段階では上から下へ再帰しながら、配列を中点で二つの部分配列に分けます。</p> <ol> <li>中点 <code>mid</code> を計算し、左の部分配列(区間 <code>[left, mid]</code>)と右の部分配列(区間 <code>[mid + 1, right]</code>)を再帰的に分割します。</li> <li>手順 <code>1.</code> を再帰的に繰り返し、部分配列の区間の長さが 1 になったら終了します。</li> </ol> <p>結合の段階では、下から上へ左右の部分配列を一つの整列済み配列に結合します。長さ 1 の部分配列から結合を始めるので、この段階の各部分配列はすべて整列済みであることに注意してください。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 11-11   マージソートの手順 </p> <p>マージソートの再帰の順序は、二分木の帰りがけ順走査と同じです。</p> <ul> <li>帰りがけ順走査:まず左の部分木を再帰的に処理し、次に右の部分木を処理して、最後に根ノードを処理します。</li> <li>マージソート:まず左の部分配列を再帰的に処理し、次に右の部分配列を処理して、最後に結合します。</li> </ul> <p>実装は次のとおりです。結合する <code>nums</code> の区間は <code>[left, right]</code> ですが、<code>tmp</code> の対応する区間は <code>[0, right - left]</code> である点に注意してください。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby merge_sort.py<pre><code>def merge(nums: list[int], left: int, mid: int, right: int):\n \"\"\"左部分配列と右部分配列をマージする\"\"\"\n # 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n # マージ後の結果を格納する一時配列 tmp を作成する\n tmp = [0] * (right - left + 1)\n # 左部分配列と右部分配列の開始インデックスを初期化する\n i, j, k = left, mid + 1, 0\n # 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while i &lt;= mid and j &lt;= right:\n if nums[i] &lt;= nums[j]:\n tmp[k] = nums[i]\n i += 1\n else:\n tmp[k] = nums[j]\n j += 1\n k += 1\n # 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while i &lt;= mid:\n tmp[k] = nums[i]\n i += 1\n k += 1\n while j &lt;= right:\n tmp[k] = nums[j]\n j += 1\n k += 1\n # 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for k in range(0, len(tmp)):\n nums[left + k] = tmp[k]\n\ndef merge_sort(nums: list[int], left: int, right: int):\n \"\"\"マージソート\"\"\"\n # 終了条件\n if left &gt;= right:\n return # 部分配列の長さが 1 になったら再帰を終了する\n # 分割段階\n mid = (left + right) // 2 # 中央を計算する\n merge_sort(nums, left, mid) # 左部分配列を再帰的に処理する\n merge_sort(nums, mid + 1, right) # 右部分配列を再帰的に処理する\n # マージ段階\n merge(nums, left, mid, right)\n</code></pre> merge_sort.cpp<pre><code>/* 左部分配列と右部分配列をマージする */\nvoid merge(vector&lt;int&gt; &amp;nums, int left, int mid, int right) {\n // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n // マージ後の結果を格納する一時配列 tmp を作成する\n vector&lt;int&gt; tmp(right - left + 1);\n // 左部分配列と右部分配列の開始インデックスを初期化する\n int i = left, j = mid + 1, k = 0;\n // 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while (i &lt;= mid &amp;&amp; j &lt;= right) {\n if (nums[i] &lt;= nums[j])\n tmp[k++] = nums[i++];\n else\n tmp[k++] = nums[j++];\n }\n // 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while (i &lt;= mid) {\n tmp[k++] = nums[i++];\n }\n while (j &lt;= right) {\n tmp[k++] = nums[j++];\n }\n // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for (k = 0; k &lt; tmp.size(); k++) {\n nums[left + k] = tmp[k];\n }\n}\n\n/* マージソート */\nvoid mergeSort(vector&lt;int&gt; &amp;nums, int left, int right) {\n // 終了条件\n if (left &gt;= right)\n return; // 部分配列の長さが 1 になったら再帰を終了する\n // 分割段階\n int mid = left + (right - left) / 2; // 中央を計算する\n mergeSort(nums, left, mid); // 左部分配列を再帰的に処理する\n mergeSort(nums, mid + 1, right); // 右部分配列を再帰的に処理する\n // マージ段階\n merge(nums, left, mid, right);\n}\n</code></pre> merge_sort.java<pre><code>/* 左部分配列と右部分配列をマージする */\nvoid merge(int[] nums, int left, int mid, int right) {\n // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n // マージ後の結果を格納する一時配列 tmp を作成する\n int[] tmp = new int[right - left + 1];\n // 左部分配列と右部分配列の開始インデックスを初期化する\n int i = left, j = mid + 1, k = 0;\n // 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while (i &lt;= mid &amp;&amp; j &lt;= right) {\n if (nums[i] &lt;= nums[j])\n tmp[k++] = nums[i++];\n else\n tmp[k++] = nums[j++];\n }\n // 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while (i &lt;= mid) {\n tmp[k++] = nums[i++];\n }\n while (j &lt;= right) {\n tmp[k++] = nums[j++];\n }\n // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for (k = 0; k &lt; tmp.length; k++) {\n nums[left + k] = tmp[k];\n }\n}\n\n/* マージソート */\nvoid mergeSort(int[] nums, int left, int right) {\n // 終了条件\n if (left &gt;= right)\n return; // 部分配列の長さが 1 になったら再帰を終了する\n // 分割段階\n int mid = left + (right - left) / 2; // 中央を計算する\n mergeSort(nums, left, mid); // 左部分配列を再帰的に処理する\n mergeSort(nums, mid + 1, right); // 右部分配列を再帰的に処理する\n // マージ段階\n merge(nums, left, mid, right);\n}\n</code></pre> merge_sort.cs<pre><code>/* 左部分配列と右部分配列をマージする */\nvoid Merge(int[] nums, int left, int mid, int right) {\n // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n // マージ後の結果を格納する一時配列 tmp を作成する\n int[] tmp = new int[right - left + 1];\n // 左部分配列と右部分配列の開始インデックスを初期化する\n int i = left, j = mid + 1, k = 0;\n // 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while (i &lt;= mid &amp;&amp; j &lt;= right) {\n if (nums[i] &lt;= nums[j])\n tmp[k++] = nums[i++];\n else\n tmp[k++] = nums[j++];\n }\n // 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while (i &lt;= mid) {\n tmp[k++] = nums[i++];\n }\n while (j &lt;= right) {\n tmp[k++] = nums[j++];\n }\n // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for (k = 0; k &lt; tmp.Length; ++k) {\n nums[left + k] = tmp[k];\n }\n}\n\n/* マージソート */\nvoid MergeSort(int[] nums, int left, int right) {\n // 終了条件\n if (left &gt;= right) return; // 部分配列の長さが 1 になったら再帰を終了する\n // 分割段階\n int mid = left + (right - left) / 2; // 中央を計算する\n MergeSort(nums, left, mid); // 左部分配列を再帰的に処理する\n MergeSort(nums, mid + 1, right); // 右部分配列を再帰的に処理する\n // マージ段階\n Merge(nums, left, mid, right);\n}\n</code></pre> merge_sort.go<pre><code>/* 左部分配列と右部分配列をマージする */\nfunc merge(nums []int, left, mid, right int) {\n // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n // マージ後の結果を格納する一時配列 tmp を作成する\n tmp := make([]int, right-left+1)\n // 左部分配列と右部分配列の開始インデックスを初期化する\n i, j, k := left, mid+1, 0\n // 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n for i &lt;= mid &amp;&amp; j &lt;= right {\n if nums[i] &lt;= nums[j] {\n tmp[k] = nums[i]\n i++\n } else {\n tmp[k] = nums[j]\n j++\n }\n k++\n }\n // 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n for i &lt;= mid {\n tmp[k] = nums[i]\n i++\n k++\n }\n for j &lt;= right {\n tmp[k] = nums[j]\n j++\n k++\n }\n // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for k := 0; k &lt; len(tmp); k++ {\n nums[left+k] = tmp[k]\n }\n}\n\n/* マージソート */\nfunc mergeSort(nums []int, left, right int) {\n // 終了条件\n if left &gt;= right {\n return\n }\n // 分割段階\n mid := left + (right - left) / 2\n mergeSort(nums, left, mid)\n mergeSort(nums, mid+1, right)\n // マージ段階\n merge(nums, left, mid, right)\n}\n</code></pre> merge_sort.swift<pre><code>/* 左部分配列と右部分配列をマージする */\nfunc merge(nums: inout [Int], left: Int, mid: Int, right: Int) {\n // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n // マージ後の結果を格納する一時配列 tmp を作成する\n var tmp = Array(repeating: 0, count: right - left + 1)\n // 左部分配列と右部分配列の開始インデックスを初期化する\n var i = left, j = mid + 1, k = 0\n // 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while i &lt;= mid, j &lt;= right {\n if nums[i] &lt;= nums[j] {\n tmp[k] = nums[i]\n i += 1\n } else {\n tmp[k] = nums[j]\n j += 1\n }\n k += 1\n }\n // 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while i &lt;= mid {\n tmp[k] = nums[i]\n i += 1\n k += 1\n }\n while j &lt;= right {\n tmp[k] = nums[j]\n j += 1\n k += 1\n }\n // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for k in tmp.indices {\n nums[left + k] = tmp[k]\n }\n}\n\n/* マージソート */\nfunc mergeSort(nums: inout [Int], left: Int, right: Int) {\n // 終了条件\n if left &gt;= right { // 部分配列の長さが 1 になったら再帰を終了する\n return\n }\n // 分割段階\n let mid = left + (right - left) / 2 // 中央を計算する\n mergeSort(nums: &amp;nums, left: left, right: mid) // 左部分配列を再帰的に処理する\n mergeSort(nums: &amp;nums, left: mid + 1, right: right) // 右部分配列を再帰的に処理する\n // マージ段階\n merge(nums: &amp;nums, left: left, mid: mid, right: right)\n}\n</code></pre> merge_sort.js<pre><code>/* 左部分配列と右部分配列をマージする */\nfunction merge(nums, left, mid, right) {\n // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n // マージ後の結果を格納する一時配列 tmp を作成する\n const tmp = new Array(right - left + 1);\n // 左部分配列と右部分配列の開始インデックスを初期化する\n let i = left,\n j = mid + 1,\n k = 0;\n // 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while (i &lt;= mid &amp;&amp; j &lt;= right) {\n if (nums[i] &lt;= nums[j]) {\n tmp[k++] = nums[i++];\n } else {\n tmp[k++] = nums[j++];\n }\n }\n // 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while (i &lt;= mid) {\n tmp[k++] = nums[i++];\n }\n while (j &lt;= right) {\n tmp[k++] = nums[j++];\n }\n // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for (k = 0; k &lt; tmp.length; k++) {\n nums[left + k] = tmp[k];\n }\n}\n\n/* マージソート */\nfunction mergeSort(nums, left, right) {\n // 終了条件\n if (left &gt;= right) return; // 部分配列の長さが 1 になったら再帰を終了する\n // 分割段階\n let mid = Math.floor(left + (right - left) / 2); // 中央を計算する\n mergeSort(nums, left, mid); // 左部分配列を再帰的に処理する\n mergeSort(nums, mid + 1, right); // 右部分配列を再帰的に処理する\n // マージ段階\n merge(nums, left, mid, right);\n}\n</code></pre> merge_sort.ts<pre><code>/* 左部分配列と右部分配列をマージする */\nfunction merge(nums: number[], left: number, mid: number, right: number): void {\n // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n // マージ後の結果を格納する一時配列 tmp を作成する\n const tmp = new Array(right - left + 1);\n // 左部分配列と右部分配列の開始インデックスを初期化する\n let i = left,\n j = mid + 1,\n k = 0;\n // 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while (i &lt;= mid &amp;&amp; j &lt;= right) {\n if (nums[i] &lt;= nums[j]) {\n tmp[k++] = nums[i++];\n } else {\n tmp[k++] = nums[j++];\n }\n }\n // 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while (i &lt;= mid) {\n tmp[k++] = nums[i++];\n }\n while (j &lt;= right) {\n tmp[k++] = nums[j++];\n }\n // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for (k = 0; k &lt; tmp.length; k++) {\n nums[left + k] = tmp[k];\n }\n}\n\n/* マージソート */\nfunction mergeSort(nums: number[], left: number, right: number): void {\n // 終了条件\n if (left &gt;= right) return; // 部分配列の長さが 1 になったら再帰を終了する\n // 分割段階\n let mid = Math.floor(left + (right - left) / 2); // 中央を計算する\n mergeSort(nums, left, mid); // 左部分配列を再帰的に処理する\n mergeSort(nums, mid + 1, right); // 右部分配列を再帰的に処理する\n // マージ段階\n merge(nums, left, mid, right);\n}\n</code></pre> merge_sort.dart<pre><code>/* 左部分配列と右部分配列をマージする */\nvoid merge(List&lt;int&gt; nums, int left, int mid, int right) {\n // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n // マージ後の結果を格納する一時配列 tmp を作成する\n List&lt;int&gt; tmp = List.filled(right - left + 1, 0);\n // 左部分配列と右部分配列の開始インデックスを初期化する\n int i = left, j = mid + 1, k = 0;\n // 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while (i &lt;= mid &amp;&amp; j &lt;= right) {\n if (nums[i] &lt;= nums[j])\n tmp[k++] = nums[i++];\n else\n tmp[k++] = nums[j++];\n }\n // 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while (i &lt;= mid) {\n tmp[k++] = nums[i++];\n }\n while (j &lt;= right) {\n tmp[k++] = nums[j++];\n }\n // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for (k = 0; k &lt; tmp.length; k++) {\n nums[left + k] = tmp[k];\n }\n}\n\n/* マージソート */\nvoid mergeSort(List&lt;int&gt; nums, int left, int right) {\n // 終了条件\n if (left &gt;= right) return; // 部分配列の長さが 1 になったら再帰を終了する\n // 分割段階\n int mid = left + (right - left) ~/ 2; // 中央を計算する\n mergeSort(nums, left, mid); // 左部分配列を再帰的に処理する\n mergeSort(nums, mid + 1, right); // 右部分配列を再帰的に処理する\n // マージ段階\n merge(nums, left, mid, right);\n}\n</code></pre> merge_sort.rs<pre><code>/* 左部分配列と右部分配列をマージする */\nfn merge(nums: &amp;mut [i32], left: usize, mid: usize, right: usize) {\n // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n // マージ後の結果を格納する一時配列 tmp を作成する\n let tmp_size = right - left + 1;\n let mut tmp = vec![0; tmp_size];\n // 左部分配列と右部分配列の開始インデックスを初期化する\n let (mut i, mut j, mut k) = (left, mid + 1, 0);\n // 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while i &lt;= mid &amp;&amp; j &lt;= right {\n if nums[i] &lt;= nums[j] {\n tmp[k] = nums[i];\n i += 1;\n } else {\n tmp[k] = nums[j];\n j += 1;\n }\n k += 1;\n }\n // 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while i &lt;= mid {\n tmp[k] = nums[i];\n k += 1;\n i += 1;\n }\n while j &lt;= right {\n tmp[k] = nums[j];\n k += 1;\n j += 1;\n }\n // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for k in 0..tmp_size {\n nums[left + k] = tmp[k];\n }\n}\n\n/* マージソート */\nfn merge_sort(nums: &amp;mut [i32], left: usize, right: usize) {\n // 終了条件\n if left &gt;= right {\n return; // 部分配列の長さが 1 になったら再帰を終了する\n }\n\n // 分割段階\n let mid = left + (right - left) / 2; // 中央を計算する\n merge_sort(nums, left, mid); // 左部分配列を再帰的に処理する\n merge_sort(nums, mid + 1, right); // 右部分配列を再帰的に処理する\n\n // マージ段階\n merge(nums, left, mid, right);\n}\n</code></pre> merge_sort.c<pre><code>/* 左部分配列と右部分配列をマージする */\nvoid merge(int *nums, int left, int mid, int right) {\n // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n // マージ後の結果を格納する一時配列 tmp を作成する\n int tmpSize = right - left + 1;\n int *tmp = (int *)malloc(tmpSize * sizeof(int));\n // 左部分配列と右部分配列の開始インデックスを初期化する\n int i = left, j = mid + 1, k = 0;\n // 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while (i &lt;= mid &amp;&amp; j &lt;= right) {\n if (nums[i] &lt;= nums[j]) {\n tmp[k++] = nums[i++];\n } else {\n tmp[k++] = nums[j++];\n }\n }\n // 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while (i &lt;= mid) {\n tmp[k++] = nums[i++];\n }\n while (j &lt;= right) {\n tmp[k++] = nums[j++];\n }\n // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for (k = 0; k &lt; tmpSize; ++k) {\n nums[left + k] = tmp[k];\n }\n // メモリを解放する\n free(tmp);\n}\n\n/* マージソート */\nvoid mergeSort(int *nums, int left, int right) {\n // 終了条件\n if (left &gt;= right)\n return; // 部分配列の長さが 1 になったら再帰を終了する\n // 分割段階\n int mid = left + (right - left) / 2; // 中央を計算する\n mergeSort(nums, left, mid); // 左部分配列を再帰的に処理する\n mergeSort(nums, mid + 1, right); // 右部分配列を再帰的に処理する\n // マージ段階\n merge(nums, left, mid, right);\n}\n</code></pre> merge_sort.kt<pre><code>/* 左部分配列と右部分配列をマージする */\nfun merge(nums: IntArray, left: Int, mid: Int, right: Int) {\n // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n // マージ後の結果を格納する一時配列 tmp を作成する\n val tmp = IntArray(right - left + 1)\n // 左部分配列と右部分配列の開始インデックスを初期化する\n var i = left\n var j = mid + 1\n var k = 0\n // 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while (i &lt;= mid &amp;&amp; j &lt;= right) {\n if (nums[i] &lt;= nums[j])\n tmp[k++] = nums[i++]\n else\n tmp[k++] = nums[j++]\n }\n // 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while (i &lt;= mid) {\n tmp[k++] = nums[i++]\n }\n while (j &lt;= right) {\n tmp[k++] = nums[j++]\n }\n // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n for (l in tmp.indices) {\n nums[left + l] = tmp[l]\n }\n}\n\n/* マージソート */\nfun mergeSort(nums: IntArray, left: Int, right: Int) {\n // 終了条件\n if (left &gt;= right) return // 部分配列の長さが 1 になったら再帰を終了する\n // 分割段階\n val mid = left + (right - left) / 2 // 中央を計算する\n mergeSort(nums, left, mid) // 左部分配列を再帰的に処理する\n mergeSort(nums, mid + 1, right) // 右部分配列を再帰的に処理する\n // マージ段階\n merge(nums, left, mid, right)\n}\n</code></pre> merge_sort.rb<pre><code>### 左部分配列と右部分配列をマージする ###\ndef merge(nums, left, mid, right)\n # 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]\n # マージ後の結果を格納する一時配列 tmp を作成する\n tmp = Array.new(right - left + 1, 0)\n # 左部分配列と右部分配列の開始インデックスを初期化する\n i, j, k = left, mid + 1, 0\n # 左右両方の部分配列に要素が残っている間、比較して小さいほうの要素を一時配列にコピーする\n while i &lt;= mid &amp;&amp; j &lt;= right\n if nums[i] &lt;= nums[j]\n tmp[k] = nums[i]\n i += 1\n else\n tmp[k] = nums[j]\n j += 1\n end\n k += 1\n end\n # 左部分配列と右部分配列の残りの要素を一時配列にコピーする\n while i &lt;= mid\n tmp[k] = nums[i]\n i += 1\n k += 1\n end\n while j &lt;= right\n tmp[k] = nums[j]\n j += 1\n k += 1\n end\n # 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーし戻す\n (0...tmp.length).each do |k|\n nums[left + k] = tmp[k]\n end\nend\n\n### マージソート ###\ndef merge_sort(nums, left, right)\n # 終了条件\n # 部分配列の長さが 1 になったら再帰を終了する\n return if left &gt;= right\n # 分割段階\n mid = left + (right - left) / 2 # 中央を計算する\n merge_sort(nums, left, mid) # 左部分配列を再帰的に処理する\n merge_sort(nums, mid + 1, right) # 右部分配列を再帰的に処理する\n # マージ段階\n merge(nums, left, mid, right)\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.6   マージソート"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1162","level":2,"title":"11.6.2   アルゴリズムの特徴","text":"<ul> <li>時間計算量は \\(O(n \\log n)\\)、非適応的ソート:分割によって高さ \\(\\log n\\) の再帰木ができ、各層の結合操作の総数は \\(n\\) なので、全体の時間計算量は \\(O(n \\log n)\\) です。</li> <li>空間計算量は \\(O(n)\\)、非インプレースソート:再帰の深さは \\(\\log n\\) で、スタックフレームに \\(O(\\log n)\\) の領域を使います。結合には補助配列が必要で、\\(O(n)\\) の追加領域を使います。</li> <li>安定ソート:結合の過程で等しい要素の順序は変わりません。</li> </ul>","path":["第 11 章   ソート","11.6   マージソート"],"tags":[]},{"location":"chapter_sorting/merge_sort/#1163","level":2,"title":"11.6.3   連結リストのソート","text":"<p>連結リストでは、マージソートはほかのソートアルゴリズムより大きな利点があります。連結リストをソートする空間計算量を \\(O(1)\\) に改善できるためです。</p> <ul> <li>分割の段階:再帰の代わりに反復を使って連結リストを分割すれば、再帰のスタックフレーム領域を省けます。</li> <li>結合の段階:連結リストのノードの追加や削除は、参照(ポインタ)の変更だけで行えます。そのため、二つの短い整列済みリストを一つの長い整列済みリストに結合する際、追加のリストを作る必要はありません。</li> </ul> <p>具体的な実装はやや複雑なので、興味があれば関連資料で学んでみてください。</p>","path":["第 11 章   ソート","11.6   マージソート"],"tags":[]},{"location":"chapter_sorting/quick_sort/","level":1,"title":"11.5   クイックソート","text":"<p>クイックソート(quick sort)は分割統治法に基づくソートアルゴリズムで、実行効率が高く、広く使われています。</p> <p>中心となる操作は「番兵を用いた分割」です。配列の要素を一つ「ピボット」として選び、それより小さい要素を左へ、大きい要素を右へ移すことを目指します。具体的な流れを図 11-8 に示します。</p> <ol> <li>配列の左端の要素をピボットとして選び、ポインタ <code>i</code> と <code>j</code> を配列の両端に初期化します。</li> <li>ループの各反復で、<code>i</code>(<code>j</code>)を使ってピボットより大きい(小さい)最初の要素を探し、その二つを交換します。</li> <li>手順 <code>2.</code> を繰り返し、<code>i</code> と <code>j</code> が出会ったら停止します。最後に、ピボットを二つの部分配列の境目へ交換して移します。</li> </ol> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 11-8   番兵を用いた分割の手順 </p> <p>分割後、元の配列は左の部分配列、ピボット、右の部分配列の三つに分かれ、「左の部分配列の任意の要素 \\(\\leq\\) ピボット \\(\\leq\\) 右の部分配列の任意の要素」を満たします。したがって、あとは二つの部分配列をソートするだけです。</p> <p>クイックソートの分割統治法</p> <p>番兵を用いた分割は、長い配列のソート問題を、二つの短い配列のソート問題へと単純化します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py<pre><code>def partition(self, nums: list[int], left: int, right: int) -&gt; int:\n \"\"\"番兵を用いた分割\"\"\"\n # nums[left] をピボットとする\n i, j = left, right\n while i &lt; j:\n while i &lt; j and nums[j] &gt;= nums[left]:\n j -= 1 # 右から左へ、ピボットより小さい最初の要素を探す\n while i &lt; j and nums[i] &lt;= nums[left]:\n i += 1 # 左から右へ、ピボットより大きい最初の要素を探す\n # 要素の交換\n nums[i], nums[j] = nums[j], nums[i]\n # ピボットを 2 つの部分配列の境界に移す\n nums[i], nums[left] = nums[left], nums[i]\n return i # ピボットのインデックスを返す\n</code></pre> quick_sort.cpp<pre><code>/* 番兵を用いた分割 */\nint partition(vector&lt;int&gt; &amp;nums, int left, int right) {\n // nums[left] をピボットとする\n int i = left, j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left])\n j--; // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left])\n i++; // 左から右へ、ピボットより大きい最初の要素を探す\n swap(nums[i], nums[j]); // この 2 つの要素を交換する\n }\n swap(nums[i], nums[left]); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.java<pre><code>/* 要素の交換 */\nvoid swap(int[] nums, int i, int j) {\n int tmp = nums[i];\n nums[i] = nums[j];\n nums[j] = tmp;\n}\n\n/* 番兵を用いた分割 */\nint partition(int[] nums, int left, int right) {\n // nums[left] をピボットとする\n int i = left, j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left])\n j--; // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left])\n i++; // 左から右へ、ピボットより大きい最初の要素を探す\n swap(nums, i, j); // この 2 つの要素を交換する\n }\n swap(nums, i, left); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.cs<pre><code>/* 要素の交換 */\nvoid Swap(int[] nums, int i, int j) {\n (nums[j], nums[i]) = (nums[i], nums[j]);\n}\n\n/* 番兵を用いた分割 */\nint Partition(int[] nums, int left, int right) {\n // nums[left] をピボットとする\n int i = left, j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left])\n j--; // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left])\n i++; // 左から右へ、ピボットより大きい最初の要素を探す\n Swap(nums, i, j); // この 2 つの要素を交換する\n }\n Swap(nums, i, left); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.go<pre><code>/* 番兵を用いた分割 */\nfunc (q *quickSort) partition(nums []int, left, right int) int {\n // nums[left] をピボットとする\n i, j := left, right\n for i &lt; j {\n for i &lt; j &amp;&amp; nums[j] &gt;= nums[left] {\n j-- // 右から左へ、ピボットより小さい最初の要素を探す\n }\n for i &lt; j &amp;&amp; nums[i] &lt;= nums[left] {\n i++ // 左から右へ、ピボットより大きい最初の要素を探す\n }\n // 要素の交換\n nums[i], nums[j] = nums[j], nums[i]\n }\n // ピボットを 2 つの部分配列の境界に移す\n nums[i], nums[left] = nums[left], nums[i]\n return i // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.swift<pre><code>/* 番兵を用いた分割 */\nfunc partition(nums: inout [Int], left: Int, right: Int) -&gt; Int {\n // nums[left] をピボットとする\n var i = left\n var j = right\n while i &lt; j {\n while i &lt; j, nums[j] &gt;= nums[left] {\n j -= 1 // 右から左へ、ピボットより小さい最初の要素を探す\n }\n while i &lt; j, nums[i] &lt;= nums[left] {\n i += 1 // 左から右へ、ピボットより大きい最初の要素を探す\n }\n nums.swapAt(i, j) // この 2 つの要素を交換する\n }\n nums.swapAt(i, left) // ピボットを 2 つの部分配列の境界に移す\n return i // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.js<pre><code>/* 要素の交換 */\nswap(nums, i, j) {\n let tmp = nums[i];\n nums[i] = nums[j];\n nums[j] = tmp;\n}\n\n/* 番兵を用いた分割 */\npartition(nums, left, right) {\n // nums[left] をピボットとする\n let i = left,\n j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left]) {\n j -= 1; // 右から左へ、ピボットより小さい最初の要素を探す\n }\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left]) {\n i += 1; // 左から右へ、ピボットより大きい最初の要素を探す\n }\n // 要素の交換\n this.swap(nums, i, j); // この 2 つの要素を交換する\n }\n this.swap(nums, i, left); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.ts<pre><code>/* 要素の交換 */\nswap(nums: number[], i: number, j: number): void {\n let tmp = nums[i];\n nums[i] = nums[j];\n nums[j] = tmp;\n}\n\n/* 番兵を用いた分割 */\npartition(nums: number[], left: number, right: number): number {\n // nums[left] をピボットとする\n let i = left,\n j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left]) {\n j -= 1; // 右から左へ、ピボットより小さい最初の要素を探す\n }\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left]) {\n i += 1; // 左から右へ、ピボットより大きい最初の要素を探す\n }\n // 要素の交換\n this.swap(nums, i, j); // この 2 つの要素を交換する\n }\n this.swap(nums, i, left); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.dart<pre><code>/* 要素の交換 */\nvoid _swap(List&lt;int&gt; nums, int i, int j) {\n int tmp = nums[i];\n nums[i] = nums[j];\n nums[j] = tmp;\n}\n\n/* 番兵を用いた分割 */\nint _partition(List&lt;int&gt; nums, int left, int right) {\n // nums[left] をピボットとする\n int i = left, j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left]) j--; // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left]) i++; // 左から右へ、ピボットより大きい最初の要素を探す\n _swap(nums, i, j); // この 2 つの要素を交換する\n }\n _swap(nums, i, left); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.rs<pre><code>/* 番兵を用いた分割 */\nfn partition(nums: &amp;mut [i32], left: usize, right: usize) -&gt; usize {\n // nums[left] をピボットとする\n let (mut i, mut j) = (left, right);\n while i &lt; j {\n while i &lt; j &amp;&amp; nums[j] &gt;= nums[left] {\n j -= 1; // 右から左へ、ピボットより小さい最初の要素を探す\n }\n while i &lt; j &amp;&amp; nums[i] &lt;= nums[left] {\n i += 1; // 左から右へ、ピボットより大きい最初の要素を探す\n }\n nums.swap(i, j); // この 2 つの要素を交換する\n }\n nums.swap(i, left); // ピボットを 2 つの部分配列の境界に移す\n i // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.c<pre><code>/* 要素の交換 */\nvoid swap(int nums[], int i, int j) {\n int tmp = nums[i];\n nums[i] = nums[j];\n nums[j] = tmp;\n}\n\n/* 番兵を用いた分割 */\nint partition(int nums[], int left, int right) {\n // nums[left] をピボットとする\n int i = left, j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left]) {\n j--; // 右から左へ、ピボットより小さい最初の要素を探す\n }\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left]) {\n i++; // 左から右へ、ピボットより大きい最初の要素を探す\n }\n // この 2 つの要素を交換する\n swap(nums, i, j);\n }\n // ピボットを 2 つの部分配列の境界に移す\n swap(nums, i, left);\n // ピボットのインデックスを返す\n return i;\n}\n</code></pre> quick_sort.kt<pre><code>/* 要素の交換 */\nfun swap(nums: IntArray, i: Int, j: Int) {\n val temp = nums[i]\n nums[i] = nums[j]\n nums[j] = temp\n}\n\n/* 番兵を用いた分割 */\nfun partition(nums: IntArray, left: Int, right: Int): Int {\n // nums[left] をピボットとする\n var i = left\n var j = right\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left])\n j-- // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left])\n i++ // 左から右へ、ピボットより大きい最初の要素を探す\n swap(nums, i, j) // この 2 つの要素を交換する\n }\n swap(nums, i, left) // ピボットを 2 つの部分配列の境界に移す\n return i // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.rb<pre><code>### 番兵を用いた分割 ###\ndef partition(nums, left, right)\n # nums[left] をピボットとする\n i, j = left, right\n while i &lt; j\n while i &lt; j &amp;&amp; nums[j] &gt;= nums[left]\n j -= 1 # 右から左へ、ピボットより小さい最初の要素を探す\n end\n while i &lt; j &amp;&amp; nums[i] &lt;= nums[left]\n i += 1 # 左から右へ、ピボットより大きい最初の要素を探す\n end\n # 要素の交換\n nums[i], nums[j] = nums[j], nums[i]\n end\n # ピボットを 2 つの部分配列の境界に移す\n nums[i], nums[left] = nums[left], nums[i]\n i # ピボットのインデックスを返す\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.5   クイックソート"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1151","level":2,"title":"11.5.1   アルゴリズムの流れ","text":"<p>クイックソート全体の流れを図 11-9 に示します。</p> <ol> <li>まず、元の配列に番兵を用いた分割を一回行い、未整列の左右の部分配列を得ます。</li> <li>続いて、左右それぞれの部分配列に対して、再帰的に同じ分割を行います。</li> <li>部分配列の長さが 1 になるまで再帰を続けると、配列全体のソートが完了します。</li> </ol> <p></p> <p> 図 11-9   クイックソートの流れ </p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py<pre><code>def quick_sort(self, nums: list[int], left: int, right: int):\n \"\"\"クイックソート\"\"\"\n # 部分配列の長さが 1 になったら再帰を終了する\n if left &gt;= right:\n return\n # 番兵を用いた分割\n pivot = self.partition(nums, left, right)\n # 左部分配列と右部分配列を再帰的に処理する\n self.quick_sort(nums, left, pivot - 1)\n self.quick_sort(nums, pivot + 1, right)\n</code></pre> quick_sort.cpp<pre><code>/* クイックソート */\nvoid quickSort(vector&lt;int&gt; &amp;nums, int left, int right) {\n // 部分配列の長さが 1 になったら再帰を終了する\n if (left &gt;= right)\n return;\n // 番兵を用いた分割\n int pivot = partition(nums, left, right);\n // 左部分配列と右部分配列を再帰的に処理する\n quickSort(nums, left, pivot - 1);\n quickSort(nums, pivot + 1, right);\n}\n</code></pre> quick_sort.java<pre><code>/* クイックソート */\nvoid quickSort(int[] nums, int left, int right) {\n // 部分配列の長さが 1 になったら再帰を終了する\n if (left &gt;= right)\n return;\n // 番兵を用いた分割\n int pivot = partition(nums, left, right);\n // 左部分配列と右部分配列を再帰的に処理する\n quickSort(nums, left, pivot - 1);\n quickSort(nums, pivot + 1, right);\n}\n</code></pre> quick_sort.cs<pre><code>/* クイックソート */\nvoid QuickSort(int[] nums, int left, int right) {\n // 部分配列の長さが 1 になったら再帰を終了する\n if (left &gt;= right)\n return;\n // 番兵を用いた分割\n int pivot = Partition(nums, left, right);\n // 左部分配列と右部分配列を再帰的に処理する\n QuickSort(nums, left, pivot - 1);\n QuickSort(nums, pivot + 1, right);\n}\n</code></pre> quick_sort.go<pre><code>/* クイックソート */\nfunc (q *quickSort) quickSort(nums []int, left, right int) {\n // 部分配列の長さが 1 になったら再帰を終了する\n if left &gt;= right {\n return\n }\n // 番兵を用いた分割\n pivot := q.partition(nums, left, right)\n // 左部分配列と右部分配列を再帰的に処理する\n q.quickSort(nums, left, pivot-1)\n q.quickSort(nums, pivot+1, right)\n}\n</code></pre> quick_sort.swift<pre><code>/* クイックソート */\nfunc quickSort(nums: inout [Int], left: Int, right: Int) {\n // 部分配列の長さが 1 になったら再帰を終了する\n if left &gt;= right {\n return\n }\n // 番兵を用いた分割\n let pivot = partition(nums: &amp;nums, left: left, right: right)\n // 左部分配列と右部分配列を再帰的に処理する\n quickSort(nums: &amp;nums, left: left, right: pivot - 1)\n quickSort(nums: &amp;nums, left: pivot + 1, right: right)\n}\n</code></pre> quick_sort.js<pre><code>/* クイックソート */\nquickSort(nums, left, right) {\n // 部分配列の長さが 1 になったら再帰を終了する\n if (left &gt;= right) return;\n // 番兵を用いた分割\n const pivot = this.partition(nums, left, right);\n // 左部分配列と右部分配列を再帰的に処理する\n this.quickSort(nums, left, pivot - 1);\n this.quickSort(nums, pivot + 1, right);\n}\n</code></pre> quick_sort.ts<pre><code>/* クイックソート */\nquickSort(nums: number[], left: number, right: number): void {\n // 部分配列の長さが 1 になったら再帰を終了する\n if (left &gt;= right) {\n return;\n }\n // 番兵を用いた分割\n const pivot = this.partition(nums, left, right);\n // 左部分配列と右部分配列を再帰的に処理する\n this.quickSort(nums, left, pivot - 1);\n this.quickSort(nums, pivot + 1, right);\n}\n</code></pre> quick_sort.dart<pre><code>/* クイックソート */\nvoid quickSort(List&lt;int&gt; nums, int left, int right) {\n // 部分配列の長さが 1 になったら再帰を終了する\n if (left &gt;= right) return;\n // 番兵を用いた分割\n int pivot = _partition(nums, left, right);\n // 左部分配列と右部分配列を再帰的に処理する\n quickSort(nums, left, pivot - 1);\n quickSort(nums, pivot + 1, right);\n}\n</code></pre> quick_sort.rs<pre><code>/* クイックソート */\npub fn quick_sort(left: i32, right: i32, nums: &amp;mut [i32]) {\n // 部分配列の長さが 1 になったら再帰を終了する\n if left &gt;= right {\n return;\n }\n // 番兵を用いた分割\n let pivot = Self::partition(nums, left as usize, right as usize) as i32;\n // 左部分配列と右部分配列を再帰的に処理する\n Self::quick_sort(left, pivot - 1, nums);\n Self::quick_sort(pivot + 1, right, nums);\n}\n</code></pre> quick_sort.c<pre><code>/* クイックソート */\nvoid quickSort(int nums[], int left, int right) {\n // 部分配列の長さが 1 になったら再帰を終了する\n if (left &gt;= right) {\n return;\n }\n // 番兵を用いた分割\n int pivot = partition(nums, left, right);\n // 左部分配列と右部分配列を再帰的に処理する\n quickSort(nums, left, pivot - 1);\n quickSort(nums, pivot + 1, right);\n}\n</code></pre> quick_sort.kt<pre><code>/* クイックソート */\nfun quickSort(nums: IntArray, left: Int, right: Int) {\n // 部分配列の長さが 1 になったら再帰を終了する\n if (left &gt;= right) return\n // 番兵を用いた分割\n val pivot = partition(nums, left, right)\n // 左部分配列と右部分配列を再帰的に処理する\n quickSort(nums, left, pivot - 1)\n quickSort(nums, pivot + 1, right)\n}\n</code></pre> quick_sort.rb<pre><code>### クイックソートクラス ###\ndef quick_sort(nums, left, right)\n # 部分配列の長さが 1 でない場合、再帰する\n if left &lt; right\n # 番兵を用いた分割\n pivot = partition(nums, left, right)\n # 左部分配列と右部分配列を再帰的に処理する\n quick_sort(nums, left, pivot - 1)\n quick_sort(nums, pivot + 1, right)\n end\n nums\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.5   クイックソート"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1152","level":2,"title":"11.5.2   アルゴリズムの特徴","text":"<ul> <li>時間計算量は \\(O(n \\log n)\\)、非適応的ソート:平均的な場合、分割の再帰の層数は \\(\\log n\\)、各層の反復回数の合計は \\(n\\) なので、全体で \\(O(n \\log n)\\) 時間です。最悪の場合、毎回、長さ \\(n\\) の配列が長さ \\(0\\) と \\(n - 1\\) の部分配列に分かれます。このとき再帰の層数は \\(n\\)、各層の反復回数は \\(n\\) となり、全体で \\(O(n^2)\\) 時間かかります。</li> <li>空間計算量は \\(O(n)\\)、インプレースソート:入力配列が完全に逆順の場合、再帰の深さは最悪の \\(n\\) となり、スタックフレームに \\(O(n)\\) の領域を使います。ソート自体は元の配列上で行い、追加の配列は使いません。</li> <li>不安定ソート:分割の最後の手順で、ピボットが等しい要素の右側に交換される可能性があります。</li> </ul>","path":["第 11 章   ソート","11.5   クイックソート"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1153","level":2,"title":"11.5.3   クイックソートが速い理由","text":"<p>名前からも、クイックソートには効率上の利点があることがうかがえます。平均時間計算量はマージソートやヒープソートと同じですが、通常はクイックソートのほうが効率的です。主な理由は次のとおりです。</p> <ul> <li>最悪の場合になる確率が低い:最悪時間計算量は \\(O(n^2)\\) で、マージソートほど安定していませんが、大多数の場合は \\(O(n \\log n)\\) 時間で実行できます。</li> <li>キャッシュを効率よく使える:分割時には部分配列全体をキャッシュに読み込めるため、要素へのアクセスが効率的です。ヒープソートのように要素へ飛び飛びにアクセスするアルゴリズムには、この特徴がありません。</li> <li>計算量の定数係数が小さい:この三つのアルゴリズムの中で、比較、代入、交換などの操作の総数が最も少なくなります。挿入ソートがバブルソートより速い理由と似ています。</li> </ul>","path":["第 11 章   ソート","11.5   クイックソート"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1154","level":2,"title":"11.5.4   ピボットの選び方の改善","text":"<p>入力によっては、クイックソートの時間効率が下がります。極端な例として、完全に逆順の配列を考えます。左端をピボットにすると、分割後にはピボットが右端に移り、左の部分配列の長さは \\(n - 1\\)、右は \\(0\\) になります。このまま再帰を続けると、毎回どちらかの部分配列の長さが \\(0\\) となり、分割統治法が機能せず、バブルソートに近い形へと退化します。</p> <p>この状況をできるだけ避けるため、分割時のピボットの選択方法を改善できます。例えば、要素をランダムに選ぶ方法があります。ただし、運悪く毎回不適切なピボットを選べば、効率は依然としてよくありません。</p> <p>プログラミング言語が通常生成するのは「疑似乱数」であることにも注意が必要です。その疑似乱数列に合わせた特定のテストケースを作れば、クイックソートの効率はやはり悪化する可能性があります。</p> <p>さらに改善するには、配列から三つの候補(通常は先頭、末尾、中点の要素)を選び、それらの中央値をピボットにします。これにより、ピボットが小さすぎず大きすぎない確率が大幅に上がります。候補をさらに増やして、頑健性を高めることもできます。この方法では、時間計算量が \\(O(n^2)\\) に悪化する確率が大きく下がります。</p> <p>コード例は次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py<pre><code>def median_three(self, nums: list[int], left: int, mid: int, right: int) -&gt; int:\n \"\"\"候補となる 3 要素の中央値を選ぶ\"\"\"\n l, m, r = nums[left], nums[mid], nums[right]\n if (l &lt;= m &lt;= r) or (r &lt;= m &lt;= l):\n return mid # m が l と r の間にある\n if (m &lt;= l &lt;= r) or (r &lt;= l &lt;= m):\n return left # l が m と r の間にある\n return right\n\ndef partition(self, nums: list[int], left: int, right: int) -&gt; int:\n \"\"\"番兵を用いた分割(3 値の中央値を選択)\"\"\"\n # nums[left] をピボットとする\n med = self.median_three(nums, left, (left + right) // 2, right)\n # 中央値を配列の左端に移す\n nums[left], nums[med] = nums[med], nums[left]\n # nums[left] をピボットとする\n i, j = left, right\n while i &lt; j:\n while i &lt; j and nums[j] &gt;= nums[left]:\n j -= 1 # 右から左へ、ピボットより小さい最初の要素を探す\n while i &lt; j and nums[i] &lt;= nums[left]:\n i += 1 # 左から右へ、ピボットより大きい最初の要素を探す\n # 要素の交換\n nums[i], nums[j] = nums[j], nums[i]\n # ピボットを 2 つの部分配列の境界に移す\n nums[i], nums[left] = nums[left], nums[i]\n return i # ピボットのインデックスを返す\n</code></pre> quick_sort.cpp<pre><code>/* 候補となる 3 要素の中央値を選ぶ */\nint medianThree(vector&lt;int&gt; &amp;nums, int left, int mid, int right) {\n int l = nums[left], m = nums[mid], r = nums[right];\n if ((l &lt;= m &amp;&amp; m &lt;= r) || (r &lt;= m &amp;&amp; m &lt;= l))\n return mid; // m が l と r の間にある\n if ((m &lt;= l &amp;&amp; l &lt;= r) || (r &lt;= l &amp;&amp; l &lt;= m))\n return left; // l が m と r の間にある\n return right;\n}\n\n/* 番兵を用いた分割(3 値の中央値を選択) */\nint partition(vector&lt;int&gt; &amp;nums, int left, int right) {\n // 候補となる 3 要素の中央値を選ぶ\n int med = medianThree(nums, left, (left + right) / 2, right);\n // 中央値を配列の左端に移す\n swap(nums[left], nums[med]);\n // nums[left] をピボットとする\n int i = left, j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left])\n j--; // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left])\n i++; // 左から右へ、ピボットより大きい最初の要素を探す\n swap(nums[i], nums[j]); // この 2 つの要素を交換する\n }\n swap(nums[i], nums[left]); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.java<pre><code>/* 候補となる 3 要素の中央値を選ぶ */\nint medianThree(int[] nums, int left, int mid, int right) {\n int l = nums[left], m = nums[mid], r = nums[right];\n if ((l &lt;= m &amp;&amp; m &lt;= r) || (r &lt;= m &amp;&amp; m &lt;= l))\n return mid; // m が l と r の間にある\n if ((m &lt;= l &amp;&amp; l &lt;= r) || (r &lt;= l &amp;&amp; l &lt;= m))\n return left; // l が m と r の間にある\n return right;\n}\n\n/* 番兵を用いた分割(3 値の中央値を選択) */\nint partition(int[] nums, int left, int right) {\n // 候補となる 3 要素の中央値を選ぶ\n int med = medianThree(nums, left, (left + right) / 2, right);\n // 中央値を配列の左端に移す\n swap(nums, left, med);\n // nums[left] をピボットとする\n int i = left, j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left])\n j--; // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left])\n i++; // 左から右へ、ピボットより大きい最初の要素を探す\n swap(nums, i, j); // この 2 つの要素を交換する\n }\n swap(nums, i, left); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.cs<pre><code>/* 候補となる 3 要素の中央値を選ぶ */\nint MedianThree(int[] nums, int left, int mid, int right) {\n int l = nums[left], m = nums[mid], r = nums[right];\n if ((l &lt;= m &amp;&amp; m &lt;= r) || (r &lt;= m &amp;&amp; m &lt;= l))\n return mid; // m が l と r の間にある\n if ((m &lt;= l &amp;&amp; l &lt;= r) || (r &lt;= l &amp;&amp; l &lt;= m))\n return left; // l が m と r の間にある\n return right;\n}\n\n/* 番兵を用いた分割(3 値の中央値を選択) */\nint Partition(int[] nums, int left, int right) {\n // 候補となる 3 要素の中央値を選ぶ\n int med = MedianThree(nums, left, (left + right) / 2, right);\n // 中央値を配列の左端に移す\n Swap(nums, left, med);\n // nums[left] をピボットとする\n int i = left, j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left])\n j--; // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left])\n i++; // 左から右へ、ピボットより大きい最初の要素を探す\n Swap(nums, i, j); // この 2 つの要素を交換する\n }\n Swap(nums, i, left); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.go<pre><code>/* 候補となる 3 要素の中央値を選ぶ */\nfunc (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int {\n l, m, r := nums[left], nums[mid], nums[right]\n if (l &lt;= m &amp;&amp; m &lt;= r) || (r &lt;= m &amp;&amp; m &lt;= l) {\n return mid // m が l と r の間にある\n }\n if (m &lt;= l &amp;&amp; l &lt;= r) || (r &lt;= l &amp;&amp; l &lt;= m) {\n return left // l が m と r の間にある\n }\n return right\n}\n\n/* 番兵を用いた分割(3 値の中央値を選択)*/\nfunc (q *quickSortMedian) partition(nums []int, left, right int) int {\n // nums[left] をピボットとする\n med := q.medianThree(nums, left, (left+right)/2, right)\n // 中央値を配列の左端に移す\n nums[left], nums[med] = nums[med], nums[left]\n // nums[left] をピボットとする\n i, j := left, right\n for i &lt; j {\n for i &lt; j &amp;&amp; nums[j] &gt;= nums[left] {\n j-- //右から左へ、ピボットより小さい最初の要素を探す\n }\n for i &lt; j &amp;&amp; nums[i] &lt;= nums[left] {\n i++ //左から右へ、ピボットより大きい最初の要素を探す\n }\n //要素の交換\n nums[i], nums[j] = nums[j], nums[i]\n }\n //ピボットを 2 つの部分配列の境界に移す\n nums[i], nums[left] = nums[left], nums[i]\n return i //ピボットのインデックスを返す\n}\n</code></pre> quick_sort.swift<pre><code>/* 候補となる 3 要素の中央値を選ぶ */\nfunc medianThree(nums: [Int], left: Int, mid: Int, right: Int) -&gt; Int {\n let l = nums[left]\n let m = nums[mid]\n let r = nums[right]\n if (l &lt;= m &amp;&amp; m &lt;= r) || (r &lt;= m &amp;&amp; m &lt;= l) {\n return mid // m が l と r の間にある\n }\n if (m &lt;= l &amp;&amp; l &lt;= r) || (r &lt;= l &amp;&amp; l &lt;= m) {\n return left // l が m と r の間にある\n }\n return right\n}\n\n/* 番兵を用いた分割(3 値の中央値を選択) */\nfunc partitionMedian(nums: inout [Int], left: Int, right: Int) -&gt; Int {\n // 候補となる 3 要素の中央値を選ぶ\n let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right)\n // 中央値を配列の左端に移す\n nums.swapAt(left, med)\n return partition(nums: &amp;nums, left: left, right: right)\n}\n</code></pre> quick_sort.js<pre><code>/* 候補となる 3 要素の中央値を選ぶ */\nmedianThree(nums, left, mid, right) {\n let l = nums[left],\n m = nums[mid],\n r = nums[right];\n // m が l と r の間にある\n if ((l &lt;= m &amp;&amp; m &lt;= r) || (r &lt;= m &amp;&amp; m &lt;= l)) return mid;\n // l が m と r の間にある\n if ((m &lt;= l &amp;&amp; l &lt;= r) || (r &lt;= l &amp;&amp; l &lt;= m)) return left;\n return right;\n}\n\n/* 番兵を用いた分割(3 値の中央値を選択) */\npartition(nums, left, right) {\n // 候補となる 3 要素の中央値を選ぶ\n let med = this.medianThree(\n nums,\n left,\n Math.floor((left + right) / 2),\n right\n );\n // 中央値を配列の左端に移す\n this.swap(nums, left, med);\n // nums[left] をピボットとする\n let i = left,\n j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left]) j--; // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left]) i++; // 左から右へ、ピボットより大きい最初の要素を探す\n this.swap(nums, i, j); // この 2 つの要素を交換する\n }\n this.swap(nums, i, left); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.ts<pre><code>/* 候補となる 3 要素の中央値を選ぶ */\nmedianThree(\n nums: number[],\n left: number,\n mid: number,\n right: number\n): number {\n let l = nums[left],\n m = nums[mid],\n r = nums[right];\n // m が l と r の間にある\n if ((l &lt;= m &amp;&amp; m &lt;= r) || (r &lt;= m &amp;&amp; m &lt;= l)) return mid;\n // l が m と r の間にある\n if ((m &lt;= l &amp;&amp; l &lt;= r) || (r &lt;= l &amp;&amp; l &lt;= m)) return left;\n return right;\n}\n\n/* 番兵を用いた分割(3 値の中央値を選択) */\npartition(nums: number[], left: number, right: number): number {\n // 候補となる 3 要素の中央値を選ぶ\n let med = this.medianThree(\n nums,\n left,\n Math.floor((left + right) / 2),\n right\n );\n // 中央値を配列の左端に移す\n this.swap(nums, left, med);\n // nums[left] をピボットとする\n let i = left,\n j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left]) {\n j--; // 右から左へ、ピボットより小さい最初の要素を探す\n }\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left]) {\n i++; // 左から右へ、ピボットより大きい最初の要素を探す\n }\n this.swap(nums, i, j); // この 2 つの要素を交換する\n }\n this.swap(nums, i, left); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.dart<pre><code>/* 候補となる 3 要素の中央値を選ぶ */\nint _medianThree(List&lt;int&gt; nums, int left, int mid, int right) {\n int l = nums[left], m = nums[mid], r = nums[right];\n if ((l &lt;= m &amp;&amp; m &lt;= r) || (r &lt;= m &amp;&amp; m &lt;= l))\n return mid; // m が l と r の間にある\n if ((m &lt;= l &amp;&amp; l &lt;= r) || (r &lt;= l &amp;&amp; l &lt;= m))\n return left; // l が m と r の間にある\n return right;\n}\n\n/* 番兵を用いた分割(3 値の中央値を選択) */\nint _partition(List&lt;int&gt; nums, int left, int right) {\n // 候補となる 3 要素の中央値を選ぶ\n int med = _medianThree(nums, left, (left + right) ~/ 2, right);\n // 中央値を配列の左端に移す\n _swap(nums, left, med);\n // nums[left] をピボットとする\n int i = left, j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left]) j--; // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left]) i++; // 左から右へ、ピボットより大きい最初の要素を探す\n _swap(nums, i, j); // この 2 つの要素を交換する\n }\n _swap(nums, i, left); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.rs<pre><code>/* 候補となる 3 要素の中央値を選ぶ */\nfn median_three(nums: &amp;mut [i32], left: usize, mid: usize, right: usize) -&gt; usize {\n let (l, m, r) = (nums[left], nums[mid], nums[right]);\n if (l &lt;= m &amp;&amp; m &lt;= r) || (r &lt;= m &amp;&amp; m &lt;= l) {\n return mid; // m が l と r の間にある\n }\n if (m &lt;= l &amp;&amp; l &lt;= r) || (r &lt;= l &amp;&amp; l &lt;= m) {\n return left; // l が m と r の間にある\n }\n right\n}\n\n/* 番兵を用いた分割(3 値の中央値を選択) */\nfn partition(nums: &amp;mut [i32], left: usize, right: usize) -&gt; usize {\n // 候補となる 3 要素の中央値を選ぶ\n let med = Self::median_three(nums, left, (left + right) / 2, right);\n // 中央値を配列の左端に移す\n nums.swap(left, med);\n // nums[left] をピボットとする\n let (mut i, mut j) = (left, right);\n while i &lt; j {\n while i &lt; j &amp;&amp; nums[j] &gt;= nums[left] {\n j -= 1; // 右から左へ、ピボットより小さい最初の要素を探す\n }\n while i &lt; j &amp;&amp; nums[i] &lt;= nums[left] {\n i += 1; // 左から右へ、ピボットより大きい最初の要素を探す\n }\n nums.swap(i, j); // この 2 つの要素を交換する\n }\n nums.swap(i, left); // ピボットを 2 つの部分配列の境界に移す\n i // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.c<pre><code>/* 候補となる 3 要素の中央値を選ぶ */\nint medianThree(int nums[], int left, int mid, int right) {\n int l = nums[left], m = nums[mid], r = nums[right];\n if ((l &lt;= m &amp;&amp; m &lt;= r) || (r &lt;= m &amp;&amp; m &lt;= l))\n return mid; // m が l と r の間にある\n if ((m &lt;= l &amp;&amp; l &lt;= r) || (r &lt;= l &amp;&amp; l &lt;= m))\n return left; // l が m と r の間にある\n return right;\n}\n\n/* 番兵を用いた分割(3 値の中央値を選択) */\nint partitionMedian(int nums[], int left, int right) {\n // 候補となる 3 要素の中央値を選ぶ\n int med = medianThree(nums, left, (left + right) / 2, right);\n // 中央値を配列の左端に移す\n swap(nums, left, med);\n // nums[left] をピボットとする\n int i = left, j = right;\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left])\n j--; // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left])\n i++; // 左から右へ、ピボットより大きい最初の要素を探す\n swap(nums, i, j); // この 2 つの要素を交換する\n }\n swap(nums, i, left); // ピボットを 2 つの部分配列の境界に移す\n return i; // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.kt<pre><code>/* 候補となる 3 要素の中央値を選ぶ */\nfun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int {\n val l = nums[left]\n val m = nums[mid]\n val r = nums[right]\n if ((m in l..r) || (m in r..l))\n return mid // m が l と r の間にある\n if ((l in m..r) || (l in r..m))\n return left // l が m と r の間にある\n return right\n}\n\n/* 番兵を用いた分割(3 値の中央値を選択) */\nfun partitionMedian(nums: IntArray, left: Int, right: Int): Int {\n // 候補となる 3 要素の中央値を選ぶ\n val med = medianThree(nums, left, (left + right) / 2, right)\n // 中央値を配列の左端に移す\n swap(nums, left, med)\n // nums[left] をピボットとする\n var i = left\n var j = right\n while (i &lt; j) {\n while (i &lt; j &amp;&amp; nums[j] &gt;= nums[left])\n j-- // 右から左へ、ピボットより小さい最初の要素を探す\n while (i &lt; j &amp;&amp; nums[i] &lt;= nums[left])\n i++ // 左から右へ、ピボットより大きい最初の要素を探す\n swap(nums, i, j) // この 2 つの要素を交換する\n }\n swap(nums, i, left) // ピボットを 2 つの部分配列の境界に移す\n return i // ピボットのインデックスを返す\n}\n</code></pre> quick_sort.rb<pre><code>### 候補となる 3 要素の中央値を選ぶ ###\ndef median_three(nums, left, mid, right)\n # 候補となる 3 要素の中央値を選ぶ\n _l, _m, _r = nums[left], nums[mid], nums[right]\n # m が l と r の間にある\n return mid if (_l &lt;= _m &amp;&amp; _m &lt;= _r) || (_r &lt;= _m &amp;&amp; _m &lt;= _l)\n # l が m と r の間にある\n return left if (_m &lt;= _l &amp;&amp; _l &lt;= _r) || (_r &lt;= _l &amp;&amp; _l &lt;= _m)\n return right\nend\n\n### 番兵を用いた分割(3 値の中央値を選択)###\ndef partition(nums, left, right)\n ### nums[left] をピボットとする\n med = median_three(nums, left, (left + right) / 2, right)\n # 中央値を配列の左端に移す\n nums[left], nums[med] = nums[med], nums[left]\n i, j = left, right\n while i &lt; j\n while i &lt; j &amp;&amp; nums[j] &gt;= nums[left]\n j -= 1 # 右から左へ、ピボットより小さい最初の要素を探す\n end\n while i &lt; j &amp;&amp; nums[i] &lt;= nums[left]\n i += 1 # 左から右へ、ピボットより大きい最初の要素を探す\n end\n # 要素の交換\n nums[i], nums[j] = nums[j], nums[i]\n end\n # ピボットを 2 つの部分配列の境界に移す\n nums[i], nums[left] = nums[left], nums[i]\n i # ピボットのインデックスを返す\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.5   クイックソート"],"tags":[]},{"location":"chapter_sorting/quick_sort/#1155","level":2,"title":"11.5.5   再帰の深さの改善","text":"<p>入力によっては、クイックソートが多くの領域を使うことがあります。完全に整列済みの入力配列を例に、再帰中の部分配列の長さを \\(m\\) とします。毎回の分割で、長さ \\(0\\) の左の部分配列と、長さ \\(m - 1\\) の右の部分配列が生まれます。つまり、一段の再帰で問題が 1 要素分しか小さくならず、再帰木の高さは \\(n - 1\\) に達し、スタックフレームに \\(O(n)\\) の領域が必要となります。</p> <p>スタックフレームの蓄積を防ぐには、毎回の分割後に二つの部分配列の長さを比べ、短いほうだけを再帰的に処理します。短いほうの長さは \\(n / 2\\) を超えないため、再帰の深さを \\(\\log n\\) 以下に抑えられ、最悪空間計算量を \\(O(\\log n)\\) に改善できます。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby quick_sort.py<pre><code>def quick_sort(self, nums: list[int], left: int, right: int):\n \"\"\"クイックソート(再帰の深さの最適化)\"\"\"\n # 部分配列の長さが 1 になったら終了する\n while left &lt; right:\n # 番兵を用いた分割操作\n pivot = self.partition(nums, left, right)\n # 2 つの部分配列のうち短いほうにクイックソートを適用する\n if pivot - left &lt; right - pivot:\n self.quick_sort(nums, left, pivot - 1) # 左部分配列を再帰的にソートする\n left = pivot + 1 # 残りの未ソート区間は [pivot + 1, right]\n else:\n self.quick_sort(nums, pivot + 1, right) # 右部分配列を再帰的にソートする\n right = pivot - 1 # 残りの未ソート区間は [left, pivot - 1]\n</code></pre> quick_sort.cpp<pre><code>/* クイックソート(再帰の深さの最適化) */\nvoid quickSort(vector&lt;int&gt; &amp;nums, int left, int right) {\n // 部分配列の長さが 1 になったら終了する\n while (left &lt; right) {\n // 番兵を用いた分割操作\n int pivot = partition(nums, left, right);\n // 2 つの部分配列のうち短いほうにクイックソートを適用する\n if (pivot - left &lt; right - pivot) {\n quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソートする\n left = pivot + 1; // 残りの未ソート区間は [pivot + 1, right]\n } else {\n quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソートする\n right = pivot - 1; // 残りの未ソート区間は [left, pivot - 1]\n }\n }\n}\n</code></pre> quick_sort.java<pre><code>/* クイックソート(再帰の深さの最適化) */\nvoid quickSort(int[] nums, int left, int right) {\n // 部分配列の長さが 1 になったら終了する\n while (left &lt; right) {\n // 番兵を用いた分割操作\n int pivot = partition(nums, left, right);\n // 2 つの部分配列のうち短いほうにクイックソートを適用する\n if (pivot - left &lt; right - pivot) {\n quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソートする\n left = pivot + 1; // 残りの未ソート区間は [pivot + 1, right]\n } else {\n quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソートする\n right = pivot - 1; // 残りの未ソート区間は [left, pivot - 1]\n }\n }\n}\n</code></pre> quick_sort.cs<pre><code>/* クイックソート(再帰の深さの最適化) */\nvoid QuickSort(int[] nums, int left, int right) {\n // 部分配列の長さが 1 になったら終了する\n while (left &lt; right) {\n // 番兵を用いた分割操作\n int pivot = Partition(nums, left, right);\n // 2 つの部分配列のうち短いほうにクイックソートを適用する\n if (pivot - left &lt; right - pivot) {\n QuickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソートする\n left = pivot + 1; // 残りの未ソート区間は [pivot + 1, right]\n } else {\n QuickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソートする\n right = pivot - 1; // 残りの未ソート区間は [left, pivot - 1]\n }\n }\n}\n</code></pre> quick_sort.go<pre><code>/* クイックソート(再帰の深さの最適化)*/\nfunc (q *quickSortTailCall) quickSort(nums []int, left, right int) {\n // 部分配列の長さが 1 になったら終了する\n for left &lt; right {\n // 番兵を用いた分割操作\n pivot := q.partition(nums, left, right)\n // 2 つの部分配列のうち短いほうにクイックソートを適用する\n if pivot-left &lt; right-pivot {\n q.quickSort(nums, left, pivot-1) // 左部分配列を再帰的にソートする\n left = pivot + 1 // 残りの未ソート区間は [pivot + 1, right]\n } else {\n q.quickSort(nums, pivot+1, right) // 右部分配列を再帰的にソートする\n right = pivot - 1 // 残りの未ソート区間は [left, pivot - 1]\n }\n }\n}\n</code></pre> quick_sort.swift<pre><code>/* クイックソート(再帰の深さの最適化) */\nfunc quickSortTailCall(nums: inout [Int], left: Int, right: Int) {\n var left = left\n var right = right\n // 部分配列の長さが 1 になったら終了する\n while left &lt; right {\n // 番兵を用いた分割操作\n let pivot = partition(nums: &amp;nums, left: left, right: right)\n // 2 つの部分配列のうち短いほうにクイックソートを適用する\n if (pivot - left) &lt; (right - pivot) {\n quickSortTailCall(nums: &amp;nums, left: left, right: pivot - 1) // 左部分配列を再帰的にソートする\n left = pivot + 1 // 残りの未ソート区間は [pivot + 1, right]\n } else {\n quickSortTailCall(nums: &amp;nums, left: pivot + 1, right: right) // 右部分配列を再帰的にソートする\n right = pivot - 1 // 残りの未ソート区間は [left, pivot - 1]\n }\n }\n}\n</code></pre> quick_sort.js<pre><code>/* クイックソート(再帰の深さの最適化) */\nquickSort(nums, left, right) {\n // 部分配列の長さが 1 になったら終了する\n while (left &lt; right) {\n // 番兵を用いた分割操作\n let pivot = this.partition(nums, left, right);\n // 2 つの部分配列のうち短いほうにクイックソートを適用する\n if (pivot - left &lt; right - pivot) {\n this.quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソートする\n left = pivot + 1; // 残りの未ソート区間は [pivot + 1, right]\n } else {\n this.quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソートする\n right = pivot - 1; // 残りの未ソート区間は [left, pivot - 1]\n }\n }\n}\n</code></pre> quick_sort.ts<pre><code>/* クイックソート(再帰の深さの最適化) */\nquickSort(nums: number[], left: number, right: number): void {\n // 部分配列の長さが 1 になったら終了する\n while (left &lt; right) {\n // 番兵を用いた分割操作\n let pivot = this.partition(nums, left, right);\n // 2 つの部分配列のうち短いほうにクイックソートを適用する\n if (pivot - left &lt; right - pivot) {\n this.quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソートする\n left = pivot + 1; // 残りの未ソート区間は [pivot + 1, right]\n } else {\n this.quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソートする\n right = pivot - 1; // 残りの未ソート区間は [left, pivot - 1]\n }\n }\n}\n</code></pre> quick_sort.dart<pre><code>/* クイックソート(再帰の深さの最適化) */\nvoid quickSort(List&lt;int&gt; nums, int left, int right) {\n // 部分配列の長さが 1 になったら終了する\n while (left &lt; right) {\n // 番兵を用いた分割操作\n int pivot = _partition(nums, left, right);\n // 2 つの部分配列のうち短いほうにクイックソートを適用する\n if (pivot - left &lt; right - pivot) {\n quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソートする\n left = pivot + 1; // 残りの未ソート区間は [pivot + 1, right]\n } else {\n quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソートする\n right = pivot - 1; // 残りの未ソート区間は [left, pivot - 1]\n }\n }\n}\n</code></pre> quick_sort.rs<pre><code>/* クイックソート(再帰の深さの最適化) */\npub fn quick_sort(mut left: i32, mut right: i32, nums: &amp;mut [i32]) {\n // 部分配列の長さが 1 になったら終了する\n while left &lt; right {\n // 番兵を用いた分割操作\n let pivot = Self::partition(nums, left as usize, right as usize) as i32;\n // 2 つの部分配列のうち短いほうにクイックソートを適用する\n if pivot - left &lt; right - pivot {\n Self::quick_sort(left, pivot - 1, nums); // 左部分配列を再帰的にソートする\n left = pivot + 1; // 残りの未ソート区間は [pivot + 1, right]\n } else {\n Self::quick_sort(pivot + 1, right, nums); // 右部分配列を再帰的にソートする\n right = pivot - 1; // 残りの未ソート区間は [left, pivot - 1]\n }\n }\n}\n</code></pre> quick_sort.c<pre><code>/* クイックソート(再帰の深さの最適化) */\nvoid quickSortTailCall(int nums[], int left, int right) {\n // 部分配列の長さが 1 になったら終了する\n while (left &lt; right) {\n // 番兵を用いた分割操作\n int pivot = partition(nums, left, right);\n // 2 つの部分配列のうち短いほうにクイックソートを適用する\n if (pivot - left &lt; right - pivot) {\n // 左部分配列を再帰的にソートする\n quickSortTailCall(nums, left, pivot - 1);\n // 残りの未ソート区間は [pivot + 1, right]\n left = pivot + 1;\n } else {\n // 右部分配列を再帰的にソートする\n quickSortTailCall(nums, pivot + 1, right);\n // 残りの未ソート区間は [left, pivot - 1]\n right = pivot - 1;\n }\n }\n}\n</code></pre> quick_sort.kt<pre><code>/* クイックソート(再帰の深さの最適化) */\nfun quickSortTailCall(nums: IntArray, left: Int, right: Int) {\n // 部分配列の長さが 1 になったら終了する\n var l = left\n var r = right\n while (l &lt; r) {\n // 番兵を用いた分割操作\n val pivot = partition(nums, l, r)\n // 2 つの部分配列のうち短いほうにクイックソートを適用する\n if (pivot - l &lt; r - pivot) {\n quickSort(nums, l, pivot - 1) // 左部分配列を再帰的にソートする\n l = pivot + 1 // 残りの未ソート区間は [pivot + 1, right]\n } else {\n quickSort(nums, pivot + 1, r) // 右部分配列を再帰的にソートする\n r = pivot - 1 // 残りの未ソート区間は [left, pivot - 1]\n }\n }\n}\n</code></pre> quick_sort.rb<pre><code>### クイックソート(再帰の深さの最適化)###\ndef quick_sort(nums, left, right)\n # 部分配列の長さが 1 でない場合、再帰する\n while left &lt; right\n # 番兵を用いた分割\n pivot = partition(nums, left, right)\n # 2 つの部分配列のうち短いほうにクイックソートを適用する\n if pivot - left &lt; right - pivot\n quick_sort(nums, left, pivot - 1)\n left = pivot + 1 # 残りの未ソート区間は [pivot + 1, right]\n else\n quick_sort(nums, pivot + 1, right)\n right = pivot - 1 # 残りの未ソート区間は [left, pivot - 1]\n end\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.5   クイックソート"],"tags":[]},{"location":"chapter_sorting/radix_sort/","level":1,"title":"11.10   基数ソート","text":"<p>前節で紹介したカウントソートは、データ量 \\(n\\) が多く、値の範囲 \\(m\\) が狭い場合に適しています。\\(n = 10^6\\) 個の学籍番号をソートし、各番号が \\(8\\) 桁だとすると、値の範囲は \\(m = 10^8\\) と非常に大きく、カウントソートでは大量のメモリが必要です。基数ソートなら、この状況を避けられます。</p> <p>基数ソート(radix sort)の中心的な考え方はカウントソートと同じで、個数を数えてソートします。さらに、数の各桁の関係を利用して一桁ずつ順にソートし、最終的な結果を得ます。</p>","path":["第 11 章   ソート","11.10   基数ソート"],"tags":[]},{"location":"chapter_sorting/radix_sort/#11101","level":2,"title":"11.10.1   アルゴリズムの流れ","text":"<p>学籍番号を例に、最下位を第 \\(1\\) 桁、最上位を第 \\(8\\) 桁とすると、基数ソートの流れは図 11-18 のとおりです。</p> <ol> <li>桁の位置を \\(k = 1\\) に初期化します。</li> <li>学籍番号の第 \\(k\\) 桁についてカウントソートします。これで、第 \\(k\\) 桁の昇順にデータが並びます。</li> <li>\\(k\\) を \\(1\\) 増やして手順 <code>2.</code> に戻り、すべての桁をソートするまで繰り返します。</li> </ol> <p></p> <p> 図 11-18   基数ソートの流れ </p> <p>実装を詳しく見ていきます。\\(d\\) 進数 \\(x\\) の第 \\(k\\) 桁 \\(x_k\\) は、次の式で求められます。</p> \\[ x_k = \\lfloor\\frac{x}{d^{k-1}}\\rfloor \\bmod d \\] <p>\\(\\lfloor a \\rfloor\\) は浮動小数点数 \\(a\\) の切り捨て、\\(\\bmod \\: d\\) は \\(d\\) を法とする剰余を表します。学籍番号では \\(d = 10\\)、\\(k \\in [1, 8]\\) です。</p> <p>また、数値の第 \\(k\\) 桁に基づいてソートできるよう、カウントソートのコードを少し変更する必要があります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby radix_sort.py<pre><code>def digit(num: int, exp: int) -&gt; int:\n \"\"\"要素 num の第 k 桁を取得する。exp = 10^(k-1) とする\"\"\"\n # k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n return (num // exp) % 10\n\ndef counting_sort_digit(nums: list[int], exp: int):\n \"\"\"カウントソート(nums の第 k 桁に基づくソート)\"\"\"\n # 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n counter = [0] * 10\n n = len(nums)\n # 0~9 の各数字の出現回数を数える\n for i in range(n):\n d = digit(nums[i], exp) # nums[i] の第 k 桁を取得し、d とする\n counter[d] += 1 # 数字 d の出現回数を数える\n # 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for i in range(1, 10):\n counter[i] += counter[i - 1]\n # 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n res = [0] * n\n for i in range(n - 1, -1, -1):\n d = digit(nums[i], exp)\n j = counter[d] - 1 # 配列内での d のインデックス j を取得する\n res[j] = nums[i] # 現在の要素をインデックス j に入れる\n counter[d] -= 1 # d の個数を 1 減らす\n # 結果で元の配列 nums を上書きする\n for i in range(n):\n nums[i] = res[i]\n\ndef radix_sort(nums: list[int]):\n \"\"\"基数ソート\"\"\"\n # 最大桁数を判定するため、配列の最大要素を取得する\n m = max(nums)\n # 下位の桁から上位の桁へ順に走査する\n exp = 1\n while exp &lt;= m:\n # 配列の要素の第 k 桁に対してカウントソートを行う\n # k = 1 -&gt; exp = 1\n # k = 2 -&gt; exp = 10\n # つまり exp = 10^(k-1)\n counting_sort_digit(nums, exp)\n exp *= 10\n</code></pre> radix_sort.cpp<pre><code>/* 要素 num の第 k 桁を取得する。exp = 10^(k-1) とする */\nint digit(int num, int exp) {\n // k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n return (num / exp) % 10;\n}\n\n/* カウントソート(nums の第 k 桁に基づくソート) */\nvoid countingSortDigit(vector&lt;int&gt; &amp;nums, int exp) {\n // 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n vector&lt;int&gt; counter(10, 0);\n int n = nums.size();\n // 0~9 の各数字の出現回数を数える\n for (int i = 0; i &lt; n; i++) {\n int d = digit(nums[i], exp); // nums[i] の第 k 桁を取得し、d とする\n counter[d]++; // 数字 d の出現回数を数える\n }\n // 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for (int i = 1; i &lt; 10; i++) {\n counter[i] += counter[i - 1];\n }\n // 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n vector&lt;int&gt; res(n, 0);\n for (int i = n - 1; i &gt;= 0; i--) {\n int d = digit(nums[i], exp);\n int j = counter[d] - 1; // 配列内での d のインデックス j を取得する\n res[j] = nums[i]; // 現在の要素をインデックス j に入れる\n counter[d]--; // d の個数を 1 減らす\n }\n // 結果で元の配列 nums を上書きする\n for (int i = 0; i &lt; n; i++)\n nums[i] = res[i];\n}\n\n/* 基数ソート */\nvoid radixSort(vector&lt;int&gt; &amp;nums) {\n // 最大桁数を判定するため、配列の最大要素を取得する\n int m = *max_element(nums.begin(), nums.end());\n // 下位の桁から上位の桁へ順に走査する\n for (int exp = 1; exp &lt;= m; exp *= 10)\n // 配列の要素の第 k 桁に対してカウントソートを行う\n // k = 1 -&gt; exp = 1\n // k = 2 -&gt; exp = 10\n // つまり exp = 10^(k-1)\n countingSortDigit(nums, exp);\n}\n</code></pre> radix_sort.java<pre><code>/* 要素 num の第 k 桁を取得する。exp = 10^(k-1) とする */\nint digit(int num, int exp) {\n // k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n return (num / exp) % 10;\n}\n\n/* カウントソート(nums の第 k 桁に基づくソート) */\nvoid countingSortDigit(int[] nums, int exp) {\n // 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n int[] counter = new int[10];\n int n = nums.length;\n // 0~9 の各数字の出現回数を数える\n for (int i = 0; i &lt; n; i++) {\n int d = digit(nums[i], exp); // nums[i] の第 k 桁を取得し、d とする\n counter[d]++; // 数字 d の出現回数を数える\n }\n // 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for (int i = 1; i &lt; 10; i++) {\n counter[i] += counter[i - 1];\n }\n // 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n int[] res = new int[n];\n for (int i = n - 1; i &gt;= 0; i--) {\n int d = digit(nums[i], exp);\n int j = counter[d] - 1; // 配列内での d のインデックス j を取得する\n res[j] = nums[i]; // 現在の要素をインデックス j に入れる\n counter[d]--; // d の個数を 1 減らす\n }\n // 結果で元の配列 nums を上書きする\n for (int i = 0; i &lt; n; i++)\n nums[i] = res[i];\n}\n\n/* 基数ソート */\nvoid radixSort(int[] nums) {\n // 最大桁数を判定するため、配列の最大要素を取得する\n int m = Integer.MIN_VALUE;\n for (int num : nums)\n if (num &gt; m)\n m = num;\n // 下位の桁から上位の桁へ順に走査する\n for (int exp = 1; exp &lt;= m; exp *= 10) {\n // 配列の要素の第 k 桁に対してカウントソートを行う\n // k = 1 -&gt; exp = 1\n // k = 2 -&gt; exp = 10\n // つまり exp = 10^(k-1)\n countingSortDigit(nums, exp);\n }\n}\n</code></pre> radix_sort.cs<pre><code>/* 要素 num の第 k 桁を取得する。exp = 10^(k-1) とする */\nint Digit(int num, int exp) {\n // k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n return (num / exp) % 10;\n}\n\n/* カウントソート(nums の第 k 桁に基づくソート) */\nvoid CountingSortDigit(int[] nums, int exp) {\n // 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n int[] counter = new int[10];\n int n = nums.Length;\n // 0~9 の各数字の出現回数を数える\n for (int i = 0; i &lt; n; i++) {\n int d = Digit(nums[i], exp); // nums[i] の第 k 桁を取得し、d とする\n counter[d]++; // 数字 d の出現回数を数える\n }\n // 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for (int i = 1; i &lt; 10; i++) {\n counter[i] += counter[i - 1];\n }\n // 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n int[] res = new int[n];\n for (int i = n - 1; i &gt;= 0; i--) {\n int d = Digit(nums[i], exp);\n int j = counter[d] - 1; // 配列内での d のインデックス j を取得する\n res[j] = nums[i]; // 現在の要素をインデックス j に入れる\n counter[d]--; // d の個数を 1 減らす\n }\n // 結果で元の配列 nums を上書きする\n for (int i = 0; i &lt; n; i++) {\n nums[i] = res[i];\n }\n}\n\n/* 基数ソート */\nvoid RadixSort(int[] nums) {\n // 最大桁数を判定するため、配列の最大要素を取得する\n int m = int.MinValue;\n foreach (int num in nums) {\n if (num &gt; m) m = num;\n }\n // 下位の桁から上位の桁へ順に走査する\n for (int exp = 1; exp &lt;= m; exp *= 10) {\n // 配列の要素の第 k 桁に対してカウントソートを行う\n // k = 1 -&gt; exp = 1\n // k = 2 -&gt; exp = 10\n // つまり exp = 10^(k-1)\n CountingSortDigit(nums, exp);\n }\n}\n</code></pre> radix_sort.go<pre><code>/* 要素 num の第 k 桁を取得する。exp = 10^(k-1) とする */\nfunc digit(num, exp int) int {\n // k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n return (num / exp) % 10\n}\n\n/* カウントソート(nums の第 k 桁に基づくソート) */\nfunc countingSortDigit(nums []int, exp int) {\n // 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n counter := make([]int, 10)\n n := len(nums)\n // 0~9 の各数字の出現回数を数える\n for i := 0; i &lt; n; i++ {\n d := digit(nums[i], exp) // nums[i] の第 k 桁を取得し、d とする\n counter[d]++ // 数字 d の出現回数を数える\n }\n // 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for i := 1; i &lt; 10; i++ {\n counter[i] += counter[i-1]\n }\n // 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n res := make([]int, n)\n for i := n - 1; i &gt;= 0; i-- {\n d := digit(nums[i], exp)\n j := counter[d] - 1 // 配列内での d のインデックス j を取得する\n res[j] = nums[i] // 現在の要素をインデックス j に入れる\n counter[d]-- // d の個数を 1 減らす\n }\n // 結果で元の配列 nums を上書きする\n for i := 0; i &lt; n; i++ {\n nums[i] = res[i]\n }\n}\n\n/* 基数ソート */\nfunc radixSort(nums []int) {\n // 最大桁数を判定するため、配列の最大要素を取得する\n max := math.MinInt\n for _, num := range nums {\n if num &gt; max {\n max = num\n }\n }\n // 下位の桁から上位の桁へ順に走査する\n for exp := 1; max &gt;= exp; exp *= 10 {\n // 配列の要素の第 k 桁に対してカウントソートを行う\n // k = 1 -&gt; exp = 1\n // k = 2 -&gt; exp = 10\n // つまり exp = 10^(k-1)\n countingSortDigit(nums, exp)\n }\n}\n</code></pre> radix_sort.swift<pre><code>/* 要素 num の第 k 桁を取得する。exp = 10^(k-1) とする */\nfunc digit(num: Int, exp: Int) -&gt; Int {\n // k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n (num / exp) % 10\n}\n\n/* カウントソート(nums の第 k 桁に基づくソート) */\nfunc countingSortDigit(nums: inout [Int], exp: Int) {\n // 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n var counter = Array(repeating: 0, count: 10)\n // 0~9 の各数字の出現回数を数える\n for i in nums.indices {\n let d = digit(num: nums[i], exp: exp) // nums[i] の第 k 桁を取得し、d とする\n counter[d] += 1 // 数字 d の出現回数を数える\n }\n // 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for i in 1 ..&lt; 10 {\n counter[i] += counter[i - 1]\n }\n // 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n var res = Array(repeating: 0, count: nums.count)\n for i in nums.indices.reversed() {\n let d = digit(num: nums[i], exp: exp)\n let j = counter[d] - 1 // 配列内での d のインデックス j を取得する\n res[j] = nums[i] // 現在の要素をインデックス j に入れる\n counter[d] -= 1 // d の個数を 1 減らす\n }\n // 結果で元の配列 nums を上書きする\n for i in nums.indices {\n nums[i] = res[i]\n }\n}\n\n/* 基数ソート */\nfunc radixSort(nums: inout [Int]) {\n // 最大桁数を判定するため、配列の最大要素を取得する\n var m = Int.min\n for num in nums {\n if num &gt; m {\n m = num\n }\n }\n // 下位の桁から上位の桁へ順に走査する\n for exp in sequence(first: 1, next: { m &gt;= ($0 * 10) ? $0 * 10 : nil }) {\n // 配列の要素の第 k 桁に対してカウントソートを行う\n // k = 1 -&gt; exp = 1\n // k = 2 -&gt; exp = 10\n // つまり exp = 10^(k-1)\n countingSortDigit(nums: &amp;nums, exp: exp)\n }\n}\n</code></pre> radix_sort.js<pre><code>/* 要素 num の第 k 桁を取得する。exp = 10^(k-1) とする */\nfunction digit(num, exp) {\n // k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n return Math.floor(num / exp) % 10;\n}\n\n/* カウントソート(nums の第 k 桁に基づくソート) */\nfunction countingSortDigit(nums, exp) {\n // 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n const counter = new Array(10).fill(0);\n const n = nums.length;\n // 0~9 の各数字の出現回数を数える\n for (let i = 0; i &lt; n; i++) {\n const d = digit(nums[i], exp); // nums[i] の第 k 桁を取得し、d とする\n counter[d]++; // 数字 d の出現回数を数える\n }\n // 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for (let i = 1; i &lt; 10; i++) {\n counter[i] += counter[i - 1];\n }\n // 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n const res = new Array(n).fill(0);\n for (let i = n - 1; i &gt;= 0; i--) {\n const d = digit(nums[i], exp);\n const j = counter[d] - 1; // 配列内での d のインデックス j を取得する\n res[j] = nums[i]; // 現在の要素をインデックス j に入れる\n counter[d]--; // d の個数を 1 減らす\n }\n // 結果で元の配列 nums を上書きする\n for (let i = 0; i &lt; n; i++) {\n nums[i] = res[i];\n }\n}\n\n/* 基数ソート */\nfunction radixSort(nums) {\n // 最大桁数を判定するため、配列の最大要素を取得する\n let m = Math.max(... nums);\n // 下位の桁から上位の桁へ順に走査する\n for (let exp = 1; exp &lt;= m; exp *= 10) {\n // 配列の要素の第 k 桁に対してカウントソートを行う\n // k = 1 -&gt; exp = 1\n // k = 2 -&gt; exp = 10\n // つまり exp = 10^(k-1)\n countingSortDigit(nums, exp);\n }\n}\n</code></pre> radix_sort.ts<pre><code>/* 要素 num の第 k 桁を取得する。exp = 10^(k-1) とする */\nfunction digit(num: number, exp: number): number {\n // k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n return Math.floor(num / exp) % 10;\n}\n\n/* カウントソート(nums の第 k 桁に基づくソート) */\nfunction countingSortDigit(nums: number[], exp: number): void {\n // 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n const counter = new Array(10).fill(0);\n const n = nums.length;\n // 0~9 の各数字の出現回数を数える\n for (let i = 0; i &lt; n; i++) {\n const d = digit(nums[i], exp); // nums[i] の第 k 桁を取得し、d とする\n counter[d]++; // 数字 d の出現回数を数える\n }\n // 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for (let i = 1; i &lt; 10; i++) {\n counter[i] += counter[i - 1];\n }\n // 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n const res = new Array(n).fill(0);\n for (let i = n - 1; i &gt;= 0; i--) {\n const d = digit(nums[i], exp);\n const j = counter[d] - 1; // 配列内での d のインデックス j を取得する\n res[j] = nums[i]; // 現在の要素をインデックス j に入れる\n counter[d]--; // d の個数を 1 減らす\n }\n // 結果で元の配列 nums を上書きする\n for (let i = 0; i &lt; n; i++) {\n nums[i] = res[i];\n }\n}\n\n/* 基数ソート */\nfunction radixSort(nums: number[]): void {\n // 最大桁数を判定するため、配列の最大要素を取得する\n let m: number = Math.max(... nums);\n // 下位の桁から上位の桁へ順に走査する\n for (let exp = 1; exp &lt;= m; exp *= 10) {\n // 配列の要素の第 k 桁に対してカウントソートを行う\n // k = 1 -&gt; exp = 1\n // k = 2 -&gt; exp = 10\n // つまり exp = 10^(k-1)\n countingSortDigit(nums, exp);\n }\n}\n</code></pre> radix_sort.dart<pre><code>/* 要素 _num の第 k 桁を取得する。exp = 10^(k-1) とする */\nint digit(int _num, int exp) {\n // k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n return (_num ~/ exp) % 10;\n}\n\n/* カウントソート(nums の第 k 桁に基づくソート) */\nvoid countingSortDigit(List&lt;int&gt; nums, int exp) {\n // 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n List&lt;int&gt; counter = List&lt;int&gt;.filled(10, 0);\n int n = nums.length;\n // 0~9 の各数字の出現回数を数える\n for (int i = 0; i &lt; n; i++) {\n int d = digit(nums[i], exp); // nums[i] の第 k 桁を取得し、d とする\n counter[d]++; // 数字 d の出現回数を数える\n }\n // 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for (int i = 1; i &lt; 10; i++) {\n counter[i] += counter[i - 1];\n }\n // 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n List&lt;int&gt; res = List&lt;int&gt;.filled(n, 0);\n for (int i = n - 1; i &gt;= 0; i--) {\n int d = digit(nums[i], exp);\n int j = counter[d] - 1; // 配列内での d のインデックス j を取得する\n res[j] = nums[i]; // 現在の要素をインデックス j に入れる\n counter[d]--; // d の個数を 1 減らす\n }\n // 結果で元の配列 nums を上書きする\n for (int i = 0; i &lt; n; i++) nums[i] = res[i];\n}\n\n/* 基数ソート */\nvoid radixSort(List&lt;int&gt; nums) {\n // 最大桁数を判定するため、配列の最大要素を取得する\n // Dart の int の長さは 64 ビット\n int m = -1 &lt;&lt; 63;\n for (int _num in nums) if (_num &gt; m) m = _num;\n // 下位の桁から上位の桁へ順に走査する\n for (int exp = 1; exp &lt;= m; exp *= 10)\n // 配列の要素の第 k 桁に対してカウントソートを行う\n // k = 1 -&gt; exp = 1\n // k = 2 -&gt; exp = 10\n // つまり exp = 10^(k-1)\n countingSortDigit(nums, exp);\n}\n</code></pre> radix_sort.rs<pre><code>/* 要素 num の第 k 桁を取得する。exp = 10^(k-1) とする */\nfn digit(num: i32, exp: i32) -&gt; usize {\n // k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n return ((num / exp) % 10) as usize;\n}\n\n/* カウントソート(nums の第 k 桁に基づくソート) */\nfn counting_sort_digit(nums: &amp;mut [i32], exp: i32) {\n // 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n let mut counter = [0; 10];\n let n = nums.len();\n // 0~9 の各数字の出現回数を数える\n for i in 0..n {\n let d = digit(nums[i], exp); // nums[i] の第 k 桁を取得し、d とする\n counter[d] += 1; // 数字 d の出現回数を数える\n }\n // 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for i in 1..10 {\n counter[i] += counter[i - 1];\n }\n // 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n let mut res = vec![0; n];\n for i in (0..n).rev() {\n let d = digit(nums[i], exp);\n let j = counter[d] - 1; // 配列内での d のインデックス j を取得する\n res[j] = nums[i]; // 現在の要素をインデックス j に入れる\n counter[d] -= 1; // d の個数を 1 減らす\n }\n // 結果で元の配列 nums を上書きする\n nums.copy_from_slice(&amp;res);\n}\n\n/* 基数ソート */\nfn radix_sort(nums: &amp;mut [i32]) {\n // 最大桁数を判定するため、配列の最大要素を取得する\n let m = *nums.into_iter().max().unwrap();\n // 下位の桁から上位の桁へ順に走査する\n let mut exp = 1;\n while exp &lt;= m {\n counting_sort_digit(nums, exp);\n exp *= 10;\n }\n}\n</code></pre> radix_sort.c<pre><code>/* 要素 num の第 k 桁を取得する。exp = 10^(k-1) とする */\nint digit(int num, int exp) {\n // k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n return (num / exp) % 10;\n}\n\n/* カウントソート(nums の第 k 桁に基づくソート) */\nvoid countingSortDigit(int nums[], int size, int exp) {\n // 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n int *counter = (int *)malloc((sizeof(int) * 10));\n memset(counter, 0, sizeof(int) * 10); // 後でメモリを解放できるように 0 で初期化する\n // 0~9 の各数字の出現回数を数える\n for (int i = 0; i &lt; size; i++) {\n // nums[i] の第 k 桁を取得し、d とする\n int d = digit(nums[i], exp);\n // 数字 d の出現回数を数える\n counter[d]++;\n }\n // 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for (int i = 1; i &lt; 10; i++) {\n counter[i] += counter[i - 1];\n }\n // 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n int *res = (int *)malloc(sizeof(int) * size);\n for (int i = size - 1; i &gt;= 0; i--) {\n int d = digit(nums[i], exp);\n int j = counter[d] - 1; // 配列内での d のインデックス j を取得する\n res[j] = nums[i]; // 現在の要素をインデックス j に入れる\n counter[d]--; // d の個数を 1 減らす\n }\n // 結果で元の配列 nums を上書きする\n for (int i = 0; i &lt; size; i++) {\n nums[i] = res[i];\n }\n // メモリを解放する\n free(res);\n free(counter);\n}\n\n/* 基数ソート */\nvoid radixSort(int nums[], int size) {\n // 最大桁数を判定するため、配列の最大要素を取得する\n int max = INT32_MIN;\n for (int i = 0; i &lt; size; i++) {\n if (nums[i] &gt; max) {\n max = nums[i];\n }\n }\n // 下位の桁から上位の桁へ順に走査する\n for (int exp = 1; max &gt;= exp; exp *= 10)\n // 配列の要素の第 k 桁に対してカウントソートを行う\n // k = 1 -&gt; exp = 1\n // k = 2 -&gt; exp = 10\n // つまり exp = 10^(k-1)\n countingSortDigit(nums, size, exp);\n}\n</code></pre> radix_sort.kt<pre><code>/* 要素 num の第 k 桁を取得する。exp = 10^(k-1) とする */\nfun digit(num: Int, exp: Int): Int {\n // k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n return (num / exp) % 10\n}\n\n/* カウントソート(nums の第 k 桁に基づくソート) */\nfun countingSortDigit(nums: IntArray, exp: Int) {\n // 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n val counter = IntArray(10)\n val n = nums.size\n // 0~9 の各数字の出現回数を数える\n for (i in 0..&lt;n) {\n val d = digit(nums[i], exp) // nums[i] の第 k 桁を取得し、d とする\n counter[d]++ // 数字 d の出現回数を数える\n }\n // 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n for (i in 1..9) {\n counter[i] += counter[i - 1]\n }\n // 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n val res = IntArray(n)\n for (i in n - 1 downTo 0) {\n val d = digit(nums[i], exp)\n val j = counter[d] - 1 // 配列内での d のインデックス j を取得する\n res[j] = nums[i] // 現在の要素をインデックス j に入れる\n counter[d]-- // d の個数を 1 減らす\n }\n // 結果で元の配列 nums を上書きする\n for (i in 0..&lt;n)\n nums[i] = res[i]\n}\n\n/* 基数ソート */\nfun radixSort(nums: IntArray) {\n // 最大桁数を判定するため、配列の最大要素を取得する\n var m = Int.MIN_VALUE\n for (num in nums) if (num &gt; m) m = num\n var exp = 1\n // 下位の桁から上位の桁へ順に走査する\n while (exp &lt;= m) {\n // 配列の要素の第 k 桁に対してカウントソートを行う\n // k = 1 -&gt; exp = 1\n // k = 2 -&gt; exp = 10\n // つまり exp = 10^(k-1)\n countingSortDigit(nums, exp)\n exp *= 10\n }\n}\n</code></pre> radix_sort.rb<pre><code>### 要素 num の第 k 桁を取得する。exp = 10^(k-1) とする ###\ndef digit(num, exp)\n # k ではなく exp を渡すことで、ここで高コストのべき乗計算を繰り返すのを避ける\n (num / exp) % 10\nend\n\n### カウントソート(nums の第 k 桁に基づくソート)###\ndef counting_sort_digit(nums, exp)\n # 十進数の各桁の範囲は 0~9 なので、長さ 10 のバケット配列が必要\n counter = Array.new(10, 0)\n n = nums.length\n # 0~9 の各数字の出現回数を数える\n for i in 0...n\n d = digit(nums[i], exp) # nums[i] の第 k 桁を取得し、d とする\n counter[d] += 1 # 数字 d の出現回数を数える\n end\n # 累積和を求め、「出現回数」を「配列のインデックス」に変換する\n (1...10).each { |i| counter[i] += counter[i - 1] }\n # 逆順に走査し、バケット内の集計結果に基づいて各要素を res に入れる\n res = Array.new(n, 0)\n for i in (n - 1).downto(0)\n d = digit(nums[i], exp)\n j = counter[d] - 1 # 配列内での d のインデックス j を取得する\n res[j] = nums[i] # 現在の要素をインデックス j に入れる\n counter[d] -= 1 # d の個数を 1 減らす\n end\n # 結果で元の配列 nums を上書きする\n (0...n).each { |i| nums[i] = res[i] }\nend\n\n### 基数ソート ###\ndef radix_sort(nums)\n # 最大桁数を判定するため、配列の最大要素を取得する\n m = nums.max\n # 下位の桁から上位の桁へ順に走査する\n exp = 1\n while exp &lt;= m\n # 配列の要素の第 k 桁に対してカウントソートを行う\n # k = 1 -&gt; exp = 1\n # k = 2 -&gt; exp = 10\n # つまり exp = 10^(k-1)\n counting_sort_digit(nums, exp)\n exp *= 10\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>なぜ最下位の桁からソートするのか?</p> <p>ソートを繰り返すと、後の回の結果が前の回の結果に優先します。例えば、1 回目の結果が \\(a &lt; b\\) で、2 回目が \\(a &gt; b\\) なら、1 回目の結果が 2 回目の結果に置き換わります。数では上位の桁が下位の桁より優先されるため、下位の桁を先に、上位の桁を後にソートします。</p>","path":["第 11 章   ソート","11.10   基数ソート"],"tags":[]},{"location":"chapter_sorting/radix_sort/#11102","level":2,"title":"11.10.2   アルゴリズムの特徴","text":"<p>カウントソートと比べ、基数ソートは値の範囲が広い場合に適しています。ただし、データを固定桁数の形式で表せて、桁数が大きすぎないことが前提です。例えば、浮動小数点数は桁数 \\(k\\) が大きすぎて時間計算量が \\(O(nk) \\gg O(n^2)\\) となる可能性があり、基数ソートには適していません。</p> <ul> <li>時間計算量は \\(O(nk)\\)、非適応的ソート:データ量を \\(n\\)、基数を \\(d\\)、最大桁数を \\(k\\) とすると、一桁のカウントソートには \\(O(n + d)\\) 時間、全 \\(k\\) 桁には \\(O((n + d)k)\\) 時間かかります。通常、\\(d\\) と \\(k\\) は比較的小さいので、時間計算量は \\(O(n)\\) に近づきます。</li> <li>空間計算量は \\(O(n + d)\\)、非インプレースソート:カウントソートと同様に、長さ \\(n\\) と \\(d\\) の配列 <code>res</code> と <code>counter</code> を使います。</li> <li>安定ソート:カウントソートが安定なら、基数ソートも安定です。カウントソートが不安定な場合、基数ソートで正しい結果を得られる保証はありません。</li> </ul>","path":["第 11 章   ソート","11.10   基数ソート"],"tags":[]},{"location":"chapter_sorting/selection_sort/","level":1,"title":"11.2   選択ソート","text":"<p>選択ソート(selection sort)の仕組みはとても簡単です。ループの各反復で未整列の区間から最小の要素を選び、整列済み区間の末尾に置きます。</p> <p>配列の長さを \\(n\\) とすると、選択ソートの流れは図 11-2 のとおりです。</p> <ol> <li>初期状態ではすべての要素が未整列で、未整列のインデックス区間は \\([0, n-1]\\) です。</li> <li>区間 \\([0, n-1]\\) の最小要素を選び、インデックス \\(0\\) の要素と交換します。これで先頭の 1 要素が整列済みになります。</li> <li>区間 \\([1, n-1]\\) の最小要素を選び、インデックス \\(1\\) の要素と交換します。これで先頭の 2 要素が整列済みになります。</li> <li>同様に、選択と交換を \\(n - 1\\) 回繰り返すと、先頭の \\(n - 1\\) 要素が整列済みになります。</li> <li>残った一つの要素は必ず最大要素なので、ソートは不要です。これで配列全体のソートが完了します。</li> </ol> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt;&lt;11&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 11-2   選択ソートの手順 </p> <p>コードでは、未整列区間の最小要素を記録するために \\(k\\) を使います。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby selection_sort.py<pre><code>def selection_sort(nums: list[int]):\n \"\"\"選択ソート\"\"\"\n n = len(nums)\n # 外側のループ:未ソート区間は [i, n-1]\n for i in range(n - 1):\n # 内側のループ:未ソート区間の最小要素を探す\n k = i\n for j in range(i + 1, n):\n if nums[j] &lt; nums[k]:\n k = j # 最小要素のインデックスを記録する\n # この最小要素を未ソート区間の先頭要素と交換する\n nums[i], nums[k] = nums[k], nums[i]\n</code></pre> selection_sort.cpp<pre><code>/* 選択ソート */\nvoid selectionSort(vector&lt;int&gt; &amp;nums) {\n int n = nums.size();\n // 外側のループ:未ソート区間は [i, n-1]\n for (int i = 0; i &lt; n - 1; i++) {\n // 内側のループ:未ソート区間の最小要素を探す\n int k = i;\n for (int j = i + 1; j &lt; n; j++) {\n if (nums[j] &lt; nums[k])\n k = j; // 最小要素のインデックスを記録する\n }\n // この最小要素を未ソート区間の先頭要素と交換する\n swap(nums[i], nums[k]);\n }\n}\n</code></pre> selection_sort.java<pre><code>/* 選択ソート */\nvoid selectionSort(int[] nums) {\n int n = nums.length;\n // 外側のループ:未ソート区間は [i, n-1]\n for (int i = 0; i &lt; n - 1; i++) {\n // 内側のループ:未ソート区間の最小要素を探す\n int k = i;\n for (int j = i + 1; j &lt; n; j++) {\n if (nums[j] &lt; nums[k])\n k = j; // 最小要素のインデックスを記録する\n }\n // この最小要素を未ソート区間の先頭要素と交換する\n int temp = nums[i];\n nums[i] = nums[k];\n nums[k] = temp;\n }\n}\n</code></pre> selection_sort.cs<pre><code>/* 選択ソート */\nvoid SelectionSort(int[] nums) {\n int n = nums.Length;\n // 外側のループ:未ソート区間は [i, n-1]\n for (int i = 0; i &lt; n - 1; i++) {\n // 内側のループ:未ソート区間の最小要素を探す\n int k = i;\n for (int j = i + 1; j &lt; n; j++) {\n if (nums[j] &lt; nums[k])\n k = j; // 最小要素のインデックスを記録する\n }\n // この最小要素を未ソート区間の先頭要素と交換する\n (nums[k], nums[i]) = (nums[i], nums[k]);\n }\n}\n</code></pre> selection_sort.go<pre><code>/* 選択ソート */\nfunc selectionSort(nums []int) {\n n := len(nums)\n // 外側のループ:未ソート区間は [i, n-1]\n for i := 0; i &lt; n-1; i++ {\n // 内側のループ:未ソート区間の最小要素を探す\n k := i\n for j := i + 1; j &lt; n; j++ {\n if nums[j] &lt; nums[k] {\n // 最小要素のインデックスを記録する\n k = j\n }\n }\n // この最小要素を未ソート区間の先頭要素と交換する\n nums[i], nums[k] = nums[k], nums[i]\n\n }\n}\n</code></pre> selection_sort.swift<pre><code>/* 選択ソート */\nfunc selectionSort(nums: inout [Int]) {\n // 外側のループ:未ソート区間は [i, n-1]\n for i in nums.indices.dropLast() {\n // 内側のループ:未ソート区間の最小要素を探す\n var k = i\n for j in nums.indices.dropFirst(i + 1) {\n if nums[j] &lt; nums[k] {\n k = j // 最小要素のインデックスを記録する\n }\n }\n // この最小要素を未ソート区間の先頭要素と交換する\n nums.swapAt(i, k)\n }\n}\n</code></pre> selection_sort.js<pre><code>/* 選択ソート */\nfunction selectionSort(nums) {\n let n = nums.length;\n // 外側のループ:未ソート区間は [i, n-1]\n for (let i = 0; i &lt; n - 1; i++) {\n // 内側のループ:未ソート区間の最小要素を探す\n let k = i;\n for (let j = i + 1; j &lt; n; j++) {\n if (nums[j] &lt; nums[k]) {\n k = j; // 最小要素のインデックスを記録する\n }\n }\n // この最小要素を未ソート区間の先頭要素と交換する\n [nums[i], nums[k]] = [nums[k], nums[i]];\n }\n}\n</code></pre> selection_sort.ts<pre><code>/* 選択ソート */\nfunction selectionSort(nums: number[]): void {\n let n = nums.length;\n // 外側のループ:未ソート区間は [i, n-1]\n for (let i = 0; i &lt; n - 1; i++) {\n // 内側のループ:未ソート区間の最小要素を探す\n let k = i;\n for (let j = i + 1; j &lt; n; j++) {\n if (nums[j] &lt; nums[k]) {\n k = j; // 最小要素のインデックスを記録する\n }\n }\n // この最小要素を未ソート区間の先頭要素と交換する\n [nums[i], nums[k]] = [nums[k], nums[i]];\n }\n}\n</code></pre> selection_sort.dart<pre><code>/* 選択ソート */\nvoid selectionSort(List&lt;int&gt; nums) {\n int n = nums.length;\n // 外側のループ:未ソート区間は [i, n-1]\n for (int i = 0; i &lt; n - 1; i++) {\n // 内側のループ:未ソート区間の最小要素を探す\n int k = i;\n for (int j = i + 1; j &lt; n; j++) {\n if (nums[j] &lt; nums[k]) k = j; // 最小要素のインデックスを記録する\n }\n // この最小要素を未ソート区間の先頭要素と交換する\n int temp = nums[i];\n nums[i] = nums[k];\n nums[k] = temp;\n }\n}\n</code></pre> selection_sort.rs<pre><code>/* 選択ソート */\nfn selection_sort(nums: &amp;mut [i32]) {\n if nums.is_empty() {\n return;\n }\n let n = nums.len();\n // 外側のループ:未ソート区間は [i, n-1]\n for i in 0..n - 1 {\n // 内側のループ:未ソート区間の最小要素を探す\n let mut k = i;\n for j in i + 1..n {\n if nums[j] &lt; nums[k] {\n k = j; // 最小要素のインデックスを記録する\n }\n }\n // この最小要素を未ソート区間の先頭要素と交換する\n nums.swap(i, k);\n }\n}\n</code></pre> selection_sort.c<pre><code>/* 選択ソート */\nvoid selectionSort(int nums[], int n) {\n // 外側のループ:未ソート区間は [i, n-1]\n for (int i = 0; i &lt; n - 1; i++) {\n // 内側のループ:未ソート区間の最小要素を探す\n int k = i;\n for (int j = i + 1; j &lt; n; j++) {\n if (nums[j] &lt; nums[k])\n k = j; // 最小要素のインデックスを記録する\n }\n // この最小要素を未ソート区間の先頭要素と交換する\n int temp = nums[i];\n nums[i] = nums[k];\n nums[k] = temp;\n }\n}\n</code></pre> selection_sort.kt<pre><code>/* 選択ソート */\nfun selectionSort(nums: IntArray) {\n val n = nums.size\n // 外側のループ:未ソート区間は [i, n-1]\n for (i in 0..&lt;n - 1) {\n var k = i\n // 内側のループ:未ソート区間の最小要素を探す\n for (j in i + 1..&lt;n) {\n if (nums[j] &lt; nums[k])\n k = j // 最小要素のインデックスを記録する\n }\n // この最小要素を未ソート区間の先頭要素と交換する\n val temp = nums[i]\n nums[i] = nums[k]\n nums[k] = temp\n }\n}\n</code></pre> selection_sort.rb<pre><code>### 選択ソート ###\ndef selection_sort(nums)\n n = nums.length\n # 外側のループ:未ソート区間は [i, n-1]\n for i in 0...(n - 1)\n # 内側のループ:未ソート区間の最小要素を探す\n k = i\n for j in (i + 1)...n\n if nums[j] &lt; nums[k]\n k = j # 最小要素のインデックスを記録する\n end\n end\n # この最小要素を未ソート区間の先頭要素と交換する\n nums[i], nums[k] = nums[k], nums[i]\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 11 章   ソート","11.2   選択ソート"],"tags":[]},{"location":"chapter_sorting/selection_sort/#1121","level":2,"title":"11.2.1   アルゴリズムの特徴","text":"<ul> <li>時間計算量は \\(O(n^2)\\)、非適応的ソート:外側のループは \\(n - 1\\) 回です。内側のループは最初が \\(n - 1\\) 回、最後が \\(1\\) 回で、順に \\(n - 1\\)、\\(n - 2\\)、\\(\\dots\\)、\\(2\\)、\\(1\\) 回実行されます。その合計は \\(\\frac{n(n - 1)}{2}\\) です。</li> <li>空間計算量は \\(O(1)\\)、インプレースソート:ポインタ \\(i\\) と \\(j\\) は定数サイズの追加領域を使います。</li> <li>不安定ソート:図 11-3 のように、要素 <code>nums[i]</code> が等しい要素の右側に移され、両者の相対的な順序が変わる可能性があります。</li> </ul> <p> 図 11-3   選択ソートが不安定になる例 </p>","path":["第 11 章   ソート","11.2   選択ソート"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/","level":1,"title":"11.1   ソートアルゴリズム","text":"<p>ソートアルゴリズム(sorting algorithm)は、データの集合を特定の順序に並べるために使います。整列済みのデータは通常、より効率よく探索、分析、処理できるため、ソートには幅広い用途があります。</p> <p>図 11-1 のように、ソートするデータの型には整数、浮動小数点数、文字、文字列などがあります。大小の判定規則は、数値の大小、文字の ASCII コード順、独自の規則など、必要に応じて設定できます。</p> <p></p> <p> 図 11-1   データ型と判定規則の例 </p>","path":["第 11 章   ソート","11.1   ソートアルゴリズム"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/#1111","level":2,"title":"11.1.1   評価の観点","text":"<p>実行効率:時間計算量ができるだけ小さく、操作の総数も少ない(時間計算量の定数項が小さい)ことが望まれます。特にデータが多い場合、実行効率は重要です。</p> <p>インプレース性:インプレースソートは、その名のとおり元の配列を直接操作してソートします。追加の補助配列が不要なので、メモリを節約できます。通常はデータの移動も少なく、実行も速くなります。</p> <p>安定性:安定ソートでは、ソート後も等しい要素の配列内での相対的な順序が変わりません。</p> <p>安定ソートは、複数の基準で段階的にソートする場合に必要です。学生の情報を格納した表があり、第 1 列が名前、第 2 列が年齢だとします。この場合、不安定ソートでは、入力データがもともと持っていた順序が失われる可能性があります。</p> <pre><code># 入力データは名前順にソート済み\n# (name, age)\n ('A', 19)\n ('B', 18)\n ('C', 21)\n ('D', 19)\n ('E', 23)\n\n# 不安定なソートアルゴリズムで年齢順に並べると、\n# 結果では ('D', 19) と ('A', 19) の相対的な位置が変わり、\n# 入力データの名前順という性質が失われる\n ('B', 18)\n ('D', 19)\n ('A', 19)\n ('C', 21)\n ('E', 23)\n</code></pre> <p>適応性:適応的ソートは、入力データがすでに持つ順序の情報を利用して計算量を減らし、時間効率を高めます。最良時間計算量は通常、平均時間計算量より優れています。</p> <p>比較に基づくかどうか:比較ソートは、比較演算子(\\(&lt;\\)、\\(=\\)、\\(&gt;\\))で要素間の相対的な順序を判定し、配列全体をソートします。最悪時間計算量の下界は \\(\\Omega(n \\log n)\\) です。一方、非比較ソートは比較演算子を使わず、時間計算量が \\(O(n)\\) にもなりますが、汎用性は比較的低くなります。</p>","path":["第 11 章   ソート","11.1   ソートアルゴリズム"],"tags":[]},{"location":"chapter_sorting/sorting_algorithm/#1112","level":2,"title":"11.1.2   理想的なソートアルゴリズム","text":"<p>速く、インプレースで、安定で、適応的で、汎用性が高いことが理想です。しかし、これらをすべて兼ね備えたソートアルゴリズムは、これまで見つかっていません。そのため、具体的なデータの特徴と問題の要件に応じて選ぶ必要があります。</p> <p>これからさまざまなソートアルゴリズムを学び、これらの観点からそれぞれの長所と短所を分析していきます。</p>","path":["第 11 章   ソート","11.1   ソートアルゴリズム"],"tags":[]},{"location":"chapter_sorting/summary/","level":1,"title":"11.11   まとめ","text":"","path":["第 11 章   ソート","11.11   まとめ"],"tags":[]},{"location":"chapter_sorting/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>バブルソートは隣り合う要素を交換してソートします。フラグを追加して早期終了できるようにすると、最良時間計算量を \\(O(n)\\) に改善できます。</li> <li>挿入ソートは、毎回、未整列区間の要素を整列済み区間の正しい位置へ挿入します。時間計算量は \\(O(n^2)\\) ですが、基本操作が比較的少ないため、少量のデータのソートに広く使われます。</li> <li>クイックソートは番兵を用いた分割に基づきます。毎回最悪のピボットを選ぶと、時間計算量が \\(O(n^2)\\) に悪化します。中央値やランダムな値をピボットにすると、その確率を下げられます。短い部分区間を優先して再帰的に処理すれば、再帰の深さを減らし、空間計算量を \\(O(\\log n)\\) に改善できます。</li> <li>マージソートは分割と結合の二段階からなり、分割統治法の典型です。配列のソートには補助配列が必要で空間計算量は \\(O(n)\\) ですが、連結リストなら \\(O(1)\\) に改善できます。</li> <li>バケットソートは、データの振り分け、バケット内のソート、結果の結合という三段階からなります。これも分割統治法を体現しており、大量のデータに適しています。均等に振り分けることが鍵です。</li> <li>カウントソートはバケットソートの特殊な場合で、出現回数を数えてソートします。データ量が多く、値の範囲が限られる場合に適し、データを正の整数に変換できることが必要です。</li> <li>基数ソートは一桁ずつソートする方法で、データを固定桁数の数値として表せることが必要です。</li> <li>理想的には、高効率、安定、インプレース、適応性などの利点を備えたソートが望まれます。しかし、ほかのデータ構造やアルゴリズムと同じく、すべてを満たす方法はありません。実際には、データの特徴に応じて選ぶ必要があります。</li> <li>図 11-19 で、主なソートアルゴリズムの効率、安定性、インプレース性、適応性などを比較します。</li> </ul> <p> 図 11-19   ソートアルゴリズムの比較 </p>","path":["第 11 章   ソート","11.11   まとめ"],"tags":[]},{"location":"chapter_sorting/summary/#2-q-a","level":3,"title":"2.   Q &amp; A","text":"<p>Q:ソートの安定性は、どのような場合に必要ですか?</p> <p>実際には、オブジェクトの特定の属性でソートすることがあります。例えば、名前と身長を持つ学生を、まず名前順に並べて <code>(A, 180) (B, 185) (C, 170) (D, 170)</code> を得てから、身長順に並べたいとします。ソートが不安定だと、<code>(D, 170) (C, 170) (A, 180) (B, 185)</code> になる可能性があります。</p> <p>すると D と C が入れ替わり、名前順が崩れてしまいます。これは望む結果ではありません。</p> <p>Q:番兵を用いた分割では、「右から左へ探す」と「左から右へ探す」の順序を入れ替えられますか?</p> <p>できません。左端をピボットにする場合は、必ず右から左、次に左から右の順で探します。直感に反するかもしれませんが、理由を詳しく見ましょう。</p> <p><code>partition()</code> の最後の手順では、<code>nums[left]</code> と <code>nums[i]</code> を交換します。交換後、ピボットの左の要素はすべてピボット以下(<code>&lt;=</code>)になるため、交換前に <code>nums[left] &gt;= nums[i]</code> が成り立つ必要があります。先に左から右へ探すと、ピボットより大きい要素が見つからなかった場合、<code>i == j</code> でループを抜け、その時点で <code>nums[j] == nums[i] &gt; nums[left]</code> の可能性があります。すると、最後の交換でピボットより大きい要素を左端に移してしまい、分割に失敗します。</p> <p>例えば、配列 <code>[0, 0, 0, 0, 1]</code> で先に左から右へ探すと、分割後は <code>[1, 0, 0, 0, 0]</code> となり、正しくありません。</p> <p>さらに考えると、<code>nums[right]</code> をピボットにする場合は逆で、必ず左から右へ先に探す必要があります。</p> <p>Q:クイックソートの再帰の深さの改善では、なぜ短い配列を選ぶと深さが \\(\\log n\\) 以下になりますか?</p> <p>再帰の深さは、まだ戻っていない再帰呼び出しの数です。各分割で元の配列は二つに分かれます。改善後に再帰する部分配列の長さは、最大でも元の半分です。最悪の場合に毎回ちょうど半分でも、最終的な深さは \\(\\log n\\) です。</p> <p>元のクイックソートでは、長いほうを続けて再帰する可能性があります。最悪の場合、長さが \\(n\\)、\\(n - 1\\)、\\(\\dots\\)、\\(2\\)、\\(1\\) となり、深さは \\(n\\) です。改善によってこれを避けられます。</p> <p>Q:要素がすべて等しいと、クイックソートは \\(O(n^2)\\) 時間になりますか? どう対処できますか?</p> <p>はい。その場合、分割で配列をピボットより小さい部分、等しい部分、大きい部分の三つに分け、小さい部分と大きい部分だけを再帰的に処理できます。この方法なら、全要素が等しい配列は一回の分割でソートが完了します。</p> <p>Q:バケットソートの最悪時間計算量は、なぜ \\(O(n^2)\\) ですか?</p> <p>最悪の場合、すべての要素が同じバケットに入ります。それを \\(O(n^2)\\) のアルゴリズムでソートすると、時間計算量は \\(O(n^2)\\) になります。</p>","path":["第 11 章   ソート","11.11   まとめ"],"tags":[]},{"location":"chapter_stack_and_queue/","level":1,"title":"第 5 章   スタックとキュー","text":"<p>概要</p> <p>スタックは積み重なった猫たちのようであり、キューは列に並ぶ猫たちのようです。</p> <p>それぞれ、先入れ後出しと先入れ先出しという論理的な関係を表しています。</p>","path":["第 5 章   スタックとキュー"],"tags":[]},{"location":"chapter_stack_and_queue/#_1","level":2,"title":"この章の内容","text":"<ul> <li>5.1   スタック</li> <li>5.2   キュー</li> <li>5.3   両端キュー</li> <li>5.4   まとめ</li> <li>5.5   練習問題</li> </ul>","path":["第 5 章   スタックとキュー"],"tags":[]},{"location":"chapter_stack_and_queue/deque/","level":1,"title":"5.3   両端キュー","text":"<p>キューでは、先頭要素の削除と末尾への追加しかできません。図 5-7 の両端キュー(double-ended queue)は、先頭と末尾の両方で追加・削除ができ、より柔軟です。</p> <p></p> <p> 図 5-7   両端キューの操作 </p>","path":["第 5 章   スタックとキュー","5.3   両端キュー"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#531","level":2,"title":"5.3.1   両端キューの主な操作","text":"<p>主な操作を表 5-3 に示します。具体的なメソッド名は、使う言語によって異なります。</p> <p> 表 5-3   両端キューの操作効率 </p> メソッド名 説明 時間計算量 <code>push_first()</code> 先頭に要素を追加 \\(O(1)\\) <code>push_last()</code> 末尾に要素を追加 \\(O(1)\\) <code>pop_first()</code> 先頭要素を削除 \\(O(1)\\) <code>pop_last()</code> 末尾要素を削除 \\(O(1)\\) <code>peek_first()</code> 先頭要素にアクセス \\(O(1)\\) <code>peek_last()</code> 末尾要素にアクセス \\(O(1)\\) <p>同様に、言語が用意する両端キュークラスをそのまま利用できます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby deque.py<pre><code>from collections import deque\n\n# 両端キューを初期化\ndeq: deque[int] = deque()\n\n# 要素をエンキュー\ndeq.append(2) # 末尾に追加\ndeq.append(5)\ndeq.append(4)\ndeq.appendleft(3) # 先頭に追加\ndeq.appendleft(1)\n\n# 要素にアクセス\nfront: int = deq[0] # 先頭要素\nrear: int = deq[-1] # 末尾要素\n\n# 要素をデキュー\npop_front: int = deq.popleft() # 先頭要素を取り出す\npop_rear: int = deq.pop() # 末尾要素を取り出す\n\n# 両端キューの長さを取得\nsize: int = len(deq)\n\n# 両端キューが空かどうかを判定\nis_empty: bool = len(deq) == 0\n</code></pre> deque.cpp<pre><code>/* 両端キューを初期化 */\ndeque&lt;int&gt; deque;\n\n/* 要素をエンキュー */\ndeque.push_back(2); // 末尾に追加\ndeque.push_back(5);\ndeque.push_back(4);\ndeque.push_front(3); // 先頭に追加\ndeque.push_front(1);\n\n/* 要素にアクセス */\nint front = deque.front(); // 先頭要素\nint back = deque.back(); // 末尾要素\n\n/* 要素をデキュー */\ndeque.pop_front(); // 先頭要素を取り出す\ndeque.pop_back(); // 末尾要素を取り出す\n\n/* 両端キューの長さを取得 */\nint size = deque.size();\n\n/* 両端キューが空かどうかを判定 */\nbool empty = deque.empty();\n</code></pre> deque.java<pre><code>/* 両端キューを初期化 */\nDeque&lt;Integer&gt; deque = new LinkedList&lt;&gt;();\n\n/* 要素をエンキュー */\ndeque.offerLast(2); // 末尾に追加\ndeque.offerLast(5);\ndeque.offerLast(4);\ndeque.offerFirst(3); // 先頭に追加\ndeque.offerFirst(1);\n\n/* 要素にアクセス */\nint peekFirst = deque.peekFirst(); // 先頭要素\nint peekLast = deque.peekLast(); // 末尾要素\n\n/* 要素をデキュー */\nint popFirst = deque.pollFirst(); // 先頭要素を取り出す\nint popLast = deque.pollLast(); // 末尾要素を取り出す\n\n/* 両端キューの長さを取得 */\nint size = deque.size();\n\n/* 両端キューが空かどうかを判定 */\nboolean isEmpty = deque.isEmpty();\n</code></pre> deque.cs<pre><code>/* 両端キューを初期化 */\n// C# では LinkedList を両端キューとして使う\nLinkedList&lt;int&gt; deque = new();\n\n/* 要素をエンキュー */\ndeque.AddLast(2); // 末尾に追加\ndeque.AddLast(5);\ndeque.AddLast(4);\ndeque.AddFirst(3); // 先頭に追加\ndeque.AddFirst(1);\n\n/* 要素にアクセス */\nint peekFirst = deque.First.Value; // 先頭要素\nint peekLast = deque.Last.Value; // 末尾要素\n\n/* 要素をデキュー */\ndeque.RemoveFirst(); // 先頭要素を取り出す\ndeque.RemoveLast(); // 末尾要素を取り出す\n\n/* 両端キューの長さを取得 */\nint size = deque.Count;\n\n/* 両端キューが空かどうかを判定 */\nbool isEmpty = deque.Count == 0;\n</code></pre> deque_test.go<pre><code>/* 両端キューを初期化 */\n// Go では list を両端キューとして使う\ndeque := list.New()\n\n/* 要素をエンキュー */\ndeque.PushBack(2) // 末尾に追加\ndeque.PushBack(5)\ndeque.PushBack(4)\ndeque.PushFront(3) // 先頭に追加\ndeque.PushFront(1)\n\n/* 要素にアクセス */\nfront := deque.Front() // 先頭要素\nrear := deque.Back() // 末尾要素\n\n/* 要素をデキュー */\ndeque.Remove(front) // 先頭要素を取り出す\ndeque.Remove(rear) // 末尾要素を取り出す\n\n/* 両端キューの長さを取得 */\nsize := deque.Len()\n\n/* 両端キューが空かどうかを判定 */\nisEmpty := deque.Len() == 0\n</code></pre> deque.swift<pre><code>/* 両端キューを初期化 */\n// Swift には組み込みの両端キュークラスがないため、Array を両端キューとして使える\nvar deque: [Int] = []\n\n/* 要素をエンキュー */\ndeque.append(2) // 末尾に追加\ndeque.append(5)\ndeque.append(4)\ndeque.insert(3, at: 0) // 先頭に追加\ndeque.insert(1, at: 0)\n\n/* 要素にアクセス */\nlet peekFirst = deque.first! // 先頭要素\nlet peekLast = deque.last! // 末尾要素\n\n/* 要素をデキュー */\n// Array で代用する場合、popFirst の計算量は O(n)\nlet popFirst = deque.removeFirst() // 先頭要素を取り出す\nlet popLast = deque.removeLast() // 末尾要素を取り出す\n\n/* 両端キューの長さを取得 */\nlet size = deque.count\n\n/* 両端キューが空かどうかを判定 */\nlet isEmpty = deque.isEmpty\n</code></pre> deque.js<pre><code>/* 両端キューを初期化 */\n// JavaScript には組み込みの両端キューがないため、Array で代用する必要がある\nconst deque = [];\n\n/* 要素をエンキュー */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// 配列なので unshift() の時間計算量は O(n) であることに注意\ndeque.unshift(3);\ndeque.unshift(1);\n\n/* 要素にアクセス */\nconst peekFirst = deque[0];\nconst peekLast = deque[deque.length - 1];\n\n/* 要素をデキュー */\n// 配列なので shift() の時間計算量は O(n) であることに注意\nconst popFront = deque.shift();\nconst popBack = deque.pop();\n\n/* 両端キューの長さを取得 */\nconst size = deque.length;\n\n/* 両端キューが空かどうかを判定 */\nconst isEmpty = size === 0;\n</code></pre> deque.ts<pre><code>/* 両端キューを初期化 */\n// TypeScript には組み込みの両端キューがないため、Array で代用する必要がある\nconst deque: number[] = [];\n\n/* 要素をエンキュー */\ndeque.push(2);\ndeque.push(5);\ndeque.push(4);\n// 配列なので unshift() の時間計算量は O(n) であることに注意\ndeque.unshift(3);\ndeque.unshift(1);\n\n/* 要素にアクセス */\nconst peekFirst: number = deque[0];\nconst peekLast: number = deque[deque.length - 1];\n\n/* 要素をデキュー */\n// 配列なので shift() の時間計算量は O(n) であることに注意\nconst popFront: number = deque.shift() as number;\nconst popBack: number = deque.pop() as number;\n\n/* 両端キューの長さを取得 */\nconst size: number = deque.length;\n\n/* 両端キューが空かどうかを判定 */\nconst isEmpty: boolean = size === 0;\n</code></pre> deque.dart<pre><code>/* 両端キューを初期化 */\n// Dart では Queue は両端キューとして定義されている\nQueue&lt;int&gt; deque = Queue&lt;int&gt;();\n\n/* 要素をエンキュー */\ndeque.addLast(2); // 末尾に追加\ndeque.addLast(5);\ndeque.addLast(4);\ndeque.addFirst(3); // 先頭に追加\ndeque.addFirst(1);\n\n/* 要素にアクセス */\nint peekFirst = deque.first; // 先頭要素\nint peekLast = deque.last; // 末尾要素\n\n/* 要素をデキュー */\nint popFirst = deque.removeFirst(); // 先頭要素を取り出す\nint popLast = deque.removeLast(); // 末尾要素を取り出す\n\n/* 両端キューの長さを取得 */\nint size = deque.length;\n\n/* 両端キューが空かどうかを判定 */\nbool isEmpty = deque.isEmpty;\n</code></pre> deque.rs<pre><code>/* 両端キューを初期化 */\nlet mut deque: VecDeque&lt;u32&gt; = VecDeque::new();\n\n/* 要素をエンキュー */\ndeque.push_back(2); // 末尾に追加\ndeque.push_back(5);\ndeque.push_back(4);\ndeque.push_front(3); // 先頭に追加\ndeque.push_front(1);\n\n/* 要素にアクセス */\nif let Some(front) = deque.front() { // 先頭要素\n}\nif let Some(rear) = deque.back() { // 末尾要素\n}\n\n/* 要素をデキュー */\nif let Some(pop_front) = deque.pop_front() { // 先頭要素を取り出す\n}\nif let Some(pop_rear) = deque.pop_back() { // 末尾要素を取り出す\n}\n\n/* 両端キューの長さを取得 */\nlet size = deque.len();\n\n/* 両端キューが空かどうかを判定 */\nlet is_empty = deque.is_empty();\n</code></pre> deque.c<pre><code>// C には組み込みの両端キューがない\n</code></pre> deque.kt<pre><code>/* 両端キューを初期化 */\nval deque = LinkedList&lt;Int&gt;()\n\n/* 要素をエンキュー */\ndeque.offerLast(2) // 末尾に追加\ndeque.offerLast(5)\ndeque.offerLast(4)\ndeque.offerFirst(3) // 先頭に追加\ndeque.offerFirst(1)\n\n/* 要素にアクセス */\nval peekFirst = deque.peekFirst() // 先頭要素\nval peekLast = deque.peekLast() // 末尾要素\n\n/* 要素をデキュー */\nval popFirst = deque.pollFirst() // 先頭要素を取り出す\nval popLast = deque.pollLast() // 末尾要素を取り出す\n\n/* 両端キューの長さを取得 */\nval size = deque.size\n\n/* 両端キューが空かどうかを判定 */\nval isEmpty = deque.isEmpty()\n</code></pre> deque.rb<pre><code># 両端キューを初期化\n# Ruby には組み込みの両端キューがないため、Array で代用する必要がある\ndeque = []\n\n# 要素をエンキュー\ndeque &lt;&lt; 2\ndeque &lt;&lt; 5\ndeque &lt;&lt; 4\n# 配列なので Array#unshift の時間計算量は O(n) であることに注意\ndeque.unshift(3)\ndeque.unshift(1)\n\n# 要素にアクセス\npeek_first = deque.first\npeek_last = deque.last\n\n# 要素をデキュー\n# 配列なので Array#shift の時間計算量は O(n) であることに注意\npop_front = deque.shift\npop_back = deque.pop\n\n# 両端キューの長さを取得\nsize = deque.length\n\n# 両端キューが空かどうかを判定\nis_empty = size.zero?\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 5 章   スタックとキュー","5.3   両端キュー"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#532","level":2,"title":"5.3.2   両端キューの実装 *","text":"<p>実装はキューと似ており、基盤となるデータ構造に連結リストか配列を選べます。</p>","path":["第 5 章   スタックとキュー","5.3   両端キュー"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#1","level":3,"title":"1.   双方向連結リストによる実装","text":"<p>前節では、通常の単方向連結リストでキューを実装しました。先頭ノードの削除(デキュー)と、末尾ノードの後への追加(エンキュー)が容易だからです。</p> <p>両端キューでは、先頭でも末尾でも追加と削除ができます。つまり、反対側の対称的な操作も必要です。そのため、基盤に双方向連結リストを使います。</p> <p>図 5-8 のように、双方向連結リストの先頭と末尾を両端キューの先頭と末尾に対応させ、両端でノードを追加・削除できるようにします。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 5-8   連結リストによる両端キューの追加と削除 </p> <p>実装は次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_deque.py<pre><code>class ListNode:\n \"\"\"双方向連結リストのノード\"\"\"\n\n def __init__(self, val: int):\n \"\"\"コンストラクタ\"\"\"\n self.val: int = val\n self.next: ListNode | None = None # 後続ノードへの参照\n self.prev: ListNode | None = None # 前のノードへの参照\n\nclass LinkedListDeque:\n \"\"\"双方向連結リストで実装した両端キュー\"\"\"\n\n def __init__(self):\n \"\"\"コンストラクタ\"\"\"\n self._front: ListNode | None = None # 先頭ノード front\n self._rear: ListNode | None = None # 末尾ノード rear\n self._size: int = 0 # 両端キューの長さ\n\n def size(self) -&gt; int:\n \"\"\"両端キューの長さを取得する\"\"\"\n return self._size\n\n def is_empty(self) -&gt; bool:\n \"\"\"両端キューが空かどうかを判定する\"\"\"\n return self._size == 0\n\n def push(self, num: int, is_front: bool):\n \"\"\"エンキュー操作\"\"\"\n node = ListNode(num)\n # 連結リストが空の場合、front と rear の両方が node を指すようにする\n if self.is_empty():\n self._front = self._rear = node\n # 先頭へのエンキュー操作\n elif is_front:\n # node を連結リストの先頭に追加する\n self._front.prev = node\n node.next = self._front\n self._front = node # 先頭ノードを更新する\n # 末尾へのエンキュー操作\n else:\n # node を連結リストの末尾に追加する\n self._rear.next = node\n node.prev = self._rear\n self._rear = node # 末尾ノードを更新する\n self._size += 1 # キューの長さを更新する\n\n def push_first(self, num: int):\n \"\"\"先頭にエンキューする\"\"\"\n self.push(num, True)\n\n def push_last(self, num: int):\n \"\"\"末尾にエンキューする\"\"\"\n self.push(num, False)\n\n def pop(self, is_front: bool) -&gt; int:\n \"\"\"デキュー操作\"\"\"\n if self.is_empty():\n raise IndexError(\"両端キューが空です\")\n # キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if self._size == 1:\n val: int = self._front.val\n self._front = None\n self._rear = None\n # 先頭からのデキュー操作\n elif is_front:\n val: int = self._front.val # 先頭ノードの値を一時保存する\n # 先頭ノードを削除する\n fnext: ListNode | None = self._front.next\n fnext.prev = None\n self._front.next = None\n self._front = fnext # 先頭ノードを更新する\n # 末尾からのデキュー操作\n else:\n val: int = self._rear.val # 末尾ノードの値を一時保存する\n # 末尾ノードを削除する\n rprev: ListNode | None = self._rear.prev\n rprev.next = None\n self._rear.prev = None\n self._rear = rprev # 末尾ノードを更新する\n self._size -= 1 # キューの長さを更新する\n return val\n\n def pop_first(self) -&gt; int:\n \"\"\"先頭からデキューする\"\"\"\n return self.pop(True)\n\n def pop_last(self) -&gt; int:\n \"\"\"末尾からデキューする\"\"\"\n return self.pop(False)\n\n def peek_first(self) -&gt; int:\n \"\"\"キューの先頭要素にアクセス\"\"\"\n if self.is_empty():\n raise IndexError(\"両端キューが空です\")\n return self._front.val\n\n def peek_last(self) -&gt; int:\n \"\"\"キューの末尾要素にアクセスする\"\"\"\n if self.is_empty():\n raise IndexError(\"両端キューが空です\")\n return self._rear.val\n\n def to_array(self) -&gt; list[int]:\n \"\"\"表示用の配列を返す\"\"\"\n node = self._front\n res = [0] * self.size()\n for i in range(self.size()):\n res[i] = node.val\n node = node.next\n return res\n</code></pre> linkedlist_deque.cpp<pre><code>/* 双方向連結リストのノード */\nstruct DoublyListNode {\n int val; // ノードの値\n DoublyListNode *next; // 後続ノードへのポインタ\n DoublyListNode *prev; // 前のノードへのポインタ\n DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {\n }\n};\n\n/* 双方向連結リストで実装した両端キュー */\nclass LinkedListDeque {\n private:\n DoublyListNode *front, *rear; // 先頭ノード front 、末尾ノード rear\n int queSize = 0; // 両端キューの長さ\n\n public:\n /* コンストラクタ */\n LinkedListDeque() : front(nullptr), rear(nullptr) {\n }\n\n /* デストラクタ */\n ~LinkedListDeque() {\n // 連結リストを走査してノードを削除し、メモリを解放する\n DoublyListNode *pre, *cur = front;\n while (cur != nullptr) {\n pre = cur;\n cur = cur-&gt;next;\n delete pre;\n }\n }\n\n /* 両端キューの長さを取得する */\n int size() {\n return queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n bool isEmpty() {\n return size() == 0;\n }\n\n /* エンキュー操作 */\n void push(int num, bool isFront) {\n DoublyListNode *node = new DoublyListNode(num);\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n if (isEmpty())\n front = rear = node;\n // 先頭へのエンキュー操作\n else if (isFront) {\n // node を連結リストの先頭に追加する\n front-&gt;prev = node;\n node-&gt;next = front;\n front = node; // 先頭ノードを更新する\n // 末尾へのエンキュー操作\n } else {\n // node を連結リストの末尾に追加する\n rear-&gt;next = node;\n node-&gt;prev = rear;\n rear = node; // 末尾ノードを更新する\n }\n queSize++; // キューの長さを更新する\n }\n\n /* 先頭にエンキューする */\n void pushFirst(int num) {\n push(num, true);\n }\n\n /* 末尾にエンキューする */\n void pushLast(int num) {\n push(num, false);\n }\n\n /* デキュー操作 */\n int pop(bool isFront) {\n if (isEmpty())\n throw out_of_range(\"キューが空です\");\n int val;\n // キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if (queSize == 1) {\n val = front-&gt;val;\n delete front; // 同じノードを指しているため、片方を解放すればよい\n front = nullptr;\n rear = nullptr;\n }\n // 先頭からのデキュー操作\n else if (isFront) {\n val = front-&gt;val; // 先頭ノードの値を一時保存する\n // 先頭ノードを削除する\n DoublyListNode *fNext = front-&gt;next;\n fNext-&gt;prev = nullptr;\n front-&gt;next = nullptr;\n delete front;\n front = fNext; // 先頭ノードを更新する\n // 末尾からのデキュー操作\n } else {\n val = rear-&gt;val; // 末尾ノードの値を一時保存する\n // 末尾ノードを削除する\n DoublyListNode *rPrev = rear-&gt;prev;\n rPrev-&gt;next = nullptr;\n rear-&gt;prev = nullptr;\n delete rear;\n rear = rPrev; // 末尾ノードを更新する\n }\n queSize--; // キューの長さを更新する\n return val;\n }\n\n /* 先頭からデキューする */\n int popFirst() {\n return pop(true);\n }\n\n /* 末尾からデキューする */\n int popLast() {\n return pop(false);\n }\n\n /* キューの先頭要素にアクセス */\n int peekFirst() {\n if (isEmpty())\n throw out_of_range(\"両端キューが空です\");\n return front-&gt;val;\n }\n\n /* キューの末尾要素にアクセスする */\n int peekLast() {\n if (isEmpty())\n throw out_of_range(\"両端キューが空です\");\n return rear-&gt;val;\n }\n\n /* 表示用の配列を返す */\n vector&lt;int&gt; toVector() {\n DoublyListNode *node = front;\n vector&lt;int&gt; res(size());\n for (int i = 0; i &lt; res.size(); i++) {\n res[i] = node-&gt;val;\n node = node-&gt;next;\n }\n return res;\n }\n};\n</code></pre> linkedlist_deque.java<pre><code>/* 双方向連結リストのノード */\nclass ListNode {\n int val; // ノードの値\n ListNode next; // 後続ノードへの参照\n ListNode prev; // 前のノードへの参照\n\n ListNode(int val) {\n this.val = val;\n prev = next = null;\n }\n}\n\n/* 双方向連結リストで実装した両端キュー */\nclass LinkedListDeque {\n private ListNode front, rear; // 先頭ノード front 、末尾ノード rear\n private int queSize = 0; // 両端キューの長さ\n\n public LinkedListDeque() {\n front = rear = null;\n }\n\n /* 両端キューの長さを取得する */\n public int size() {\n return queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n public boolean isEmpty() {\n return size() == 0;\n }\n\n /* エンキュー操作 */\n private void push(int num, boolean isFront) {\n ListNode node = new ListNode(num);\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n if (isEmpty())\n front = rear = node;\n // 先頭へのエンキュー操作\n else if (isFront) {\n // node を連結リストの先頭に追加する\n front.prev = node;\n node.next = front;\n front = node; // 先頭ノードを更新する\n // 末尾へのエンキュー操作\n } else {\n // node を連結リストの末尾に追加する\n rear.next = node;\n node.prev = rear;\n rear = node; // 末尾ノードを更新する\n }\n queSize++; // キューの長さを更新する\n }\n\n /* 先頭にエンキューする */\n public void pushFirst(int num) {\n push(num, true);\n }\n\n /* 末尾にエンキューする */\n public void pushLast(int num) {\n push(num, false);\n }\n\n /* デキュー操作 */\n private int pop(boolean isFront) {\n if (isEmpty())\n throw new IndexOutOfBoundsException();\n int val;\n // キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if (queSize == 1) {\n val = front.val;\n front = null;\n rear = null;\n }\n // 先頭からのデキュー操作\n else if (isFront) {\n val = front.val; // 先頭ノードの値を一時保存する\n // 先頭ノードを削除する\n ListNode fNext = front.next;\n fNext.prev = null;\n front.next = null;\n front = fNext; // 先頭ノードを更新する\n // 末尾からのデキュー操作\n } else {\n val = rear.val; // 末尾ノードの値を一時保存する\n // 末尾ノードを削除する\n ListNode rPrev = rear.prev;\n rPrev.next = null;\n rear.prev = null;\n rear = rPrev; // 末尾ノードを更新する\n }\n queSize--; // キューの長さを更新する\n return val;\n }\n\n /* 先頭からデキューする */\n public int popFirst() {\n return pop(true);\n }\n\n /* 末尾からデキューする */\n public int popLast() {\n return pop(false);\n }\n\n /* キューの先頭要素にアクセス */\n public int peekFirst() {\n if (isEmpty())\n throw new IndexOutOfBoundsException();\n return front.val;\n }\n\n /* キューの末尾要素にアクセスする */\n public int peekLast() {\n if (isEmpty())\n throw new IndexOutOfBoundsException();\n return rear.val;\n }\n\n /* 表示用の配列を返す */\n public int[] toArray() {\n ListNode node = front;\n int[] res = new int[size()];\n for (int i = 0; i &lt; res.length; i++) {\n res[i] = node.val;\n node = node.next;\n }\n return res;\n }\n}\n</code></pre> linkedlist_deque.cs<pre><code>/* 双方向連結リストのノード */\nclass ListNode(int val) {\n public int val = val; // ノードの値\n public ListNode? next = null; // 後続ノードへの参照\n public ListNode? prev = null; // 前のノードへの参照\n}\n\n/* 双方向連結リストで実装した両端キュー */\nclass LinkedListDeque {\n ListNode? front, rear; // 先頭ノード front、末尾ノード rear\n int queSize = 0; // 両端キューの長さ\n\n public LinkedListDeque() {\n front = null;\n rear = null;\n }\n\n /* 両端キューの長さを取得する */\n public int Size() {\n return queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n public bool IsEmpty() {\n return Size() == 0;\n }\n\n /* エンキュー操作 */\n void Push(int num, bool isFront) {\n ListNode node = new(num);\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n if (IsEmpty()) {\n front = node;\n rear = node;\n }\n // 先頭へのエンキュー操作\n else if (isFront) {\n // node を連結リストの先頭に追加する\n front!.prev = node;\n node.next = front;\n front = node; // 先頭ノードを更新する \n }\n // 末尾へのエンキュー操作\n else {\n // node を連結リストの末尾に追加する\n rear!.next = node;\n node.prev = rear;\n rear = node; // 末尾ノードを更新する\n }\n\n queSize++; // キューの長さを更新する\n }\n\n /* 先頭にエンキューする */\n public void PushFirst(int num) {\n Push(num, true);\n }\n\n /* 末尾にエンキューする */\n public void PushLast(int num) {\n Push(num, false);\n }\n\n /* デキュー操作 */\n int? Pop(bool isFront) {\n if (IsEmpty())\n throw new Exception();\n int? val;\n // キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if (queSize == 1) {\n val = front!.val;\n front = null;\n rear = null;\n }\n // 先頭からのデキュー操作\n else if (isFront) {\n val = front!.val; // 先頭ノードの値を一時保存する\n // 先頭ノードを削除する\n ListNode? fNext = front?.next;\n fNext!.prev = null;\n front.next = null;\n front = fNext; // 先頭ノードを更新する\n }\n // 末尾からのデキュー操作\n else {\n val = rear!.val; // 末尾ノードの値を一時保存する\n // 末尾ノードを削除する\n ListNode? rPrev = rear?.prev;\n rPrev!.next = null;\n rear.prev = null;\n rear = rPrev; // 末尾ノードを更新する\n }\n\n queSize--; // キューの長さを更新する\n return val;\n }\n\n /* 先頭からデキューする */\n public int? PopFirst() {\n return Pop(true);\n }\n\n /* 末尾からデキューする */\n public int? PopLast() {\n return Pop(false);\n }\n\n /* キューの先頭要素にアクセス */\n public int? PeekFirst() {\n if (IsEmpty())\n throw new Exception();\n return front?.val;\n }\n\n /* キューの末尾要素にアクセスする */\n public int? PeekLast() {\n if (IsEmpty())\n throw new Exception();\n return rear?.val;\n }\n\n /* 表示用の配列を返す */\n public int?[] ToArray() {\n ListNode? node = front;\n int?[] res = new int?[Size()];\n for (int i = 0; i &lt; res.Length; i++) {\n res[i] = node?.val;\n node = node?.next;\n }\n\n return res;\n }\n}\n</code></pre> linkedlist_deque.go<pre><code>/* 双方向連結リストで実装した両端キュー */\ntype linkedListDeque struct {\n // 組み込みパッケージ list を使う\n data *list.List\n}\n\n/* 両端キューを初期化する */\nfunc newLinkedListDeque() *linkedListDeque {\n return &amp;linkedListDeque{\n data: list.New(),\n }\n}\n\n/* 先頭に要素をエンキューする */\nfunc (s *linkedListDeque) pushFirst(value any) {\n s.data.PushFront(value)\n}\n\n/* 末尾に要素をエンキューする */\nfunc (s *linkedListDeque) pushLast(value any) {\n s.data.PushBack(value)\n}\n\n/* キューの先頭要素をデキューする */\nfunc (s *linkedListDeque) popFirst() any {\n if s.isEmpty() {\n return nil\n }\n e := s.data.Front()\n s.data.Remove(e)\n return e.Value\n}\n\n/* キューの末尾要素をデキューする */\nfunc (s *linkedListDeque) popLast() any {\n if s.isEmpty() {\n return nil\n }\n e := s.data.Back()\n s.data.Remove(e)\n return e.Value\n}\n\n/* キューの先頭要素にアクセス */\nfunc (s *linkedListDeque) peekFirst() any {\n if s.isEmpty() {\n return nil\n }\n e := s.data.Front()\n return e.Value\n}\n\n/* キューの末尾要素にアクセスする */\nfunc (s *linkedListDeque) peekLast() any {\n if s.isEmpty() {\n return nil\n }\n e := s.data.Back()\n return e.Value\n}\n\n/* キューの長さを取得 */\nfunc (s *linkedListDeque) size() int {\n return s.data.Len()\n}\n\n/* キューが空か判定 */\nfunc (s *linkedListDeque) isEmpty() bool {\n return s.data.Len() == 0\n}\n\n/* 表示用の List を取得する */\nfunc (s *linkedListDeque) toList() *list.List {\n return s.data\n}\n</code></pre> linkedlist_deque.swift<pre><code>/* 双方向連結リストのノード */\nclass ListNode {\n var val: Int // ノードの値\n var next: ListNode? // 後続ノードへの参照\n weak var prev: ListNode? // 前のノードへの参照\n\n init(val: Int) {\n self.val = val\n }\n}\n\n/* 双方向連結リストで実装した両端キュー */\nclass LinkedListDeque {\n private var front: ListNode? // 先頭ノード front\n private var rear: ListNode? // 末尾ノード rear\n private var _size: Int // 両端キューの長さ\n\n init() {\n _size = 0\n }\n\n /* 両端キューの長さを取得する */\n func size() -&gt; Int {\n _size\n }\n\n /* 両端キューが空かどうかを判定する */\n func isEmpty() -&gt; Bool {\n size() == 0\n }\n\n /* エンキュー操作 */\n private func push(num: Int, isFront: Bool) {\n let node = ListNode(val: num)\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n if isEmpty() {\n front = node\n rear = node\n }\n // 先頭へのエンキュー操作\n else if isFront {\n // node を連結リストの先頭に追加する\n front?.prev = node\n node.next = front\n front = node // 先頭ノードを更新する\n }\n // 末尾へのエンキュー操作\n else {\n // node を連結リストの末尾に追加する\n rear?.next = node\n node.prev = rear\n rear = node // 末尾ノードを更新する\n }\n _size += 1 // キューの長さを更新する\n }\n\n /* 先頭にエンキューする */\n func pushFirst(num: Int) {\n push(num: num, isFront: true)\n }\n\n /* 末尾にエンキューする */\n func pushLast(num: Int) {\n push(num: num, isFront: false)\n }\n\n /* デキュー操作 */\n private func pop(isFront: Bool) -&gt; Int {\n if isEmpty() {\n fatalError(\"両端キューが空です\")\n }\n let val: Int\n // キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if _size == 1 {\n val = front!.val\n front = nil\n rear = nil\n }\n // 先頭からのデキュー操作\n else if isFront {\n val = front!.val // 先頭ノードの値を一時保存する\n // 先頭ノードを削除する\n let fNext = front?.next\n fNext?.prev = nil\n front?.next = nil\n front = fNext // 先頭ノードを更新する\n }\n // 末尾からのデキュー操作\n else {\n val = rear!.val // 末尾ノードの値を一時保存する\n // 末尾ノードを削除する\n let rPrev = rear?.prev\n rPrev?.next = nil\n rear?.prev = nil\n rear = rPrev // 末尾ノードを更新する\n }\n _size -= 1 // キューの長さを更新する\n return val\n }\n\n /* 先頭からデキューする */\n func popFirst() -&gt; Int {\n pop(isFront: true)\n }\n\n /* 末尾からデキューする */\n func popLast() -&gt; Int {\n pop(isFront: false)\n }\n\n /* キューの先頭要素にアクセス */\n func peekFirst() -&gt; Int {\n if isEmpty() {\n fatalError(\"両端キューが空です\")\n }\n return front!.val\n }\n\n /* キューの末尾要素にアクセスする */\n func peekLast() -&gt; Int {\n if isEmpty() {\n fatalError(\"両端キューが空です\")\n }\n return rear!.val\n }\n\n /* 表示用の配列を返す */\n func toArray() -&gt; [Int] {\n var node = front\n var res = Array(repeating: 0, count: size())\n for i in res.indices {\n res[i] = node!.val\n node = node?.next\n }\n return res\n }\n}\n</code></pre> linkedlist_deque.js<pre><code>/* 双方向連結リストのノード */\nclass ListNode {\n prev; // 前のノードへの参照(ポインタ)\n next; // 後続ノードへの参照(ポインタ)\n val; // ノードの値\n\n constructor(val) {\n this.val = val;\n this.next = null;\n this.prev = null;\n }\n}\n\n/* 双方向連結リストで実装した両端キュー */\nclass LinkedListDeque {\n #front; // 先頭ノード front\n #rear; // 末尾ノード rear\n #queSize; // 両端キューの長さ\n\n constructor() {\n this.#front = null;\n this.#rear = null;\n this.#queSize = 0;\n }\n\n /* 末尾へのエンキュー操作 */\n pushLast(val) {\n const node = new ListNode(val);\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n if (this.#queSize === 0) {\n this.#front = node;\n this.#rear = node;\n } else {\n // node を連結リストの末尾に追加する\n this.#rear.next = node;\n node.prev = this.#rear;\n this.#rear = node; // 末尾ノードを更新する\n }\n this.#queSize++;\n }\n\n /* 先頭へのエンキュー操作 */\n pushFirst(val) {\n const node = new ListNode(val);\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n if (this.#queSize === 0) {\n this.#front = node;\n this.#rear = node;\n } else {\n // node を連結リストの先頭に追加する\n this.#front.prev = node;\n node.next = this.#front;\n this.#front = node; // 先頭ノードを更新する\n }\n this.#queSize++;\n }\n\n /* 末尾からのデキュー操作 */\n popLast() {\n if (this.#queSize === 0) {\n return null;\n }\n const value = this.#rear.val; // 末尾ノードの値を保存する\n // キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if (this.#queSize === 1) {\n this.#front = null;\n this.#rear = null;\n }\n // 末尾ノードを削除する\n else {\n let temp = this.#rear.prev;\n temp.next = null;\n this.#rear.prev = null;\n this.#rear = temp; // 末尾ノードを更新する\n }\n this.#queSize--;\n return value;\n }\n\n /* 先頭からのデキュー操作 */\n popFirst() {\n if (this.#queSize === 0) {\n return null;\n }\n const value = this.#front.val; // 先頭ノードの値を保存する\n // キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if (this.#queSize === 1) {\n this.#front = null;\n this.#rear = null;\n }\n // 先頭ノードを削除する\n else {\n let temp = this.#front.next;\n temp.prev = null;\n this.#front.next = null;\n this.#front = temp; // 先頭ノードを更新する\n }\n this.#queSize--;\n return value;\n }\n\n /* キューの末尾要素にアクセスする */\n peekLast() {\n return this.#queSize === 0 ? null : this.#rear.val;\n }\n\n /* キューの先頭要素にアクセス */\n peekFirst() {\n return this.#queSize === 0 ? null : this.#front.val;\n }\n\n /* 両端キューの長さを取得する */\n size() {\n return this.#queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n isEmpty() {\n return this.#queSize === 0;\n }\n\n /* 両端キューを表示する */\n print() {\n const arr = [];\n let temp = this.#front;\n while (temp !== null) {\n arr.push(temp.val);\n temp = temp.next;\n }\n console.log('[' + arr.join(', ') + ']');\n }\n}\n</code></pre> linkedlist_deque.ts<pre><code>/* 双方向連結リストのノード */\nclass ListNode {\n prev: ListNode; // 前のノードへの参照(ポインタ)\n next: ListNode; // 後続ノードへの参照(ポインタ)\n val: number; // ノードの値\n\n constructor(val: number) {\n this.val = val;\n this.next = null;\n this.prev = null;\n }\n}\n\n/* 双方向連結リストで実装した両端キュー */\nclass LinkedListDeque {\n private front: ListNode; // 先頭ノード front\n private rear: ListNode; // 末尾ノード rear\n private queSize: number; // 両端キューの長さ\n\n constructor() {\n this.front = null;\n this.rear = null;\n this.queSize = 0;\n }\n\n /* 末尾へのエンキュー操作 */\n pushLast(val: number): void {\n const node: ListNode = new ListNode(val);\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n if (this.queSize === 0) {\n this.front = node;\n this.rear = node;\n } else {\n // node を連結リストの末尾に追加する\n this.rear.next = node;\n node.prev = this.rear;\n this.rear = node; // 末尾ノードを更新する\n }\n this.queSize++;\n }\n\n /* 先頭へのエンキュー操作 */\n pushFirst(val: number): void {\n const node: ListNode = new ListNode(val);\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n if (this.queSize === 0) {\n this.front = node;\n this.rear = node;\n } else {\n // node を連結リストの先頭に追加する\n this.front.prev = node;\n node.next = this.front;\n this.front = node; // 先頭ノードを更新する\n }\n this.queSize++;\n }\n\n /* 末尾からのデキュー操作 */\n popLast(): number {\n if (this.queSize === 0) {\n return null;\n }\n const value: number = this.rear!.val; // 末尾ノードの値を保存する\n // キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if (this.queSize === 1) {\n this.front = null;\n this.rear = null;\n }\n // 末尾ノードを削除する\n else {\n let temp: ListNode = this.rear!.prev;\n temp!.next = null;\n this.rear!.prev = null;\n this.rear = temp; // 末尾ノードを更新する\n }\n this.queSize--;\n return value;\n }\n\n /* 先頭からのデキュー操作 */\n popFirst(): number {\n if (this.queSize === 0) {\n return null;\n }\n const value: number = this.front.val; // 先頭ノードの値を保存する\n // キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if (this.queSize === 1) {\n this.front = null;\n this.rear = null;\n }\n // 先頭ノードを削除する\n else {\n let temp: ListNode = this.front!.next;\n temp!.prev = null;\n this.front!.next = null;\n this.front = temp; // 先頭ノードを更新する\n }\n this.queSize--;\n return value;\n }\n\n /* キューの末尾要素にアクセスする */\n peekLast(): number {\n return this.queSize === 0 ? null : this.rear.val;\n }\n\n /* キューの先頭要素にアクセス */\n peekFirst(): number {\n return this.queSize === 0 ? null : this.front.val;\n }\n\n /* 両端キューの長さを取得する */\n size(): number {\n return this.queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n isEmpty(): boolean {\n return this.queSize === 0;\n }\n\n /* 両端キューを表示する */\n print(): void {\n const arr: number[] = [];\n let temp: ListNode = this.front;\n while (temp !== null) {\n arr.push(temp.val);\n temp = temp.next;\n }\n console.log('[' + arr.join(', ') + ']');\n }\n}\n</code></pre> linkedlist_deque.dart<pre><code>/* 双方向連結リストのノード */\nclass ListNode {\n int val; // ノードの値\n ListNode? next; // 後続ノードへの参照\n ListNode? prev; // 前のノードへの参照\n\n ListNode(this.val, {this.next, this.prev});\n}\n\n/* 双方向連結リストで実装した両端キュー */\nclass LinkedListDeque {\n late ListNode? _front; // 先頭ノード _front\n late ListNode? _rear; // 末尾ノード _rear\n int _queSize = 0; // 両端キューの長さ\n\n LinkedListDeque() {\n this._front = null;\n this._rear = null;\n }\n\n /* 両端キューの長さを取得する */\n int size() {\n return this._queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n bool isEmpty() {\n return size() == 0;\n }\n\n /* エンキュー操作 */\n void push(int _num, bool isFront) {\n final ListNode node = ListNode(_num);\n if (isEmpty()) {\n // 連結リストが空の場合、_front と _rear の両方が node を指すようにする\n _front = _rear = node;\n } else if (isFront) {\n // 先頭へのエンキュー操作\n // node を連結リストの先頭に追加する\n _front!.prev = node;\n node.next = _front;\n _front = node; // 先頭ノードを更新する\n } else {\n // 末尾へのエンキュー操作\n // node を連結リストの末尾に追加する\n _rear!.next = node;\n node.prev = _rear;\n _rear = node; // 末尾ノードを更新する\n }\n _queSize++; // キューの長さを更新する\n }\n\n /* 先頭にエンキューする */\n void pushFirst(int _num) {\n push(_num, true);\n }\n\n /* 末尾にエンキューする */\n void pushLast(int _num) {\n push(_num, false);\n }\n\n /* デキュー操作 */\n int? pop(bool isFront) {\n // キューが空の場合、そのまま null を返す\n if (isEmpty()) {\n return null;\n }\n final int val;\n // キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if (_queSize == 1) {\n val = _front!.val;\n _front = null;\n _rear = null;\n }\n // 先頭からのデキュー操作\n else if (isFront) {\n val = _front!.val; // 先頭ノードの値を一時保存する\n // 先頭ノードを削除する\n ListNode? fNext = _front!.next;\n fNext!.prev = null;\n _front!.next = null;\n _front = fNext; // 先頭ノードを更新する\n // 末尾からのデキュー操作\n } else {\n val = _rear!.val; // 末尾ノードの値を一時保存する\n // 末尾ノードを削除する\n ListNode? rPrev = _rear!.prev;\n rPrev!.next = null;\n _rear!.prev = null;\n _rear = rPrev; // 末尾ノードを更新する\n }\n _queSize--; // キューの長さを更新する\n return val;\n }\n\n /* 先頭からデキューする */\n int? popFirst() {\n return pop(true);\n }\n\n /* 末尾からデキューする */\n int? popLast() {\n return pop(false);\n }\n\n /* キューの先頭要素にアクセス */\n int? peekFirst() {\n return _front?.val;\n }\n\n /* キューの末尾要素にアクセスする */\n int? peekLast() {\n return _rear?.val;\n }\n\n /* 表示用の配列を返す */\n List&lt;int&gt; toArray() {\n ListNode? node = _front;\n final List&lt;int&gt; res = [];\n for (int i = 0; i &lt; _queSize; i++) {\n res.add(node!.val);\n node = node.next;\n }\n return res;\n }\n}\n</code></pre> linkedlist_deque.rs<pre><code>/* 双方向連結リストのノード */\npub struct ListNode&lt;T&gt; {\n pub val: T, // ノードの値\n pub next: Option&lt;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;, // 後続ノードへのポインタ\n pub prev: Option&lt;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;, // 前のノードへのポインタ\n}\n\nimpl&lt;T&gt; ListNode&lt;T&gt; {\n pub fn new(val: T) -&gt; Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt; {\n Rc::new(RefCell::new(ListNode {\n val,\n next: None,\n prev: None,\n }))\n }\n}\n\n/* 双方向連結リストで実装した両端キュー */\n#[allow(dead_code)]\npub struct LinkedListDeque&lt;T&gt; {\n front: Option&lt;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;, // 先頭ノード front\n rear: Option&lt;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;, // 末尾ノード rear\n que_size: usize, // 両端キューの長さ\n}\n\nimpl&lt;T: Copy&gt; LinkedListDeque&lt;T&gt; {\n pub fn new() -&gt; Self {\n Self {\n front: None,\n rear: None,\n que_size: 0,\n }\n }\n\n /* 両端キューの長さを取得する */\n pub fn size(&amp;self) -&gt; usize {\n return self.que_size;\n }\n\n /* 両端キューが空かどうかを判定する */\n pub fn is_empty(&amp;self) -&gt; bool {\n return self.que_size == 0;\n }\n\n /* エンキュー操作 */\n fn push(&amp;mut self, num: T, is_front: bool) {\n let node = ListNode::new(num);\n // 先頭へのエンキュー操作\n if is_front {\n match self.front.take() {\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n None =&gt; {\n self.rear = Some(node.clone());\n self.front = Some(node);\n }\n // node を連結リストの先頭に追加する\n Some(old_front) =&gt; {\n old_front.borrow_mut().prev = Some(node.clone());\n node.borrow_mut().next = Some(old_front);\n self.front = Some(node); // 先頭ノードを更新する\n }\n }\n }\n // 末尾へのエンキュー操作\n else {\n match self.rear.take() {\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n None =&gt; {\n self.front = Some(node.clone());\n self.rear = Some(node);\n }\n // node を連結リストの末尾に追加する\n Some(old_rear) =&gt; {\n old_rear.borrow_mut().next = Some(node.clone());\n node.borrow_mut().prev = Some(old_rear);\n self.rear = Some(node); // 末尾ノードを更新する\n }\n }\n }\n self.que_size += 1; // キューの長さを更新する\n }\n\n /* 先頭にエンキューする */\n pub fn push_first(&amp;mut self, num: T) {\n self.push(num, true);\n }\n\n /* 末尾にエンキューする */\n pub fn push_last(&amp;mut self, num: T) {\n self.push(num, false);\n }\n\n /* デキュー操作 */\n fn pop(&amp;mut self, is_front: bool) -&gt; Option&lt;T&gt; {\n // キューが空の場合、そのまま None を返す\n if self.is_empty() {\n return None;\n };\n // 先頭からのデキュー操作\n if is_front {\n self.front.take().map(|old_front| {\n match old_front.borrow_mut().next.take() {\n Some(new_front) =&gt; {\n new_front.borrow_mut().prev.take();\n self.front = Some(new_front); // 先頭ノードを更新する\n }\n None =&gt; {\n self.rear.take();\n }\n }\n self.que_size -= 1; // キューの長さを更新する\n old_front.borrow().val\n })\n }\n // 末尾からのデキュー操作\n else {\n self.rear.take().map(|old_rear| {\n match old_rear.borrow_mut().prev.take() {\n Some(new_rear) =&gt; {\n new_rear.borrow_mut().next.take();\n self.rear = Some(new_rear); // 末尾ノードを更新する\n }\n None =&gt; {\n self.front.take();\n }\n }\n self.que_size -= 1; // キューの長さを更新する\n old_rear.borrow().val\n })\n }\n }\n\n /* 先頭からデキューする */\n pub fn pop_first(&amp;mut self) -&gt; Option&lt;T&gt; {\n return self.pop(true);\n }\n\n /* 末尾からデキューする */\n pub fn pop_last(&amp;mut self) -&gt; Option&lt;T&gt; {\n return self.pop(false);\n }\n\n /* キューの先頭要素にアクセス */\n pub fn peek_first(&amp;self) -&gt; Option&lt;&amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt; {\n self.front.as_ref()\n }\n\n /* キューの末尾要素にアクセスする */\n pub fn peek_last(&amp;self) -&gt; Option&lt;&amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt; {\n self.rear.as_ref()\n }\n\n /* 表示用の配列を返す */\n pub fn to_array(&amp;self, head: Option&lt;&amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;) -&gt; Vec&lt;T&gt; {\n let mut res: Vec&lt;T&gt; = Vec::new();\n fn recur&lt;T: Copy&gt;(cur: Option&lt;&amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;, res: &amp;mut Vec&lt;T&gt;) {\n if let Some(cur) = cur {\n res.push(cur.borrow().val);\n recur(cur.borrow().next.as_ref(), res);\n }\n }\n\n recur(head, &amp;mut res);\n res\n }\n}\n</code></pre> linkedlist_deque.c<pre><code>/* 双方向連結リストのノード */\ntypedef struct DoublyListNode {\n int val; // ノードの値\n struct DoublyListNode *next; // 後続ノード\n struct DoublyListNode *prev; // 前のノード\n} DoublyListNode;\n\n/* コンストラクタ */\nDoublyListNode *newDoublyListNode(int num) {\n DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode));\n new-&gt;val = num;\n new-&gt;next = NULL;\n new-&gt;prev = NULL;\n return new;\n}\n\n/* デストラクタ */\nvoid delDoublyListNode(DoublyListNode *node) {\n free(node);\n}\n\n/* 双方向連結リストで実装した両端キュー */\ntypedef struct {\n DoublyListNode *front, *rear; // 先頭ノード front 、末尾ノード rear\n int queSize; // 両端キューの長さ\n} LinkedListDeque;\n\n/* コンストラクタ */\nLinkedListDeque *newLinkedListDeque() {\n LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque));\n deque-&gt;front = NULL;\n deque-&gt;rear = NULL;\n deque-&gt;queSize = 0;\n return deque;\n}\n\n/* デストラクタ */\nvoid delLinkedListdeque(LinkedListDeque *deque) {\n // すべてのノードを解放する\n for (int i = 0; i &lt; deque-&gt;queSize &amp;&amp; deque-&gt;front != NULL; i++) {\n DoublyListNode *tmp = deque-&gt;front;\n deque-&gt;front = deque-&gt;front-&gt;next;\n free(tmp);\n }\n // deque 構造体を解放する\n free(deque);\n}\n\n/* キューの長さを取得 */\nint size(LinkedListDeque *deque) {\n return deque-&gt;queSize;\n}\n\n/* キューが空か判定 */\nbool empty(LinkedListDeque *deque) {\n return (size(deque) == 0);\n}\n\n/* エンキュー */\nvoid push(LinkedListDeque *deque, int num, bool isFront) {\n DoublyListNode *node = newDoublyListNode(num);\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n if (empty(deque)) {\n deque-&gt;front = deque-&gt;rear = node;\n }\n // 先頭へのエンキュー操作\n else if (isFront) {\n // node を連結リストの先頭に追加する\n deque-&gt;front-&gt;prev = node;\n node-&gt;next = deque-&gt;front;\n deque-&gt;front = node; // 先頭ノードを更新する\n }\n // 末尾へのエンキュー操作\n else {\n // node を連結リストの末尾に追加する\n deque-&gt;rear-&gt;next = node;\n node-&gt;prev = deque-&gt;rear;\n deque-&gt;rear = node;\n }\n deque-&gt;queSize++; // キューの長さを更新する\n}\n\n/* 先頭にエンキューする */\nvoid pushFirst(LinkedListDeque *deque, int num) {\n push(deque, num, true);\n}\n\n/* 末尾にエンキューする */\nvoid pushLast(LinkedListDeque *deque, int num) {\n push(deque, num, false);\n}\n\n/* キューの先頭要素にアクセス */\nint peekFirst(LinkedListDeque *deque) {\n assert(size(deque) &amp;&amp; deque-&gt;front);\n return deque-&gt;front-&gt;val;\n}\n\n/* キューの末尾要素にアクセスする */\nint peekLast(LinkedListDeque *deque) {\n assert(size(deque) &amp;&amp; deque-&gt;rear);\n return deque-&gt;rear-&gt;val;\n}\n\n/* デキュー */\nint pop(LinkedListDeque *deque, bool isFront) {\n if (empty(deque))\n return -1;\n int val;\n // キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if (deque-&gt;queSize == 1) {\n val = deque-&gt;front-&gt;val;\n delDoublyListNode(deque-&gt;front); // 同じノードを指しているため、片方を解放すればよい\n deque-&gt;front = NULL;\n deque-&gt;rear = NULL;\n }\n // 先頭からのデキュー操作\n else if (isFront) {\n val = peekFirst(deque); // 先頭ノードの値を一時保存する\n DoublyListNode *fNext = deque-&gt;front-&gt;next;\n fNext-&gt;prev = NULL;\n deque-&gt;front-&gt;next = NULL;\n delDoublyListNode(deque-&gt;front);\n deque-&gt;front = fNext; // 先頭ノードを更新する\n }\n // 末尾からのデキュー操作\n else {\n val = peekLast(deque); // 末尾ノードの値を一時保存する\n DoublyListNode *rPrev = deque-&gt;rear-&gt;prev;\n rPrev-&gt;next = NULL;\n deque-&gt;rear-&gt;prev = NULL;\n delDoublyListNode(deque-&gt;rear);\n deque-&gt;rear = rPrev; // 末尾ノードを更新する\n }\n deque-&gt;queSize--; // キューの長さを更新する\n return val;\n}\n\n/* 先頭からデキューする */\nint popFirst(LinkedListDeque *deque) {\n return pop(deque, true);\n}\n\n/* 末尾からデキューする */\nint popLast(LinkedListDeque *deque) {\n return pop(deque, false);\n}\n\n/* キューを表示する */\nvoid printLinkedListDeque(LinkedListDeque *deque) {\n int *arr = malloc(sizeof(int) * deque-&gt;queSize);\n // 連結リストのデータを配列にコピーする\n int i;\n DoublyListNode *node;\n for (i = 0, node = deque-&gt;front; i &lt; deque-&gt;queSize; i++) {\n arr[i] = node-&gt;val;\n node = node-&gt;next;\n }\n printArray(arr, deque-&gt;queSize);\n free(arr);\n}\n</code></pre> linkedlist_deque.kt<pre><code>/* 双方向連結リストのノード */\nclass ListNode(var _val: Int) {\n // ノードの値\n var next: ListNode? = null // 後続ノードへの参照\n var prev: ListNode? = null // 前のノードへの参照\n}\n\n/* 双方向連結リストで実装した両端キュー */\nclass LinkedListDeque {\n private var front: ListNode? = null // 先頭ノード front\n private var rear: ListNode? = null // 末尾ノード rear\n private var queSize: Int = 0 // 両端キューの長さ\n\n /* 両端キューの長さを取得する */\n fun size(): Int {\n return queSize\n }\n\n /* 両端キューが空かどうかを判定する */\n fun isEmpty(): Boolean {\n return size() == 0\n }\n\n /* エンキュー操作 */\n fun push(num: Int, isFront: Boolean) {\n val node = ListNode(num)\n // 連結リストが空の場合、front と rear の両方が node を指すようにする\n if (isEmpty()) {\n rear = node\n front = rear\n // 先頭へのエンキュー操作\n } else if (isFront) {\n // node を連結リストの先頭に追加する\n front?.prev = node\n node.next = front\n front = node // 先頭ノードを更新する\n // 末尾へのエンキュー操作\n } else {\n // node を連結リストの末尾に追加する\n rear?.next = node\n node.prev = rear\n rear = node // 末尾ノードを更新する\n }\n queSize++ // キューの長さを更新する\n }\n\n /* 先頭にエンキューする */\n fun pushFirst(num: Int) {\n push(num, true)\n }\n\n /* 末尾にエンキューする */\n fun pushLast(num: Int) {\n push(num, false)\n }\n\n /* デキュー操作 */\n fun pop(isFront: Boolean): Int {\n if (isEmpty()) \n throw IndexOutOfBoundsException()\n val _val: Int\n // キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if (queSize == 1) {\n _val = front!!._val\n front = null\n rear = null\n }\n // 先頭からのデキュー操作\n else if (isFront) {\n _val = front!!._val // 先頭ノードの値を一時保存する\n // 先頭ノードを削除する\n val fNext = front!!.next\n fNext!!.prev = null\n front!!.next = null\n front = fNext // 先頭ノードを更新する\n // 末尾からのデキュー操作\n } else {\n _val = rear!!._val // 末尾ノードの値を一時保存する\n // 末尾ノードを削除する\n val rPrev = rear!!.prev\n rPrev!!.next = null\n rear!!.prev = null\n rear = rPrev // 末尾ノードを更新する\n }\n queSize-- // キューの長さを更新する\n return _val\n }\n\n /* 先頭からデキューする */\n fun popFirst(): Int {\n return pop(true)\n }\n\n /* 末尾からデキューする */\n fun popLast(): Int {\n return pop(false)\n }\n\n /* キューの先頭要素にアクセス */\n fun peekFirst(): Int {\n if (isEmpty()) throw IndexOutOfBoundsException()\n return front!!._val\n }\n\n /* キューの末尾要素にアクセスする */\n fun peekLast(): Int {\n if (isEmpty()) throw IndexOutOfBoundsException()\n return rear!!._val\n }\n\n /* 表示用の配列を返す */\n fun toArray(): IntArray {\n var node = front\n val res = IntArray(size())\n for (i in res.indices) {\n res[i] = node!!._val\n node = node.next\n }\n return res\n }\n}\n</code></pre> linkedlist_deque.rb<pre><code>=begin\nFile: linkedlist_deque.rb\nCreated Time: 2024-04-06\nAuthor: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)\n=end\n\n### 双方向連結リストのノード\nclass ListNode\n attr_accessor :val\n attr_accessor :next # 後続ノードへの参照\n attr_accessor :prev # 前のノードへの参照\n\n ### コンストラクタ ###\n def initialize(val)\n @val = val\n end\nend\n\n### 双方向連結リストで実装した両端キュー ###\nclass LinkedListDeque\n ### 両端キューの長さを取得する ###\n attr_reader :size\n\n ### コンストラクタ ###\n def initialize\n @front = nil # 先頭ノード front\n @rear = nil # 末尾ノード rear\n @size = 0 # 両端キューの長さ\n end\n\n ### 両端キューが空かどうかを判定する ###\n def is_empty?\n size.zero?\n end\n\n ### エンキュー操作 ###\n def push(num, is_front)\n node = ListNode.new(num)\n # 連結リストが空の場合、front と rear の両方が node を指すようにする\n if is_empty?\n @front = @rear = node\n # 先頭へのエンキュー操作\n elsif is_front\n # node を連結リストの先頭に追加する\n @front.prev = node\n node.next = @front\n @front = node # 先頭ノードを更新する\n # 末尾へのエンキュー操作\n else\n # node を連結リストの末尾に追加する\n @rear.next = node\n node.prev = @rear\n @rear = node # 末尾ノードを更新する\n end\n @size += 1 # キューの長さを更新する\n end\n\n ### 先頭にエンキューする ###\n def push_first(num)\n push(num, true)\n end\n\n ### 末尾にエンキューする ###\n def push_last(num)\n push(num, false)\n end\n\n ### デキュー操作 ###\n def pop(is_front)\n raise IndexError, '両端キューが空です' if is_empty?\n\n # キューの長さが 1 のとき、先頭と末尾は同じノードを指す\n if @size == 1\n val = @front.val\n @front = nil\n @rear = nil\n # 先頭からのデキュー操作\n elsif is_front\n val = @front.val # 先頭ノードの値を一時保存する\n # 先頭ノードを削除する\n fnext = @front.next\n fnext.prev = nil\n @front.next = nil\n @front = fnext # 先頭ノードを更新する\n # 末尾からのデキュー操作\n else\n val = @rear.val # 末尾ノードの値を一時保存する\n # 末尾ノードを削除する\n rprev = @rear.prev\n rprev.next = nil\n @rear.prev = nil\n @rear = rprev # 末尾ノードを更新する\n end\n @size -= 1 # キューの長さを更新する\n\n val\n end\n\n ### 先頭からデキューする ###\n def pop_first\n pop(true)\n end\n\n ### 先頭からデキューする ###\n def pop_last\n pop(false)\n end\n\n ### キューの先頭要素にアクセス ###\n def peek_first\n raise IndexError, '両端キューが空です' if is_empty?\n\n @front.val\n end\n\n ### キューの末尾要素にアクセスする ###\n def peek_last\n raise IndexError, '両端キューが空です' if is_empty?\n\n @rear.val\n end\n\n ### 表示用の配列を返す ###\n def to_array\n node = @front\n res = Array.new(size, 0)\n for i in 0...size\n res[i] = node.val\n node = node.next\n end\n res\n end\nend\n</code></pre>","path":["第 5 章   スタックとキュー","5.3   両端キュー"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#2","level":3,"title":"2.   配列による実装","text":"<p>図 5-9 のように、配列によるキューと同様、循環配列で両端キューを実装できます。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 5-9   配列による両端キューの追加と削除 </p> <p>キューの実装に、先頭への追加と末尾からの取り出しのメソッドを加えるだけです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_deque.py<pre><code>class ArrayDeque:\n \"\"\"循環配列で実装した両端キュー\"\"\"\n\n def __init__(self, capacity: int):\n \"\"\"コンストラクタ\"\"\"\n self._nums: list[int] = [0] * capacity\n self._front: int = 0\n self._size: int = 0\n\n def capacity(self) -&gt; int:\n \"\"\"両端キューの容量を取得する\"\"\"\n return len(self._nums)\n\n def size(self) -&gt; int:\n \"\"\"両端キューの長さを取得する\"\"\"\n return self._size\n\n def is_empty(self) -&gt; bool:\n \"\"\"両端キューが空かどうかを判定する\"\"\"\n return self._size == 0\n\n def index(self, i: int) -&gt; int:\n \"\"\"循環配列のインデックスを計算する\"\"\"\n # 剰余演算を使い、配列の先頭と末尾をつなぐ\n # i が配列の末尾を越えたら、先頭に戻る\n # i が配列の先頭を越えたら、末尾に戻る\n return (i + self.capacity()) % self.capacity()\n\n def push_first(self, num: int):\n \"\"\"先頭にエンキューする\"\"\"\n if self._size == self.capacity():\n print(\"両端キューが満杯です\")\n return\n # 先頭ポインタを左に 1 つ移動する\n # 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n self._front = self.index(self._front - 1)\n # num をキューの先頭に追加する\n self._nums[self._front] = num\n self._size += 1\n\n def push_last(self, num: int):\n \"\"\"末尾にエンキューする\"\"\"\n if self._size == self.capacity():\n print(\"両端キューが満杯です\")\n return\n # 末尾のインデックス + 1 を指す末尾ポインタを計算する\n rear = self.index(self._front + self._size)\n # num をキューの末尾に追加する\n self._nums[rear] = num\n self._size += 1\n\n def pop_first(self) -&gt; int:\n \"\"\"先頭からデキューする\"\"\"\n num = self.peek_first()\n # 先頭ポインタを後ろに 1 つ移動する\n self._front = self.index(self._front + 1)\n self._size -= 1\n return num\n\n def pop_last(self) -&gt; int:\n \"\"\"末尾からデキューする\"\"\"\n num = self.peek_last()\n self._size -= 1\n return num\n\n def peek_first(self) -&gt; int:\n \"\"\"キューの先頭要素にアクセス\"\"\"\n if self.is_empty():\n raise IndexError(\"両端キューが空です\")\n return self._nums[self._front]\n\n def peek_last(self) -&gt; int:\n \"\"\"キューの末尾要素にアクセスする\"\"\"\n if self.is_empty():\n raise IndexError(\"両端キューが空です\")\n # 末尾要素のインデックスを計算する\n last = self.index(self._front + self._size - 1)\n return self._nums[last]\n\n def to_array(self) -&gt; list[int]:\n \"\"\"表示用の配列を返す\"\"\"\n # 有効な長さの範囲内にあるリストの要素だけを変換する\n res = []\n for i in range(self._size):\n res.append(self._nums[self.index(self._front + i)])\n return res\n</code></pre> array_deque.cpp<pre><code>/* 循環配列で実装した両端キュー */\nclass ArrayDeque {\n private:\n vector&lt;int&gt; nums; // 両端キューの要素を格納する配列\n int front; // キューの先頭要素を指す先頭ポインタ\n int queSize; // 両端キューの長さ\n\n public:\n /* コンストラクタ */\n ArrayDeque(int capacity) {\n nums.resize(capacity);\n front = queSize = 0;\n }\n\n /* 両端キューの容量を取得する */\n int capacity() {\n return nums.size();\n }\n\n /* 両端キューの長さを取得する */\n int size() {\n return queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n bool isEmpty() {\n return queSize == 0;\n }\n\n /* 循環配列のインデックスを計算する */\n int index(int i) {\n // 剰余演算を使い、配列の先頭と末尾をつなぐ\n // i が配列の末尾を越えたら、先頭に戻る\n // i が配列の先頭を越えたら、末尾に戻る\n return (i + capacity()) % capacity();\n }\n\n /* 先頭にエンキューする */\n void pushFirst(int num) {\n if (queSize == capacity()) {\n cout &lt;&lt; \"両端キューが満杯です\" &lt;&lt; endl;\n return;\n }\n // 先頭ポインタを左に 1 つ移動する\n // 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n front = index(front - 1);\n // num をキューの先頭に追加する\n nums[front] = num;\n queSize++;\n }\n\n /* 末尾にエンキューする */\n void pushLast(int num) {\n if (queSize == capacity()) {\n cout &lt;&lt; \"両端キューが満杯です\" &lt;&lt; endl;\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n int rear = index(front + queSize);\n // num をキューの末尾に追加する\n nums[rear] = num;\n queSize++;\n }\n\n /* 先頭からデキューする */\n int popFirst() {\n int num = peekFirst();\n // 先頭ポインタを後ろに 1 つ移動する\n front = index(front + 1);\n queSize--;\n return num;\n }\n\n /* 末尾からデキューする */\n int popLast() {\n int num = peekLast();\n queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n int peekFirst() {\n if (isEmpty())\n throw out_of_range(\"両端キューが空です\");\n return nums[front];\n }\n\n /* キューの末尾要素にアクセスする */\n int peekLast() {\n if (isEmpty())\n throw out_of_range(\"両端キューが空です\");\n // 末尾要素のインデックスを計算する\n int last = index(front + queSize - 1);\n return nums[last];\n }\n\n /* 表示用の配列を返す */\n vector&lt;int&gt; toVector() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n vector&lt;int&gt; res(queSize);\n for (int i = 0, j = front; i &lt; queSize; i++, j++) {\n res[i] = nums[index(j)];\n }\n return res;\n }\n};\n</code></pre> array_deque.java<pre><code>/* 循環配列で実装した両端キュー */\nclass ArrayDeque {\n private int[] nums; // 両端キューの要素を格納する配列\n private int front; // キューの先頭要素を指す先頭ポインタ\n private int queSize; // 両端キューの長さ\n\n /* コンストラクタ */\n public ArrayDeque(int capacity) {\n this.nums = new int[capacity];\n front = queSize = 0;\n }\n\n /* 両端キューの容量を取得する */\n public int capacity() {\n return nums.length;\n }\n\n /* 両端キューの長さを取得する */\n public int size() {\n return queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n public boolean isEmpty() {\n return queSize == 0;\n }\n\n /* 循環配列のインデックスを計算する */\n private int index(int i) {\n // 剰余演算を使い、配列の先頭と末尾をつなぐ\n // i が配列の末尾を越えたら、先頭に戻る\n // i が配列の先頭を越えたら、末尾に戻る\n return (i + capacity()) % capacity();\n }\n\n /* 先頭にエンキューする */\n public void pushFirst(int num) {\n if (queSize == capacity()) {\n System.out.println(\"両端キューが満杯です\");\n return;\n }\n // 先頭ポインタを左に 1 つ移動する\n // 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n front = index(front - 1);\n // num をキューの先頭に追加する\n nums[front] = num;\n queSize++;\n }\n\n /* 末尾にエンキューする */\n public void pushLast(int num) {\n if (queSize == capacity()) {\n System.out.println(\"両端キューが満杯です\");\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n int rear = index(front + queSize);\n // num をキューの末尾に追加する\n nums[rear] = num;\n queSize++;\n }\n\n /* 先頭からデキューする */\n public int popFirst() {\n int num = peekFirst();\n // 先頭ポインタを後ろに 1 つ移動する\n front = index(front + 1);\n queSize--;\n return num;\n }\n\n /* 末尾からデキューする */\n public int popLast() {\n int num = peekLast();\n queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n public int peekFirst() {\n if (isEmpty())\n throw new IndexOutOfBoundsException();\n return nums[front];\n }\n\n /* キューの末尾要素にアクセスする */\n public int peekLast() {\n if (isEmpty())\n throw new IndexOutOfBoundsException();\n // 末尾要素のインデックスを計算する\n int last = index(front + queSize - 1);\n return nums[last];\n }\n\n /* 表示用の配列を返す */\n public int[] toArray() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n int[] res = new int[queSize];\n for (int i = 0, j = front; i &lt; queSize; i++, j++) {\n res[i] = nums[index(j)];\n }\n return res;\n }\n}\n</code></pre> array_deque.cs<pre><code>/* 循環配列で実装した両端キュー */\nclass ArrayDeque {\n int[] nums; // 両端キューの要素を格納する配列\n int front; // キューの先頭要素を指す先頭ポインタ\n int queSize; // 両端キューの長さ\n\n /* コンストラクタ */\n public ArrayDeque(int capacity) {\n nums = new int[capacity];\n front = queSize = 0;\n }\n\n /* 両端キューの容量を取得する */\n int Capacity() {\n return nums.Length;\n }\n\n /* 両端キューの長さを取得する */\n public int Size() {\n return queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n public bool IsEmpty() {\n return queSize == 0;\n }\n\n /* 循環配列のインデックスを計算する */\n int Index(int i) {\n // 剰余演算を使い、配列の先頭と末尾をつなぐ\n // i が配列の末尾を越えたら、先頭に戻る\n // i が配列の先頭を越えたら、末尾に戻る\n return (i + Capacity()) % Capacity();\n }\n\n /* 先頭にエンキューする */\n public void PushFirst(int num) {\n if (queSize == Capacity()) {\n Console.WriteLine(\"両端キューが満杯です\");\n return;\n }\n // 先頭ポインタを左に 1 つ移動する\n // 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n front = Index(front - 1);\n // num をキューの先頭に追加する\n nums[front] = num;\n queSize++;\n }\n\n /* 末尾にエンキューする */\n public void PushLast(int num) {\n if (queSize == Capacity()) {\n Console.WriteLine(\"両端キューが満杯です\");\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n int rear = Index(front + queSize);\n // num をキューの末尾に追加する\n nums[rear] = num;\n queSize++;\n }\n\n /* 先頭からデキューする */\n public int PopFirst() {\n int num = PeekFirst();\n // 先頭ポインタを後ろに 1 つ移動する\n front = Index(front + 1);\n queSize--;\n return num;\n }\n\n /* 末尾からデキューする */\n public int PopLast() {\n int num = PeekLast();\n queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n public int PeekFirst() {\n if (IsEmpty()) {\n throw new InvalidOperationException();\n }\n return nums[front];\n }\n\n /* キューの末尾要素にアクセスする */\n public int PeekLast() {\n if (IsEmpty()) {\n throw new InvalidOperationException();\n }\n // 末尾要素のインデックスを計算する\n int last = Index(front + queSize - 1);\n return nums[last];\n }\n\n /* 表示用の配列を返す */\n public int[] ToArray() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n int[] res = new int[queSize];\n for (int i = 0, j = front; i &lt; queSize; i++, j++) {\n res[i] = nums[Index(j)];\n }\n return res;\n }\n}\n</code></pre> array_deque.go<pre><code>/* 循環配列で実装した両端キュー */\ntype arrayDeque struct {\n nums []int // 両端キューの要素を格納する配列\n front int // キューの先頭要素を指す先頭ポインタ\n queSize int // 両端キューの長さ\n queCapacity int // キューの容量(格納できる要素数の上限)\n}\n\n/* キューを初期化する */\nfunc newArrayDeque(queCapacity int) *arrayDeque {\n return &amp;arrayDeque{\n nums: make([]int, queCapacity),\n queCapacity: queCapacity,\n front: 0,\n queSize: 0,\n }\n}\n\n/* 両端キューの長さを取得する */\nfunc (q *arrayDeque) size() int {\n return q.queSize\n}\n\n/* 両端キューが空かどうかを判定する */\nfunc (q *arrayDeque) isEmpty() bool {\n return q.queSize == 0\n}\n\n/* 循環配列のインデックスを計算する */\nfunc (q *arrayDeque) index(i int) int {\n // 剰余演算を使い、配列の先頭と末尾をつなぐ\n // i が配列の末尾を越えたら、先頭に戻る\n // i が配列の先頭を越えたら、末尾に戻る\n return (i + q.queCapacity) % q.queCapacity\n}\n\n/* 先頭にエンキューする */\nfunc (q *arrayDeque) pushFirst(num int) {\n if q.queSize == q.queCapacity {\n fmt.Println(\"両端キューが満杯です\")\n return\n }\n // 先頭ポインタを左に 1 つ移動する\n // 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n q.front = q.index(q.front - 1)\n // num をキューの先頭に追加する\n q.nums[q.front] = num\n q.queSize++\n}\n\n/* 末尾にエンキューする */\nfunc (q *arrayDeque) pushLast(num int) {\n if q.queSize == q.queCapacity {\n fmt.Println(\"両端キューが満杯です\")\n return\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n rear := q.index(q.front + q.queSize)\n // num をキューの末尾に追加する\n q.nums[rear] = num\n q.queSize++\n}\n\n/* 先頭からデキューする */\nfunc (q *arrayDeque) popFirst() any {\n num := q.peekFirst()\n if num == nil {\n return nil\n }\n // 先頭ポインタを後ろに 1 つ移動する\n q.front = q.index(q.front + 1)\n q.queSize--\n return num\n}\n\n/* 末尾からデキューする */\nfunc (q *arrayDeque) popLast() any {\n num := q.peekLast()\n if num == nil {\n return nil\n }\n q.queSize--\n return num\n}\n\n/* キューの先頭要素にアクセス */\nfunc (q *arrayDeque) peekFirst() any {\n if q.isEmpty() {\n return nil\n }\n return q.nums[q.front]\n}\n\n/* キューの末尾要素にアクセスする */\nfunc (q *arrayDeque) peekLast() any {\n if q.isEmpty() {\n return nil\n }\n // 末尾要素のインデックスを計算する\n last := q.index(q.front + q.queSize - 1)\n return q.nums[last]\n}\n\n/* 表示用の Slice を取得する */\nfunc (q *arrayDeque) toSlice() []int {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n res := make([]int, q.queSize)\n for i, j := 0, q.front; i &lt; q.queSize; i++ {\n res[i] = q.nums[q.index(j)]\n j++\n }\n return res\n}\n</code></pre> array_deque.swift<pre><code>/* 循環配列で実装した両端キュー */\nclass ArrayDeque {\n private var nums: [Int] // 両端キューの要素を格納する配列\n private var front: Int // キューの先頭要素を指す先頭ポインタ\n private var _size: Int // 両端キューの長さ\n\n /* コンストラクタ */\n init(capacity: Int) {\n nums = Array(repeating: 0, count: capacity)\n front = 0\n _size = 0\n }\n\n /* 両端キューの容量を取得する */\n func capacity() -&gt; Int {\n nums.count\n }\n\n /* 両端キューの長さを取得する */\n func size() -&gt; Int {\n _size\n }\n\n /* 両端キューが空かどうかを判定する */\n func isEmpty() -&gt; Bool {\n size() == 0\n }\n\n /* 循環配列のインデックスを計算する */\n private func index(i: Int) -&gt; Int {\n // 剰余演算を使い、配列の先頭と末尾をつなぐ\n // i が配列の末尾を越えたら、先頭に戻る\n // i が配列の先頭を越えたら、末尾に戻る\n (i + capacity()) % capacity()\n }\n\n /* 先頭にエンキューする */\n func pushFirst(num: Int) {\n if size() == capacity() {\n print(\"両端キューが満杯です\")\n return\n }\n // 先頭ポインタを左に 1 つ移動する\n // 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n front = index(i: front - 1)\n // num をキューの先頭に追加する\n nums[front] = num\n _size += 1\n }\n\n /* 末尾にエンキューする */\n func pushLast(num: Int) {\n if size() == capacity() {\n print(\"両端キューが満杯です\")\n return\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n let rear = index(i: front + size())\n // num をキューの末尾に追加する\n nums[rear] = num\n _size += 1\n }\n\n /* 先頭からデキューする */\n func popFirst() -&gt; Int {\n let num = peekFirst()\n // 先頭ポインタを後ろに 1 つ移動する\n front = index(i: front + 1)\n _size -= 1\n return num\n }\n\n /* 末尾からデキューする */\n func popLast() -&gt; Int {\n let num = peekLast()\n _size -= 1\n return num\n }\n\n /* キューの先頭要素にアクセス */\n func peekFirst() -&gt; Int {\n if isEmpty() {\n fatalError(\"両端キューが空です\")\n }\n return nums[front]\n }\n\n /* キューの末尾要素にアクセスする */\n func peekLast() -&gt; Int {\n if isEmpty() {\n fatalError(\"両端キューが空です\")\n }\n // 末尾要素のインデックスを計算する\n let last = index(i: front + size() - 1)\n return nums[last]\n }\n\n /* 表示用の配列を返す */\n func toArray() -&gt; [Int] {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n (front ..&lt; front + size()).map { nums[index(i: $0)] }\n }\n}\n</code></pre> array_deque.js<pre><code>/* 循環配列で実装した両端キュー */\nclass ArrayDeque {\n #nums; // 両端キューの要素を格納する配列\n #front; // キューの先頭要素を指す先頭ポインタ\n #queSize; // 両端キューの長さ\n\n /* コンストラクタ */\n constructor(capacity) {\n this.#nums = new Array(capacity);\n this.#front = 0;\n this.#queSize = 0;\n }\n\n /* 両端キューの容量を取得する */\n capacity() {\n return this.#nums.length;\n }\n\n /* 両端キューの長さを取得する */\n size() {\n return this.#queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n isEmpty() {\n return this.#queSize === 0;\n }\n\n /* 循環配列のインデックスを計算する */\n index(i) {\n // 剰余演算を使い、配列の先頭と末尾をつなぐ\n // i が配列の末尾を越えたら、先頭に戻る\n // i が配列の先頭を越えたら、末尾に戻る\n return (i + this.capacity()) % this.capacity();\n }\n\n /* 先頭にエンキューする */\n pushFirst(num) {\n if (this.#queSize === this.capacity()) {\n console.log('両端キューが満杯です');\n return;\n }\n // 先頭ポインタを左に 1 つ移動する\n // 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n this.#front = this.index(this.#front - 1);\n // num をキューの先頭に追加する\n this.#nums[this.#front] = num;\n this.#queSize++;\n }\n\n /* 末尾にエンキューする */\n pushLast(num) {\n if (this.#queSize === this.capacity()) {\n console.log('両端キューが満杯です');\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n const rear = this.index(this.#front + this.#queSize);\n // num をキューの末尾に追加する\n this.#nums[rear] = num;\n this.#queSize++;\n }\n\n /* 先頭からデキューする */\n popFirst() {\n const num = this.peekFirst();\n // 先頭ポインタを後ろに 1 つ移動する\n this.#front = this.index(this.#front + 1);\n this.#queSize--;\n return num;\n }\n\n /* 末尾からデキューする */\n popLast() {\n const num = this.peekLast();\n this.#queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n peekFirst() {\n if (this.isEmpty()) throw new Error('両端キューが空です。');\n return this.#nums[this.#front];\n }\n\n /* キューの末尾要素にアクセスする */\n peekLast() {\n if (this.isEmpty()) throw new Error('両端キューが空です。');\n // 末尾要素のインデックスを計算する\n const last = this.index(this.#front + this.#queSize - 1);\n return this.#nums[last];\n }\n\n /* 表示用の配列を返す */\n toArray() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n const res = [];\n for (let i = 0, j = this.#front; i &lt; this.#queSize; i++, j++) {\n res[i] = this.#nums[this.index(j)];\n }\n return res;\n }\n}\n</code></pre> array_deque.ts<pre><code>/* 循環配列で実装した両端キュー */\nclass ArrayDeque {\n private nums: number[]; // 両端キューの要素を格納する配列\n private front: number; // キューの先頭要素を指す先頭ポインタ\n private queSize: number; // 両端キューの長さ\n\n /* コンストラクタ */\n constructor(capacity: number) {\n this.nums = new Array(capacity);\n this.front = 0;\n this.queSize = 0;\n }\n\n /* 両端キューの容量を取得する */\n capacity(): number {\n return this.nums.length;\n }\n\n /* 両端キューの長さを取得する */\n size(): number {\n return this.queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n isEmpty(): boolean {\n return this.queSize === 0;\n }\n\n /* 循環配列のインデックスを計算する */\n index(i: number): number {\n // 剰余演算を使い、配列の先頭と末尾をつなぐ\n // i が配列の末尾を越えたら、先頭に戻る\n // i が配列の先頭を越えたら、末尾に戻る\n return (i + this.capacity()) % this.capacity();\n }\n\n /* 先頭にエンキューする */\n pushFirst(num: number): void {\n if (this.queSize === this.capacity()) {\n console.log('両端キューが満杯です');\n return;\n }\n // 先頭ポインタを左に 1 つ移動する\n // 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n this.front = this.index(this.front - 1);\n // num をキューの先頭に追加する\n this.nums[this.front] = num;\n this.queSize++;\n }\n\n /* 末尾にエンキューする */\n pushLast(num: number): void {\n if (this.queSize === this.capacity()) {\n console.log('両端キューが満杯です');\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n const rear: number = this.index(this.front + this.queSize);\n // num をキューの末尾に追加する\n this.nums[rear] = num;\n this.queSize++;\n }\n\n /* 先頭からデキューする */\n popFirst(): number {\n const num: number = this.peekFirst();\n // 先頭ポインタを後ろに 1 つ移動する\n this.front = this.index(this.front + 1);\n this.queSize--;\n return num;\n }\n\n /* 末尾からデキューする */\n popLast(): number {\n const num: number = this.peekLast();\n this.queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n peekFirst(): number {\n if (this.isEmpty()) throw new Error('両端キューが空です。');\n return this.nums[this.front];\n }\n\n /* キューの末尾要素にアクセスする */\n peekLast(): number {\n if (this.isEmpty()) throw new Error('両端キューが空です。');\n // 末尾要素のインデックスを計算する\n const last = this.index(this.front + this.queSize - 1);\n return this.nums[last];\n }\n\n /* 表示用の配列を返す */\n toArray(): number[] {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n const res: number[] = [];\n for (let i = 0, j = this.front; i &lt; this.queSize; i++, j++) {\n res[i] = this.nums[this.index(j)];\n }\n return res;\n }\n}\n</code></pre> array_deque.dart<pre><code>/* 循環配列で実装した両端キュー */\nclass ArrayDeque {\n late List&lt;int&gt; _nums; // 両端キューの要素を格納する配列\n late int _front; // キューの先頭要素を指す先頭ポインタ\n late int _queSize; // 両端キューの長さ\n\n /* コンストラクタ */\n ArrayDeque(int capacity) {\n this._nums = List.filled(capacity, 0);\n this._front = this._queSize = 0;\n }\n\n /* 両端キューの容量を取得する */\n int capacity() {\n return _nums.length;\n }\n\n /* 両端キューの長さを取得する */\n int size() {\n return _queSize;\n }\n\n /* 両端キューが空かどうかを判定する */\n bool isEmpty() {\n return _queSize == 0;\n }\n\n /* 循環配列のインデックスを計算する */\n int index(int i) {\n // 剰余演算を使い、配列の先頭と末尾をつなぐ\n // i が配列の末尾を越えたら、先頭に戻る\n // i が配列の先頭を越えたら、末尾に戻る\n return (i + capacity()) % capacity();\n }\n\n /* 先頭にエンキューする */\n void pushFirst(int _num) {\n if (_queSize == capacity()) {\n throw Exception(\"両端キューが満杯です\");\n }\n // 先頭ポインタを左に 1 つ移動する\n // 剰余演算により、_front が配列の先頭を越えたら末尾に戻るようにする\n _front = index(_front - 1);\n // _num をキューの先頭に追加する\n _nums[_front] = _num;\n _queSize++;\n }\n\n /* 末尾にエンキューする */\n void pushLast(int _num) {\n if (_queSize == capacity()) {\n throw Exception(\"両端キューが満杯です\");\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n int rear = index(_front + _queSize);\n // _num をキューの末尾に追加する\n _nums[rear] = _num;\n _queSize++;\n }\n\n /* 先頭からデキューする */\n int popFirst() {\n int _num = peekFirst();\n // 先頭ポインタを右に 1 つ移動する\n _front = index(_front + 1);\n _queSize--;\n return _num;\n }\n\n /* 末尾からデキューする */\n int popLast() {\n int _num = peekLast();\n _queSize--;\n return _num;\n }\n\n /* キューの先頭要素にアクセス */\n int peekFirst() {\n if (isEmpty()) {\n throw Exception(\"両端キューが空です\");\n }\n return _nums[_front];\n }\n\n /* キューの末尾要素にアクセスする */\n int peekLast() {\n if (isEmpty()) {\n throw Exception(\"両端キューが空です\");\n }\n // 末尾要素のインデックスを計算する\n int last = index(_front + _queSize - 1);\n return _nums[last];\n }\n\n /* 表示用の配列を返す */\n List&lt;int&gt; toArray() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n List&lt;int&gt; res = List.filled(_queSize, 0);\n for (int i = 0, j = _front; i &lt; _queSize; i++, j++) {\n res[i] = _nums[index(j)];\n }\n return res;\n }\n}\n</code></pre> array_deque.rs<pre><code>/* 循環配列で実装した両端キュー */\nstruct ArrayDeque&lt;T&gt; {\n nums: Vec&lt;T&gt;, // 両端キューの要素を格納する配列\n front: usize, // キューの先頭要素を指す先頭ポインタ\n que_size: usize, // 両端キューの長さ\n}\n\nimpl&lt;T: Copy + Default&gt; ArrayDeque&lt;T&gt; {\n /* コンストラクタ */\n pub fn new(capacity: usize) -&gt; Self {\n Self {\n nums: vec![T::default(); capacity],\n front: 0,\n que_size: 0,\n }\n }\n\n /* 両端キューの容量を取得する */\n pub fn capacity(&amp;self) -&gt; usize {\n self.nums.len()\n }\n\n /* 両端キューの長さを取得する */\n pub fn size(&amp;self) -&gt; usize {\n self.que_size\n }\n\n /* 両端キューが空かどうかを判定する */\n pub fn is_empty(&amp;self) -&gt; bool {\n self.que_size == 0\n }\n\n /* 循環配列のインデックスを計算する */\n fn index(&amp;self, i: i32) -&gt; usize {\n // 剰余演算を使い、配列の先頭と末尾をつなぐ\n // i が配列の末尾を越えたら、先頭に戻る\n // i が配列の先頭を越えたら、末尾に戻る\n ((i + self.capacity() as i32) % self.capacity() as i32) as usize\n }\n\n /* 先頭にエンキューする */\n pub fn push_first(&amp;mut self, num: T) {\n if self.que_size == self.capacity() {\n println!(\"両端キューが満杯です\");\n return;\n }\n // 先頭ポインタを左に 1 つ移動する\n // 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n self.front = self.index(self.front as i32 - 1);\n // num をキューの先頭に追加する\n self.nums[self.front] = num;\n self.que_size += 1;\n }\n\n /* 末尾にエンキューする */\n pub fn push_last(&amp;mut self, num: T) {\n if self.que_size == self.capacity() {\n println!(\"両端キューが満杯です\");\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n let rear = self.index(self.front as i32 + self.que_size as i32);\n // num をキューの末尾に追加する\n self.nums[rear] = num;\n self.que_size += 1;\n }\n\n /* 先頭からデキューする */\n fn pop_first(&amp;mut self) -&gt; T {\n let num = self.peek_first();\n // 先頭ポインタを後ろに 1 つ移動する\n self.front = self.index(self.front as i32 + 1);\n self.que_size -= 1;\n num\n }\n\n /* 末尾からデキューする */\n fn pop_last(&amp;mut self) -&gt; T {\n let num = self.peek_last();\n self.que_size -= 1;\n num\n }\n\n /* キューの先頭要素にアクセス */\n fn peek_first(&amp;self) -&gt; T {\n if self.is_empty() {\n panic!(\"両端キューが空です\")\n };\n self.nums[self.front]\n }\n\n /* キューの末尾要素にアクセスする */\n fn peek_last(&amp;self) -&gt; T {\n if self.is_empty() {\n panic!(\"両端キューが空です\")\n };\n // 末尾要素のインデックスを計算する\n let last = self.index(self.front as i32 + self.que_size as i32 - 1);\n self.nums[last]\n }\n\n /* 表示用の配列を返す */\n fn to_array(&amp;self) -&gt; Vec&lt;T&gt; {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n let mut res = vec![T::default(); self.que_size];\n let mut j = self.front;\n for i in 0..self.que_size {\n res[i] = self.nums[self.index(j as i32)];\n j += 1;\n }\n res\n }\n}\n</code></pre> array_deque.c<pre><code>/* 循環配列で実装した両端キュー */\ntypedef struct {\n int *nums; // キューの要素を格納する配列\n int front; // キューの先頭要素を指す先頭ポインタ\n int queSize; // キューの末尾 + 1 を指す末尾ポインタ\n int queCapacity; // キューの容量\n} ArrayDeque;\n\n/* コンストラクタ */\nArrayDeque *newArrayDeque(int capacity) {\n ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque));\n // 配列を初期化する\n deque-&gt;queCapacity = capacity;\n deque-&gt;nums = (int *)malloc(sizeof(int) * deque-&gt;queCapacity);\n deque-&gt;front = deque-&gt;queSize = 0;\n return deque;\n}\n\n/* デストラクタ */\nvoid delArrayDeque(ArrayDeque *deque) {\n free(deque-&gt;nums);\n free(deque);\n}\n\n/* 両端キューの容量を取得する */\nint capacity(ArrayDeque *deque) {\n return deque-&gt;queCapacity;\n}\n\n/* 両端キューの長さを取得する */\nint size(ArrayDeque *deque) {\n return deque-&gt;queSize;\n}\n\n/* 両端キューが空かどうかを判定する */\nbool empty(ArrayDeque *deque) {\n return deque-&gt;queSize == 0;\n}\n\n/* 循環配列のインデックスを計算する */\nint dequeIndex(ArrayDeque *deque, int i) {\n // 剰余演算を使い、配列の先頭と末尾をつなぐ\n // i が配列の末尾を越えたら、先頭に戻る\n // i が配列の先頭を越えたら、末尾に戻る\n return ((i + capacity(deque)) % capacity(deque));\n}\n\n/* 先頭にエンキューする */\nvoid pushFirst(ArrayDeque *deque, int num) {\n if (deque-&gt;queSize == capacity(deque)) {\n printf(\"両端キューが満杯です\\r\\n\");\n return;\n }\n // 先頭ポインタを左に 1 つ移動する\n // 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n deque-&gt;front = dequeIndex(deque, deque-&gt;front - 1);\n // num をキューの先頭に追加する\n deque-&gt;nums[deque-&gt;front] = num;\n deque-&gt;queSize++;\n}\n\n/* 末尾にエンキューする */\nvoid pushLast(ArrayDeque *deque, int num) {\n if (deque-&gt;queSize == capacity(deque)) {\n printf(\"両端キューが満杯です\\r\\n\");\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n int rear = dequeIndex(deque, deque-&gt;front + deque-&gt;queSize);\n // num をキューの末尾に追加する\n deque-&gt;nums[rear] = num;\n deque-&gt;queSize++;\n}\n\n/* キューの先頭要素にアクセス */\nint peekFirst(ArrayDeque *deque) {\n // アクセス例外:両端キューが空です\n assert(empty(deque) == 0);\n return deque-&gt;nums[deque-&gt;front];\n}\n\n/* キューの末尾要素にアクセスする */\nint peekLast(ArrayDeque *deque) {\n // アクセス例外:両端キューが空です\n assert(empty(deque) == 0);\n int last = dequeIndex(deque, deque-&gt;front + deque-&gt;queSize - 1);\n return deque-&gt;nums[last];\n}\n\n/* 先頭からデキューする */\nint popFirst(ArrayDeque *deque) {\n int num = peekFirst(deque);\n // 先頭ポインタを後ろに 1 つ移動する\n deque-&gt;front = dequeIndex(deque, deque-&gt;front + 1);\n deque-&gt;queSize--;\n return num;\n}\n\n/* 末尾からデキューする */\nint popLast(ArrayDeque *deque) {\n int num = peekLast(deque);\n deque-&gt;queSize--;\n return num;\n}\n\n/* 表示用の配列を返す */\nint *toArray(ArrayDeque *deque, int *queSize) {\n *queSize = deque-&gt;queSize;\n int *res = (int *)calloc(deque-&gt;queSize, sizeof(int));\n int j = deque-&gt;front;\n for (int i = 0; i &lt; deque-&gt;queSize; i++) {\n res[i] = deque-&gt;nums[j % deque-&gt;queCapacity];\n j++;\n }\n return res;\n}\n</code></pre> array_deque.kt<pre><code>/* コンストラクタ */\nclass ArrayDeque(capacity: Int) {\n private var nums: IntArray = IntArray(capacity) // 両端キューの要素を格納する配列\n private var front: Int = 0 // キューの先頭要素を指す先頭ポインタ\n private var queSize: Int = 0 // 両端キューの長さ\n\n /* 両端キューの容量を取得する */\n fun capacity(): Int {\n return nums.size\n }\n\n /* 両端キューの長さを取得する */\n fun size(): Int {\n return queSize\n }\n\n /* 両端キューが空かどうかを判定する */\n fun isEmpty(): Boolean {\n return queSize == 0\n }\n\n /* 循環配列のインデックスを計算する */\n private fun index(i: Int): Int {\n // 剰余演算を使い、配列の先頭と末尾をつなぐ\n // i が配列の末尾を越えたら、先頭に戻る\n // i が配列の先頭を越えたら、末尾に戻る\n return (i + capacity()) % capacity()\n }\n\n /* 先頭にエンキューする */\n fun pushFirst(num: Int) {\n if (queSize == capacity()) {\n println(\"両端キューが満杯です\")\n return\n }\n // 先頭ポインタを左に 1 つ移動する\n // 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n front = index(front - 1)\n // num をキューの先頭に追加する\n nums[front] = num\n queSize++\n }\n\n /* 末尾にエンキューする */\n fun pushLast(num: Int) {\n if (queSize == capacity()) {\n println(\"両端キューが満杯です\")\n return\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n val rear = index(front + queSize)\n // num をキューの末尾に追加する\n nums[rear] = num\n queSize++\n }\n\n /* 先頭からデキューする */\n fun popFirst(): Int {\n val num = peekFirst()\n // 先頭ポインタを後ろに 1 つ移動する\n front = index(front + 1)\n queSize--\n return num\n }\n\n /* 末尾からデキューする */\n fun popLast(): Int {\n val num = peekLast()\n queSize--\n return num\n }\n\n /* キューの先頭要素にアクセス */\n fun peekFirst(): Int {\n if (isEmpty()) throw IndexOutOfBoundsException()\n return nums[front]\n }\n\n /* キューの末尾要素にアクセスする */\n fun peekLast(): Int {\n if (isEmpty()) throw IndexOutOfBoundsException()\n // 末尾要素のインデックスを計算する\n val last = index(front + queSize - 1)\n return nums[last]\n }\n\n /* 表示用の配列を返す */\n fun toArray(): IntArray {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n val res = IntArray(queSize)\n var i = 0\n var j = front\n while (i &lt; queSize) {\n res[i] = nums[index(j)]\n i++\n j++\n }\n return res\n }\n}\n</code></pre> array_deque.rb<pre><code>### 循環配列で実装した両端キュー ###\nclass ArrayDeque\n ### 両端キューの長さを取得する ###\n attr_reader :size\n\n ### コンストラクタ ###\n def initialize(capacity)\n @nums = Array.new(capacity, 0)\n @front = 0\n @size = 0\n end\n\n ### 両端キューの容量を取得する ###\n def capacity\n @nums.length\n end\n\n ### 両端キューが空かどうかを判定する ###\n def is_empty?\n size.zero?\n end\n\n ### 先頭にエンキューする ###\n def push_first(num)\n if size == capacity\n puts '両端キューが満杯です'\n return\n end\n\n # 先頭ポインタを左に 1 つ移動する\n # 剰余演算により、front が配列の先頭を越えたら末尾に戻るようにする\n @front = index(@front - 1)\n # num をキューの先頭に追加する\n @nums[@front] = num\n @size += 1\n end\n\n ### 末尾にエンキューする ###\n def push_last(num)\n if size == capacity\n puts '両端キューが満杯です'\n return\n end\n\n # 末尾のインデックス + 1 を指す末尾ポインタを計算する\n rear = index(@front + size)\n # num をキューの末尾に追加する\n @nums[rear] = num\n @size += 1\n end\n\n ### 先頭からデキューする ###\n def pop_first\n num = peek_first\n # 先頭ポインタを後ろに 1 つ移動する\n @front = index(@front + 1)\n @size -= 1\n num\n end\n\n ### 末尾からデキューする ###\n def pop_last\n num = peek_last\n @size -= 1\n num\n end\n\n ### キューの先頭要素にアクセス ###\n def peek_first\n raise IndexError, '両端キューが空です' if is_empty?\n\n @nums[@front]\n end\n\n ### キューの末尾要素にアクセスする ###\n def peek_last\n raise IndexError, '両端キューが空です' if is_empty?\n\n # 末尾要素のインデックスを計算する\n last = index(@front + size - 1)\n @nums[last]\n end\n\n ### 表示用の配列を返す ###\n def to_array\n # 有効な長さの範囲内にあるリストの要素だけを変換する\n res = []\n for i in 0...size\n res &lt;&lt; @nums[index(@front + i)]\n end\n res\n end\n\n private\n\n ### 循環配列のインデックスを計算する ###\n def index(i)\n # 剰余演算を使い、配列の先頭と末尾をつなぐ\n # i が配列の末尾を越えたら、先頭に戻る\n # i が配列の先頭を越えたら、末尾に戻る\n (i + capacity) % capacity\n end\nend\n</code></pre>","path":["第 5 章   スタックとキュー","5.3   両端キュー"],"tags":[]},{"location":"chapter_stack_and_queue/deque/#533","level":2,"title":"5.3.3   両端キューの用途","text":"<p>両端キューはスタックとキューの両方の論理を持つため、両者のすべての用途を実現でき、さらに高い自由度も提供します。</p> <p>ソフトウェアの「元に戻す」は通常、変更のたびに操作をスタックへ <code>push</code> し、<code>pop</code> で取り消します。ただし、システム資源の制約から、通常は、保存できる履歴が例えば \\(50\\) 回までに制限されます。長さが \\(50\\) を超えたら、底、つまりキューの先頭から削除する必要があります。スタックではできないため、両端キューに置き換えます。「元に戻す」の中心的な論理は依然として先入れ後出しであり、両端キューは追加の処理をより柔軟に実現している点に注意してください。</p>","path":["第 5 章   スタックとキュー","5.3   両端キュー"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/","level":1,"title":"5.5   練習問題","text":"","path":["第 5 章   スタックとキュー","5.5   練習問題"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#551","level":2,"title":"5.5.1   知識の確認","text":"","path":["第 5 章   スタックとキュー","5.5   練習問題"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#1","level":3,"title":"1.   スタックとキューで最初に取り出される要素","text":"<p>空のスタック <code>S</code> と空のキュー <code>Q</code> を用意し、それぞれに同じ操作列を実行します。</p> <p>手順 1:<code>A</code> を追加します。 手順 2:<code>B</code> を追加します。 手順 3:要素を一つ取り出して記録します。 手順 4:<code>C</code> を追加します。 手順 5:空になるまで要素を取り出して記録します。</p> <p><code>S</code> と <code>Q</code> から要素が取り出される順序をそれぞれ書き、「先入れ後出し」または「先入れ先出し」を使って違いを説明してください。</p> 解答例 <p>スタック <code>S</code> から取り出す順序は <code>B、C、A</code> です。<code>A、B</code> を追加した後、直近に追加した <code>B</code> を先にポップし、次に <code>C</code> を追加します。 その後は <code>C、A</code> の順にポップするため、「先入れ後出し」です。</p> <p>キュー <code>Q</code> から取り出す順序は <code>A、B、C</code> です。<code>A、B</code> を追加した後、最も早く追加した <code>A</code> を先に取り出します。 次に <code>C</code> を追加し、その後は <code>B、C</code> の順に取り出すため、「先入れ先出し」です。</p>","path":["第 5 章   スタックとキュー","5.5   練習問題"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#2","level":3,"title":"2.   キューの末尾が配列の末端を越えたら","text":"<p>長さ 5 の循環配列でキューを実装します。配列のインデックスは <code>0~4</code> です。 現在は <code>front = 3</code>、<code>size = 2</code> で、キューの <code>A、B</code> はそれぞれインデックス 3、4 にあります。</p> <ol> <li>「<code>C</code> をエンキューする」とき、<code>C</code> はどのインデックスに置きますか。その後の <code>size</code> はいくつですか。</li> <li>続いて一回デキューすると、どの要素が取り出されますか。新しい <code>front</code> と <code>size</code> はそれぞれいくつですか。</li> <li>この時点で、先頭から末尾への論理的な順序はどうなっていますか。デキュー時に配列内のほかの要素を移動する必要はありますか。理由も説明してください。</li> </ol> 解答例 <ol> <li> <p>新しい要素の位置は <code>(front + size) % 5 = (3 + 2) % 5 = 0</code> なので、<code>C</code> はインデックス 0 に置きます。エンキュー後は <code>size = 3</code> です。</p> </li> <li> <p>デキューでは現在の先頭 <code>A</code> を取り出します。新しい先頭のインデックスは <code>(3 + 1) % 5 = 4</code> なので、<code>front = 4</code>、<code>size = 2</code> となります。</p> </li> <li> <p>有効な要素の論理的な順序は <code>B、C</code> です。<code>B</code> はインデックス 4、<code>C</code> はインデックス 0 にあります。 デキューでは <code>front</code> を進め、<code>size</code> を変更するだけです。循環配列では剰余を使ってインデックスを先頭に戻すため、 ほかの要素をまとめて前へ移動する必要はありません。</p> </li> </ol>","path":["第 5 章   スタックとキュー","5.5   練習問題"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#3","level":3,"title":"3.   両端キューの両側からの操作","text":"<p>ここでは、<code>push_first</code> は先頭への追加、<code>push_last</code> は末尾への追加、 <code>pop_first</code> は先頭からの取り出し、<code>pop_last</code> は末尾からの取り出しを表します。</p> <p>空の両端キュー <code>deq</code> に、次の操作を順に実行します。</p> <ol> <li><code>push_last(A)</code></li> <li><code>push_last(B)</code></li> <li><code>push_first(C)</code></li> <li><code>pop_last()</code></li> <li><code>push_last(D)</code></li> <li><code>pop_first()</code></li> </ol> <ol> <li>二回の取り出しで得られる要素はそれぞれ何ですか。</li> <li>すべての操作が終わると、先頭から末尾へどの要素が残っていますか。</li> <li>六つの操作を確認してください。末尾への追加と先頭からの削除だけを許すキューで、すべて実行できますか。できない場合は、実行できない操作を挙げてください。さらに、両端キューでは実行できるかどうかと、その理由を説明してください。</li> </ol> 解答例 <p>最初の三つの操作後、両端キューは先頭から末尾へ <code>[C, A, B]</code> となります。</p> <ol> <li> <p><code>pop_last()</code> で <code>B</code> を取り出します。<code>D</code> を追加すると <code>[C, A, D]</code> となり、 <code>pop_first()</code> で <code>C</code> を取り出します。</p> </li> <li> <p>最後に残るのは <code>[A, D]</code> です。</p> </li> <li> <p>末尾への追加と先頭からの削除だけを許すキューでは、すべての操作を実行できません。 手順 3 の <code>push_first(C)</code> は先頭への追加、手順 4 の <code>pop_last()</code> は末尾からの削除を求めるため、どちらもこのキューで可能な操作の範囲を超えています。 両端キューは両側から追加と削除ができるので、六つの操作をすべて実行できます。</p> </li> </ol>","path":["第 5 章   スタックとキュー","5.5   練習問題"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#552","level":2,"title":"5.5.2   プログラミング演習","text":"","path":["第 5 章   スタックとキュー","5.5   練習問題"],"tags":[]},{"location":"chapter_stack_and_queue/exercises/#1_1","level":3,"title":"1.   括弧列の検証","text":"<p>三種類の括弧 <code>()</code>、<code>[]</code>、<code>{}</code> だけからなる文字列 <code>s</code> が与えられます。スタックを使い、正しい括弧列かどうかを判定してください。</p> <p>正しい列では、各閉じ括弧の種類が、直近のまだ対応付けられていない開き括弧の種類と一致し、 かつ走査の終了後に対応付けられていない開き括弧が残っていない必要があります。判定結果を真偽値で返してください。</p> 解くためのヒント <ol> <li>「閉じ括弧から対応する開き括弧」への対応表を作れます</li> <li>閉じ括弧に出会ったら、まずスタックが空でないかを確認し、次に先頭の括弧と対応するかを調べます</li> <li>走査が終わったとき、スタックも空である必要があります</li> </ol> <p>LeetCode</p>","path":["第 5 章   スタックとキュー","5.5   練習問題"],"tags":[]},{"location":"chapter_stack_and_queue/queue/","level":1,"title":"5.2   キュー","text":"<p>キュー(queue)は先入れ先出しの規則に従う線形データ構造です。行列に並ぶ様子を模しており、新しく来た人が末尾に加わり、先頭の人から順に離れていきます。</p> <p>図 5-4 のように、前側を「先頭」、後ろ側を「末尾」と呼びます。末尾に要素を加える操作を「エンキュー」、先頭要素を削除する操作を「デキュー」と呼びます。</p> <p></p> <p> 図 5-4   キューの先入れ先出しの規則 </p>","path":["第 5 章   スタックとキュー","5.2   キュー"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#521","level":2,"title":"5.2.1   キューの主な操作","text":"<p>主な操作を表 5-2 に示します。メソッド名は言語ごとに異なる場合がありますが、ここではスタックと同じ名前を使います。</p> <p> 表 5-2   キューの操作効率 </p> メソッド名 説明 時間計算量 <code>push()</code> エンキュー、つまり末尾への要素の追加 \\(O(1)\\) <code>pop()</code> 先頭要素のデキュー \\(O(1)\\) <code>peek()</code> 先頭要素へのアクセス \\(O(1)\\) <p>言語が用意するキュークラスを、そのまま利用できます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby queue.py<pre><code>from collections import deque\n\n# キューを初期化\n# Python では通常、両端キュークラス deque をキューとして使う\n# queue.Queue() は本来のキュークラスだが、使いにくいため推奨しない\nque: deque[int] = deque()\n\n# 要素をエンキュー\nque.append(1)\nque.append(3)\nque.append(2)\nque.append(5)\nque.append(4)\n\n# 先頭要素にアクセス\nfront: int = que[0]\n\n# 要素をデキュー\npop: int = que.popleft()\n\n# キューの長さを取得\nsize: int = len(que)\n\n# キューが空かどうかを判定\nis_empty: bool = len(que) == 0\n</code></pre> queue.cpp<pre><code>/* キューを初期化 */\nqueue&lt;int&gt; queue;\n\n/* 要素をエンキュー */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* 先頭要素にアクセス */\nint front = queue.front();\n\n/* 要素をデキュー */\nqueue.pop();\n\n/* キューの長さを取得 */\nint size = queue.size();\n\n/* キューが空かどうかを判定 */\nbool empty = queue.empty();\n</code></pre> queue.java<pre><code>/* キューを初期化 */\nQueue&lt;Integer&gt; queue = new LinkedList&lt;&gt;();\n\n/* 要素をエンキュー */\nqueue.offer(1);\nqueue.offer(3);\nqueue.offer(2);\nqueue.offer(5);\nqueue.offer(4);\n\n/* 先頭要素にアクセス */\nint peek = queue.peek();\n\n/* 要素をデキュー */\nint pop = queue.poll();\n\n/* キューの長さを取得 */\nint size = queue.size();\n\n/* キューが空かどうかを判定 */\nboolean isEmpty = queue.isEmpty();\n</code></pre> queue.cs<pre><code>/* キューを初期化 */\nQueue&lt;int&gt; queue = new();\n\n/* 要素をエンキュー */\nqueue.Enqueue(1);\nqueue.Enqueue(3);\nqueue.Enqueue(2);\nqueue.Enqueue(5);\nqueue.Enqueue(4);\n\n/* 先頭要素にアクセス */\nint peek = queue.Peek();\n\n/* 要素をデキュー */\nint pop = queue.Dequeue();\n\n/* キューの長さを取得 */\nint size = queue.Count;\n\n/* キューが空かどうかを判定 */\nbool isEmpty = queue.Count == 0;\n</code></pre> queue_test.go<pre><code>/* キューを初期化 */\n// Go では list をキューとして使う\nqueue := list.New()\n\n/* 要素をエンキュー */\nqueue.PushBack(1)\nqueue.PushBack(3)\nqueue.PushBack(2)\nqueue.PushBack(5)\nqueue.PushBack(4)\n\n/* 先頭要素にアクセス */\npeek := queue.Front()\n\n/* 要素をデキュー */\npop := queue.Front()\nqueue.Remove(pop)\n\n/* キューの長さを取得 */\nsize := queue.Len()\n\n/* キューが空かどうかを判定 */\nisEmpty := queue.Len() == 0\n</code></pre> queue.swift<pre><code>/* キューを初期化 */\n// Swift には組み込みのキュークラスがないため、Array をキューとして使える\nvar queue: [Int] = []\n\n/* 要素をエンキュー */\nqueue.append(1)\nqueue.append(3)\nqueue.append(2)\nqueue.append(5)\nqueue.append(4)\n\n/* 先頭要素にアクセス */\nlet peek = queue.first!\n\n/* 要素をデキュー */\n// 配列なので removeFirst の計算量は O(n)\nlet pop = queue.removeFirst()\n\n/* キューの長さを取得 */\nlet size = queue.count\n\n/* キューが空かどうかを判定 */\nlet isEmpty = queue.isEmpty\n</code></pre> queue.js<pre><code>/* キューを初期化 */\n// JavaScript には組み込みのキューがないため、Array をキューとして使える\nconst queue = [];\n\n/* 要素をエンキュー */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* 先頭要素にアクセス */\nconst peek = queue[0];\n\n/* 要素をデキュー */\n// 内部が配列なので shift() の時間計算量は O(n)\nconst pop = queue.shift();\n\n/* キューの長さを取得 */\nconst size = queue.length;\n\n/* キューが空かどうかを判定 */\nconst empty = queue.length === 0;\n</code></pre> queue.ts<pre><code>/* キューを初期化 */\n// TypeScript には組み込みのキューがないため、Array をキューとして使える\nconst queue: number[] = [];\n\n/* 要素をエンキュー */\nqueue.push(1);\nqueue.push(3);\nqueue.push(2);\nqueue.push(5);\nqueue.push(4);\n\n/* 先頭要素にアクセス */\nconst peek = queue[0];\n\n/* 要素をデキュー */\n// 内部が配列なので shift() の時間計算量は O(n)\nconst pop = queue.shift();\n\n/* キューの長さを取得 */\nconst size = queue.length;\n\n/* キューが空かどうかを判定 */\nconst empty = queue.length === 0;\n</code></pre> queue.dart<pre><code>/* キューを初期化 */\n// Dart の Queue は両端キューであり、通常のキューとしても使える\nQueue&lt;int&gt; queue = Queue();\n\n/* 要素をエンキュー */\nqueue.add(1);\nqueue.add(3);\nqueue.add(2);\nqueue.add(5);\nqueue.add(4);\n\n/* 先頭要素にアクセス */\nint peek = queue.first;\n\n/* 要素をデキュー */\nint pop = queue.removeFirst();\n\n/* キューの長さを取得 */\nint size = queue.length;\n\n/* キューが空かどうかを判定 */\nbool isEmpty = queue.isEmpty;\n</code></pre> queue.rs<pre><code>/* 両端キューを初期化 */\n// Rust では両端キューを通常のキューとして使う\nlet mut deque: VecDeque&lt;u32&gt; = VecDeque::new();\n\n/* 要素をエンキュー */\ndeque.push_back(1);\ndeque.push_back(3);\ndeque.push_back(2);\ndeque.push_back(5);\ndeque.push_back(4);\n\n/* 先頭要素にアクセス */\nif let Some(front) = deque.front() {\n}\n\n/* 要素をデキュー */\nif let Some(pop) = deque.pop_front() {\n}\n\n/* キューの長さを取得 */\nlet size = deque.len();\n\n/* キューが空かどうかを判定 */\nlet is_empty = deque.is_empty();\n</code></pre> queue.c<pre><code>// C には組み込みのキューがない\n</code></pre> queue.kt<pre><code>/* キューを初期化 */\nval queue = LinkedList&lt;Int&gt;()\n\n/* 要素をエンキュー */\nqueue.offer(1)\nqueue.offer(3)\nqueue.offer(2)\nqueue.offer(5)\nqueue.offer(4)\n\n/* 先頭要素にアクセス */\nval peek = queue.peek()\n\n/* 要素をデキュー */\nval pop = queue.poll()\n\n/* キューの長さを取得 */\nval size = queue.size\n\n/* キューが空かどうかを判定 */\nval isEmpty = queue.isEmpty()\n</code></pre> queue.rb<pre><code># キューを初期化\n# Ruby の組み込みキュー(Thread::Queue)には peek や走査のメソッドがないため、Array をキューとして使える\nqueue = []\n\n# 要素をエンキュー\nqueue.push(1)\nqueue.push(3)\nqueue.push(2)\nqueue.push(5)\nqueue.push(4)\n\n# キューの要素にアクセス\npeek = queue.first\n\n# 要素をデキュー\n# 配列なので Array#shift の時間計算量は O(n) であることに注意\npop = queue.shift\n\n# キューの長さを取得\nsize = queue.length\n\n# キューが空かどうかを判定\nis_empty = queue.empty?\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 5 章   スタックとキュー","5.2   キュー"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#522","level":2,"title":"5.2.2   キューの実装","text":"<p>キューを実装するには、一方の端で追加し、もう一方の端で削除できるデータ構造が必要です。連結リストも配列も、この条件を満たします。</p>","path":["第 5 章   スタックとキュー","5.2   キュー"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#1","level":3,"title":"1.   連結リストによる実装","text":"<p>図 5-5 のように、連結リストの先頭ノードと末尾ノードを、キューの先頭と末尾に対応させます。末尾では追加だけ、先頭では削除だけを行います。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt; <p></p> <p></p> <p></p> <p> 図 5-5   連結リストによるキューのエンキューとデキュー </p> <p>連結リストを使うコードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_queue.py<pre><code>class LinkedListQueue:\n \"\"\"連結リストで実装したキュー\"\"\"\n\n def __init__(self):\n \"\"\"コンストラクタ\"\"\"\n self._front: ListNode | None = None # 先頭ノード front\n self._rear: ListNode | None = None # 末尾ノード rear\n self._size: int = 0\n\n def size(self) -&gt; int:\n \"\"\"キューの長さを取得\"\"\"\n return self._size\n\n def is_empty(self) -&gt; bool:\n \"\"\"キューが空か判定\"\"\"\n return self._size == 0\n\n def push(self, num: int):\n \"\"\"エンキュー\"\"\"\n # 末尾ノードの後ろに num を追加する\n node = ListNode(num)\n # キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n if self._front is None:\n self._front = node\n self._rear = node\n # キューが空でない場合、このノードを末尾ノードの後ろに追加する\n else:\n self._rear.next = node\n self._rear = node\n self._size += 1\n\n def pop(self) -&gt; int:\n \"\"\"デキュー\"\"\"\n num = self.peek()\n # 先頭ノードを削除する\n self._front = self._front.next\n self._size -= 1\n return num\n\n def peek(self) -&gt; int:\n \"\"\"キューの先頭要素にアクセス\"\"\"\n if self.is_empty():\n raise IndexError(\"キューが空です\")\n return self._front.val\n\n def to_list(self) -&gt; list[int]:\n \"\"\"表示用のリストに変換する\"\"\"\n queue = []\n temp = self._front\n while temp:\n queue.append(temp.val)\n temp = temp.next\n return queue\n</code></pre> linkedlist_queue.cpp<pre><code>/* 連結リストで実装したキュー */\nclass LinkedListQueue {\n private:\n ListNode *front, *rear; // 先頭ノード front 、末尾ノード rear\n int queSize;\n\n public:\n LinkedListQueue() {\n front = nullptr;\n rear = nullptr;\n queSize = 0;\n }\n\n ~LinkedListQueue() {\n // 連結リストを走査してノードを削除し、メモリを解放する\n freeMemoryLinkedList(front);\n }\n\n /* キューの長さを取得 */\n int size() {\n return queSize;\n }\n\n /* キューが空か判定 */\n bool isEmpty() {\n return queSize == 0;\n }\n\n /* エンキュー */\n void push(int num) {\n // 末尾ノードの後ろに num を追加する\n ListNode *node = new ListNode(num);\n // キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n if (front == nullptr) {\n front = node;\n rear = node;\n }\n // キューが空でない場合、このノードを末尾ノードの後ろに追加する\n else {\n rear-&gt;next = node;\n rear = node;\n }\n queSize++;\n }\n\n /* デキュー */\n int pop() {\n int num = peek();\n // 先頭ノードを削除する\n ListNode *tmp = front;\n front = front-&gt;next;\n // メモリを解放する\n delete tmp;\n queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n int peek() {\n if (size() == 0)\n throw out_of_range(\"キューが空です\");\n return front-&gt;val;\n }\n\n /* 連結リストを Vector に変換して返す */\n vector&lt;int&gt; toVector() {\n ListNode *node = front;\n vector&lt;int&gt; res(size());\n for (int i = 0; i &lt; res.size(); i++) {\n res[i] = node-&gt;val;\n node = node-&gt;next;\n }\n return res;\n }\n};\n</code></pre> linkedlist_queue.java<pre><code>/* 連結リストで実装したキュー */\nclass LinkedListQueue {\n private ListNode front, rear; // 先頭ノード front 、末尾ノード rear\n private int queSize = 0;\n\n public LinkedListQueue() {\n front = null;\n rear = null;\n }\n\n /* キューの長さを取得 */\n public int size() {\n return queSize;\n }\n\n /* キューが空か判定 */\n public boolean isEmpty() {\n return size() == 0;\n }\n\n /* エンキュー */\n public void push(int num) {\n // 末尾ノードの後ろに num を追加する\n ListNode node = new ListNode(num);\n // キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n if (front == null) {\n front = node;\n rear = node;\n // キューが空でない場合、このノードを末尾ノードの後ろに追加する\n } else {\n rear.next = node;\n rear = node;\n }\n queSize++;\n }\n\n /* デキュー */\n public int pop() {\n int num = peek();\n // 先頭ノードを削除する\n front = front.next;\n queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n public int peek() {\n if (isEmpty())\n throw new IndexOutOfBoundsException();\n return front.val;\n }\n\n /* 連結リストを Array に変換して返す */\n public int[] toArray() {\n ListNode node = front;\n int[] res = new int[size()];\n for (int i = 0; i &lt; res.length; i++) {\n res[i] = node.val;\n node = node.next;\n }\n return res;\n }\n}\n</code></pre> linkedlist_queue.cs<pre><code>/* 連結リストで実装したキュー */\nclass LinkedListQueue {\n ListNode? front, rear; // 先頭ノード front 、末尾ノード rear \n int queSize = 0;\n\n public LinkedListQueue() {\n front = null;\n rear = null;\n }\n\n /* キューの長さを取得 */\n public int Size() {\n return queSize;\n }\n\n /* キューが空か判定 */\n public bool IsEmpty() {\n return Size() == 0;\n }\n\n /* エンキュー */\n public void Push(int num) {\n // 末尾ノードの後ろに num を追加する\n ListNode node = new(num);\n // キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n if (front == null) {\n front = node;\n rear = node;\n // キューが空でない場合、このノードを末尾ノードの後ろに追加する\n } else if (rear != null) {\n rear.next = node;\n rear = node;\n }\n queSize++;\n }\n\n /* デキュー */\n public int Pop() {\n int num = Peek();\n // 先頭ノードを削除する\n front = front?.next;\n queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n public int Peek() {\n if (IsEmpty())\n throw new Exception();\n return front!.val;\n }\n\n /* 連結リストを Array に変換して返す */\n public int[] ToArray() {\n if (front == null)\n return [];\n\n ListNode? node = front;\n int[] res = new int[Size()];\n for (int i = 0; i &lt; res.Length; i++) {\n res[i] = node!.val;\n node = node.next;\n }\n return res;\n }\n}\n</code></pre> linkedlist_queue.go<pre><code>/* 連結リストで実装したキュー */\ntype linkedListQueue struct {\n // 組み込みパッケージ list でキューを実装する\n data *list.List\n}\n\n/* キューを初期化する */\nfunc newLinkedListQueue() *linkedListQueue {\n return &amp;linkedListQueue{\n data: list.New(),\n }\n}\n\n/* エンキュー */\nfunc (s *linkedListQueue) push(value any) {\n s.data.PushBack(value)\n}\n\n/* デキュー */\nfunc (s *linkedListQueue) pop() any {\n if s.isEmpty() {\n return nil\n }\n e := s.data.Front()\n s.data.Remove(e)\n return e.Value\n}\n\n/* キューの先頭要素にアクセス */\nfunc (s *linkedListQueue) peek() any {\n if s.isEmpty() {\n return nil\n }\n e := s.data.Front()\n return e.Value\n}\n\n/* キューの長さを取得 */\nfunc (s *linkedListQueue) size() int {\n return s.data.Len()\n}\n\n/* キューが空か判定 */\nfunc (s *linkedListQueue) isEmpty() bool {\n return s.data.Len() == 0\n}\n\n/* 表示用の List を取得する */\nfunc (s *linkedListQueue) toList() *list.List {\n return s.data\n}\n</code></pre> linkedlist_queue.swift<pre><code>/* 連結リストで実装したキュー */\nclass LinkedListQueue {\n private var front: ListNode? // 先頭ノード\n private var rear: ListNode? // 末尾ノード\n private var _size: Int\n\n init() {\n _size = 0\n }\n\n /* キューの長さを取得 */\n func size() -&gt; Int {\n _size\n }\n\n /* キューが空か判定 */\n func isEmpty() -&gt; Bool {\n size() == 0\n }\n\n /* エンキュー */\n func push(num: Int) {\n // 末尾ノードの後ろに num を追加する\n let node = ListNode(x: num)\n // キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n if front == nil {\n front = node\n rear = node\n }\n // キューが空でない場合、このノードを末尾ノードの後ろに追加する\n else {\n rear?.next = node\n rear = node\n }\n _size += 1\n }\n\n /* デキュー */\n @discardableResult\n func pop() -&gt; Int {\n let num = peek()\n // 先頭ノードを削除する\n front = front?.next\n _size -= 1\n return num\n }\n\n /* キューの先頭要素にアクセス */\n func peek() -&gt; Int {\n if isEmpty() {\n fatalError(\"キューが空です\")\n }\n return front!.val\n }\n\n /* 連結リストを Array に変換して返す */\n func toArray() -&gt; [Int] {\n var node = front\n var res = Array(repeating: 0, count: size())\n for i in res.indices {\n res[i] = node!.val\n node = node?.next\n }\n return res\n }\n}\n</code></pre> linkedlist_queue.js<pre><code>/* 連結リストで実装したキュー */\nclass LinkedListQueue {\n #front; // 先頭ノード #front\n #rear; // 末尾ノード #rear\n #queSize = 0;\n\n constructor() {\n this.#front = null;\n this.#rear = null;\n }\n\n /* キューの長さを取得 */\n get size() {\n return this.#queSize;\n }\n\n /* キューが空か判定 */\n isEmpty() {\n return this.size === 0;\n }\n\n /* エンキュー */\n push(num) {\n // 末尾ノードの後ろに num を追加する\n const node = new ListNode(num);\n // キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n if (!this.#front) {\n this.#front = node;\n this.#rear = node;\n // キューが空でない場合、このノードを末尾ノードの後ろに追加する\n } else {\n this.#rear.next = node;\n this.#rear = node;\n }\n this.#queSize++;\n }\n\n /* デキュー */\n pop() {\n const num = this.peek();\n // 先頭ノードを削除する\n this.#front = this.#front.next;\n this.#queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n peek() {\n if (this.size === 0) throw new Error('キューが空です');\n return this.#front.val;\n }\n\n /* 連結リストを Array に変換して返す */\n toArray() {\n let node = this.#front;\n const res = new Array(this.size);\n for (let i = 0; i &lt; res.length; i++) {\n res[i] = node.val;\n node = node.next;\n }\n return res;\n }\n}\n</code></pre> linkedlist_queue.ts<pre><code>/* 連結リストで実装したキュー */\nclass LinkedListQueue {\n private front: ListNode | null; // 先頭ノード front\n private rear: ListNode | null; // 末尾ノード rear\n private queSize: number = 0;\n\n constructor() {\n this.front = null;\n this.rear = null;\n }\n\n /* キューの長さを取得 */\n get size(): number {\n return this.queSize;\n }\n\n /* キューが空か判定 */\n isEmpty(): boolean {\n return this.size === 0;\n }\n\n /* エンキュー */\n push(num: number): void {\n // 末尾ノードの後ろに num を追加する\n const node = new ListNode(num);\n // キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n if (!this.front) {\n this.front = node;\n this.rear = node;\n // キューが空でない場合、このノードを末尾ノードの後ろに追加する\n } else {\n this.rear!.next = node;\n this.rear = node;\n }\n this.queSize++;\n }\n\n /* デキュー */\n pop(): number {\n const num = this.peek();\n if (!this.front) throw new Error('キューが空です');\n // 先頭ノードを削除する\n this.front = this.front.next;\n this.queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n peek(): number {\n if (this.size === 0) throw new Error('キューが空です');\n return this.front!.val;\n }\n\n /* 連結リストを Array に変換して返す */\n toArray(): number[] {\n let node = this.front;\n const res = new Array&lt;number&gt;(this.size);\n for (let i = 0; i &lt; res.length; i++) {\n res[i] = node!.val;\n node = node!.next;\n }\n return res;\n }\n}\n</code></pre> linkedlist_queue.dart<pre><code>/* 連結リストで実装したキュー */\nclass LinkedListQueue {\n ListNode? _front; // 先頭ノード _front\n ListNode? _rear; // 末尾ノード _rear\n int _queSize = 0; // キューの長さ\n\n LinkedListQueue() {\n _front = null;\n _rear = null;\n }\n\n /* キューの長さを取得 */\n int size() {\n return _queSize;\n }\n\n /* キューが空か判定 */\n bool isEmpty() {\n return _queSize == 0;\n }\n\n /* エンキュー */\n void push(int _num) {\n // 末尾ノードの後ろに _num を追加する\n final node = ListNode(_num);\n // キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n if (_front == null) {\n _front = node;\n _rear = node;\n } else {\n // キューが空でない場合、このノードを末尾ノードの後ろに追加する\n _rear!.next = node;\n _rear = node;\n }\n _queSize++;\n }\n\n /* デキュー */\n int pop() {\n final int _num = peek();\n // 先頭ノードを削除する\n _front = _front!.next;\n _queSize--;\n return _num;\n }\n\n /* キューの先頭要素にアクセス */\n int peek() {\n if (_queSize == 0) {\n throw Exception('キューが空です');\n }\n return _front!.val;\n }\n\n /* 連結リストを Array に変換して返す */\n List&lt;int&gt; toArray() {\n ListNode? node = _front;\n final List&lt;int&gt; queue = [];\n while (node != null) {\n queue.add(node.val);\n node = node.next;\n }\n return queue;\n }\n}\n</code></pre> linkedlist_queue.rs<pre><code>/* 連結リストで実装したキュー */\n#[allow(dead_code)]\npub struct LinkedListQueue&lt;T&gt; {\n front: Option&lt;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;, // 先頭ノード front\n rear: Option&lt;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;, // 末尾ノード rear\n que_size: usize, // キューの長さ\n}\n\nimpl&lt;T: Copy&gt; LinkedListQueue&lt;T&gt; {\n pub fn new() -&gt; Self {\n Self {\n front: None,\n rear: None,\n que_size: 0,\n }\n }\n\n /* キューの長さを取得 */\n pub fn size(&amp;self) -&gt; usize {\n return self.que_size;\n }\n\n /* キューが空か判定 */\n pub fn is_empty(&amp;self) -&gt; bool {\n return self.que_size == 0;\n }\n\n /* エンキュー */\n pub fn push(&amp;mut self, num: T) {\n // 末尾ノードの後ろに num を追加する\n let new_rear = ListNode::new(num);\n match self.rear.take() {\n // キューが空でない場合、このノードを末尾ノードの後ろに追加する\n Some(old_rear) =&gt; {\n old_rear.borrow_mut().next = Some(new_rear.clone());\n self.rear = Some(new_rear);\n }\n // キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n None =&gt; {\n self.front = Some(new_rear.clone());\n self.rear = Some(new_rear);\n }\n }\n self.que_size += 1;\n }\n\n /* デキュー */\n pub fn pop(&amp;mut self) -&gt; Option&lt;T&gt; {\n self.front.take().map(|old_front| {\n match old_front.borrow_mut().next.take() {\n Some(new_front) =&gt; {\n self.front = Some(new_front);\n }\n None =&gt; {\n self.rear.take();\n }\n }\n self.que_size -= 1;\n old_front.borrow().val\n })\n }\n\n /* キューの先頭要素にアクセス */\n pub fn peek(&amp;self) -&gt; Option&lt;&amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt; {\n self.front.as_ref()\n }\n\n /* 連結リストを Array に変換して返す */\n pub fn to_array(&amp;self, head: Option&lt;&amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;) -&gt; Vec&lt;T&gt; {\n let mut res: Vec&lt;T&gt; = Vec::new();\n\n fn recur&lt;T: Copy&gt;(cur: Option&lt;&amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;, res: &amp;mut Vec&lt;T&gt;) {\n if let Some(cur) = cur {\n res.push(cur.borrow().val);\n recur(cur.borrow().next.as_ref(), res);\n }\n }\n\n recur(head, &amp;mut res);\n\n res\n }\n}\n</code></pre> linkedlist_queue.c<pre><code>/* 連結リストで実装したキュー */\ntypedef struct {\n ListNode *front, *rear;\n int queSize;\n} LinkedListQueue;\n\n/* コンストラクタ */\nLinkedListQueue *newLinkedListQueue() {\n LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue));\n queue-&gt;front = NULL;\n queue-&gt;rear = NULL;\n queue-&gt;queSize = 0;\n return queue;\n}\n\n/* デストラクタ */\nvoid delLinkedListQueue(LinkedListQueue *queue) {\n // すべてのノードを解放する\n while (queue-&gt;front != NULL) {\n ListNode *tmp = queue-&gt;front;\n queue-&gt;front = queue-&gt;front-&gt;next;\n free(tmp);\n }\n // queue 構造体を解放する\n free(queue);\n}\n\n/* キューの長さを取得 */\nint size(LinkedListQueue *queue) {\n return queue-&gt;queSize;\n}\n\n/* キューが空か判定 */\nbool empty(LinkedListQueue *queue) {\n return (size(queue) == 0);\n}\n\n/* エンキュー */\nvoid push(LinkedListQueue *queue, int num) {\n // 末尾ノードに node を追加する\n ListNode *node = newListNode(num);\n // キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n if (queue-&gt;front == NULL) {\n queue-&gt;front = node;\n queue-&gt;rear = node;\n }\n // キューが空でない場合、このノードを末尾ノードの後ろに追加する\n else {\n queue-&gt;rear-&gt;next = node;\n queue-&gt;rear = node;\n }\n queue-&gt;queSize++;\n}\n\n/* キューの先頭要素にアクセス */\nint peek(LinkedListQueue *queue) {\n assert(size(queue) &amp;&amp; queue-&gt;front);\n return queue-&gt;front-&gt;val;\n}\n\n/* デキュー */\nint pop(LinkedListQueue *queue) {\n int num = peek(queue);\n ListNode *tmp = queue-&gt;front;\n queue-&gt;front = queue-&gt;front-&gt;next;\n free(tmp);\n queue-&gt;queSize--;\n return num;\n}\n\n/* キューを表示する */\nvoid printLinkedListQueue(LinkedListQueue *queue) {\n int *arr = malloc(sizeof(int) * queue-&gt;queSize);\n // 連結リストのデータを配列にコピーする\n int i;\n ListNode *node;\n for (i = 0, node = queue-&gt;front; i &lt; queue-&gt;queSize; i++) {\n arr[i] = node-&gt;val;\n node = node-&gt;next;\n }\n printArray(arr, queue-&gt;queSize);\n free(arr);\n}\n</code></pre> linkedlist_queue.kt<pre><code>/* 連結リストで実装したキュー */\nclass LinkedListQueue(\n // 先頭ノード front 、末尾ノード rear\n private var front: ListNode? = null,\n private var rear: ListNode? = null,\n private var queSize: Int = 0\n) {\n\n /* キューの長さを取得 */\n fun size(): Int {\n return queSize\n }\n\n /* キューが空か判定 */\n fun isEmpty(): Boolean {\n return size() == 0\n }\n\n /* エンキュー */\n fun push(num: Int) {\n // 末尾ノードの後ろに num を追加する\n val node = ListNode(num)\n // キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n if (front == null) {\n front = node\n rear = node\n // キューが空でない場合、このノードを末尾ノードの後ろに追加する\n } else {\n rear?.next = node\n rear = node\n }\n queSize++\n }\n\n /* デキュー */\n fun pop(): Int {\n val num = peek()\n // 先頭ノードを削除する\n front = front?.next\n queSize--\n return num\n }\n\n /* キューの先頭要素にアクセス */\n fun peek(): Int {\n if (isEmpty()) throw IndexOutOfBoundsException()\n return front!!._val\n }\n\n /* 連結リストを Array に変換して返す */\n fun toArray(): IntArray {\n var node = front\n val res = IntArray(size())\n for (i in res.indices) {\n res[i] = node!!._val\n node = node.next\n }\n return res\n }\n}\n</code></pre> linkedlist_queue.rb<pre><code>### 連結リストで実装したキュー ###\nclass LinkedListQueue\n ### キューの長さを取得 ###\n attr_reader :size\n\n ### コンストラクタ ###\n def initialize\n @front = nil # 先頭ノード front\n @rear = nil # 末尾ノード rear\n @size = 0\n end\n\n ### キューが空か判定 ###\n def is_empty?\n @front.nil?\n end\n\n ### エンキュー ###\n def push(num)\n # 末尾ノードの後ろに num を追加する\n node = ListNode.new(num)\n\n # キューが空の場合、先頭と末尾の両方がこのノードを指すようにする\n if @front.nil?\n @front = node\n @rear = node\n # キューが空でない場合、このノードを末尾ノードの後ろに追加する\n else\n @rear.next = node\n @rear = node\n end\n\n @size += 1\n end\n\n ### デキュー ###\n def pop\n num = peek\n # 先頭ノードを削除する\n @front = @front.next\n @size -= 1\n num\n end\n\n ### キューの先頭要素にアクセス ###\n def peek\n raise IndexError, 'キューが空です' if is_empty?\n\n @front.val\n end\n\n ### 連結リストを Array に変換して返す ###\n def to_array\n queue = []\n temp = @front\n while temp\n queue &lt;&lt; temp.val\n temp = temp.next\n end\n queue\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 5 章   スタックとキュー","5.2   キュー"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#2","level":3,"title":"2.   配列による実装","text":"<p>配列の先頭要素の削除は時間計算量が \\(O(n)\\) なので、デキューの効率が低くなります。ただし、次の工夫で避けられます。</p> <p>変数 <code>front</code> で先頭要素のインデックスを示し、変数 <code>size</code> でキューの長さを記録します。<code>rear = front + size</code> と定義すると、<code>rear</code> は末尾要素の次の位置を指します。</p> <p>この設計では、要素を含む有効区間は <code>[front, rear - 1]</code> です。各操作の実装を図 5-6 に示します。</p> <ul> <li>エンキュー:インデックス <code>rear</code> に入力要素を代入し、<code>size</code> を 1 増やします。</li> <li>デキュー:<code>front</code> を 1 増やし、<code>size</code> を 1 減らすだけです。</li> </ul> <p>どちらも一度の操作で済み、時間計算量はともに \\(O(1)\\) です。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt; <p></p> <p></p> <p></p> <p> 図 5-6   配列によるキューのエンキューとデキュー </p> <p>一つ問題があります。追加と削除を続けると <code>front</code> と <code>rear</code> が右へ移動し、配列の末尾に達すると、それ以上進めなくなります。これを解決するため、配列の両端がつながった「循環配列」と見なします。</p> <p>循環配列では、<code>front</code> や <code>rear</code> が末尾を越えたら先頭に戻って走査を続けます。この周期性は剰余演算で実現できます。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_queue.py<pre><code>class ArrayQueue:\n \"\"\"循環配列で実装したキュー\"\"\"\n\n def __init__(self, size: int):\n \"\"\"コンストラクタ\"\"\"\n self._nums: list[int] = [0] * size # キューの要素を格納する配列\n self._front: int = 0 # キューの先頭要素を指す先頭ポインタ\n self._size: int = 0 # キューの長さ\n\n def capacity(self) -&gt; int:\n \"\"\"キューの容量を取得する\"\"\"\n return len(self._nums)\n\n def size(self) -&gt; int:\n \"\"\"キューの長さを取得\"\"\"\n return self._size\n\n def is_empty(self) -&gt; bool:\n \"\"\"キューが空か判定\"\"\"\n return self._size == 0\n\n def push(self, num: int):\n \"\"\"エンキュー\"\"\"\n if self._size == self.capacity():\n raise IndexError(\"キューが満杯です\")\n # 末尾のインデックス + 1 を指す末尾ポインタを計算する\n # 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n rear: int = (self._front + self._size) % self.capacity()\n # num をキューの末尾に追加する\n self._nums[rear] = num\n self._size += 1\n\n def pop(self) -&gt; int:\n \"\"\"デキュー\"\"\"\n num: int = self.peek()\n # 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n self._front = (self._front + 1) % self.capacity()\n self._size -= 1\n return num\n\n def peek(self) -&gt; int:\n \"\"\"キューの先頭要素にアクセス\"\"\"\n if self.is_empty():\n raise IndexError(\"キューが空です\")\n return self._nums[self._front]\n\n def to_list(self) -&gt; list[int]:\n \"\"\"表示用のリストを返す\"\"\"\n res = [0] * self.size()\n j: int = self._front\n for i in range(self.size()):\n res[i] = self._nums[(j % self.capacity())]\n j += 1\n return res\n</code></pre> array_queue.cpp<pre><code>/* 循環配列で実装したキュー */\nclass ArrayQueue {\n private:\n int *nums; // キューの要素を格納する配列\n int front; // キューの先頭要素を指す先頭ポインタ\n int queSize; // キューの長さ\n int queCapacity; // キューの容量\n\n public:\n ArrayQueue(int capacity) {\n // 配列を初期化する\n nums = new int[capacity];\n queCapacity = capacity;\n front = queSize = 0;\n }\n\n ~ArrayQueue() {\n delete[] nums;\n }\n\n /* キューの容量を取得する */\n int capacity() {\n return queCapacity;\n }\n\n /* キューの長さを取得 */\n int size() {\n return queSize;\n }\n\n /* キューが空か判定 */\n bool isEmpty() {\n return size() == 0;\n }\n\n /* エンキュー */\n void push(int num) {\n if (queSize == queCapacity) {\n cout &lt;&lt; \"キューが満杯です\" &lt;&lt; endl;\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n // 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n int rear = (front + queSize) % queCapacity;\n // num をキューの末尾に追加する\n nums[rear] = num;\n queSize++;\n }\n\n /* デキュー */\n int pop() {\n int num = peek();\n // 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n front = (front + 1) % queCapacity;\n queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n int peek() {\n if (isEmpty())\n throw out_of_range(\"キューが空です\");\n return nums[front];\n }\n\n /* 配列を Vector に変換して返す */\n vector&lt;int&gt; toVector() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n vector&lt;int&gt; arr(queSize);\n for (int i = 0, j = front; i &lt; queSize; i++, j++) {\n arr[i] = nums[j % queCapacity];\n }\n return arr;\n }\n};\n</code></pre> array_queue.java<pre><code>/* 循環配列で実装したキュー */\nclass ArrayQueue {\n private int[] nums; // キューの要素を格納する配列\n private int front; // キューの先頭要素を指す先頭ポインタ\n private int queSize; // キューの長さ\n\n public ArrayQueue(int capacity) {\n nums = new int[capacity];\n front = queSize = 0;\n }\n\n /* キューの容量を取得する */\n public int capacity() {\n return nums.length;\n }\n\n /* キューの長さを取得 */\n public int size() {\n return queSize;\n }\n\n /* キューが空か判定 */\n public boolean isEmpty() {\n return queSize == 0;\n }\n\n /* エンキュー */\n public void push(int num) {\n if (queSize == capacity()) {\n System.out.println(\"キューが満杯です\");\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n // 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n int rear = (front + queSize) % capacity();\n // num をキューの末尾に追加する\n nums[rear] = num;\n queSize++;\n }\n\n /* デキュー */\n public int pop() {\n int num = peek();\n // 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n front = (front + 1) % capacity();\n queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n public int peek() {\n if (isEmpty())\n throw new IndexOutOfBoundsException();\n return nums[front];\n }\n\n /* 配列を返す */\n public int[] toArray() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n int[] res = new int[queSize];\n for (int i = 0, j = front; i &lt; queSize; i++, j++) {\n res[i] = nums[j % capacity()];\n }\n return res;\n }\n}\n</code></pre> array_queue.cs<pre><code>/* 循環配列で実装したキュー */\nclass ArrayQueue {\n int[] nums; // キューの要素を格納する配列\n int front; // キューの先頭要素を指す先頭ポインタ\n int queSize; // キューの長さ\n\n public ArrayQueue(int capacity) {\n nums = new int[capacity];\n front = queSize = 0;\n }\n\n /* キューの容量を取得する */\n int Capacity() {\n return nums.Length;\n }\n\n /* キューの長さを取得 */\n public int Size() {\n return queSize;\n }\n\n /* キューが空か判定 */\n public bool IsEmpty() {\n return queSize == 0;\n }\n\n /* エンキュー */\n public void Push(int num) {\n if (queSize == Capacity()) {\n Console.WriteLine(\"キューが満杯です\");\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n // 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n int rear = (front + queSize) % Capacity();\n // num をキューの末尾に追加する\n nums[rear] = num;\n queSize++;\n }\n\n /* デキュー */\n public int Pop() {\n int num = Peek();\n // 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n front = (front + 1) % Capacity();\n queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n public int Peek() {\n if (IsEmpty())\n throw new Exception();\n return nums[front];\n }\n\n /* 配列を返す */\n public int[] ToArray() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n int[] res = new int[queSize];\n for (int i = 0, j = front; i &lt; queSize; i++, j++) {\n res[i] = nums[j % this.Capacity()];\n }\n return res;\n }\n}\n</code></pre> array_queue.go<pre><code>/* 循環配列で実装したキュー */\ntype arrayQueue struct {\n nums []int // キューの要素を格納する配列\n front int // キューの先頭要素を指す先頭ポインタ\n queSize int // キューの長さ\n queCapacity int // キューの容量(格納できる要素数の上限)\n}\n\n/* キューを初期化する */\nfunc newArrayQueue(queCapacity int) *arrayQueue {\n return &amp;arrayQueue{\n nums: make([]int, queCapacity),\n queCapacity: queCapacity,\n front: 0,\n queSize: 0,\n }\n}\n\n/* キューの長さを取得 */\nfunc (q *arrayQueue) size() int {\n return q.queSize\n}\n\n/* キューが空か判定 */\nfunc (q *arrayQueue) isEmpty() bool {\n return q.queSize == 0\n}\n\n/* エンキュー */\nfunc (q *arrayQueue) push(num int) {\n // rear == queCapacity のとき、キューは満杯である\n if q.queSize == q.queCapacity {\n return\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n // 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n rear := (q.front + q.queSize) % q.queCapacity\n // num をキューの末尾に追加する\n q.nums[rear] = num\n q.queSize++\n}\n\n/* デキュー */\nfunc (q *arrayQueue) pop() any {\n num := q.peek()\n if num == nil {\n return nil\n }\n\n // 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n q.front = (q.front + 1) % q.queCapacity\n q.queSize--\n return num\n}\n\n/* キューの先頭要素にアクセス */\nfunc (q *arrayQueue) peek() any {\n if q.isEmpty() {\n return nil\n }\n return q.nums[q.front]\n}\n\n/* 表示用の Slice を取得する */\nfunc (q *arrayQueue) toSlice() []int {\n rear := (q.front + q.queSize)\n if rear &gt;= q.queCapacity {\n rear %= q.queCapacity\n return append(q.nums[q.front:], q.nums[:rear]...)\n }\n return q.nums[q.front:rear]\n}\n</code></pre> array_queue.swift<pre><code>/* 循環配列で実装したキュー */\nclass ArrayQueue {\n private var nums: [Int] // キューの要素を格納する配列\n private var front: Int // キューの先頭要素を指す先頭ポインタ\n private var _size: Int // キューの長さ\n\n init(capacity: Int) {\n // 配列を初期化する\n nums = Array(repeating: 0, count: capacity)\n front = 0\n _size = 0\n }\n\n /* キューの容量を取得する */\n func capacity() -&gt; Int {\n nums.count\n }\n\n /* キューの長さを取得 */\n func size() -&gt; Int {\n _size\n }\n\n /* キューが空か判定 */\n func isEmpty() -&gt; Bool {\n size() == 0\n }\n\n /* エンキュー */\n func push(num: Int) {\n if size() == capacity() {\n print(\"キューが満杯です\")\n return\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n // 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n let rear = (front + size()) % capacity()\n // num をキューの末尾に追加する\n nums[rear] = num\n _size += 1\n }\n\n /* デキュー */\n @discardableResult\n func pop() -&gt; Int {\n let num = peek()\n // 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n front = (front + 1) % capacity()\n _size -= 1\n return num\n }\n\n /* キューの先頭要素にアクセス */\n func peek() -&gt; Int {\n if isEmpty() {\n fatalError(\"キューが空です\")\n }\n return nums[front]\n }\n\n /* 配列を返す */\n func toArray() -&gt; [Int] {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n (front ..&lt; front + size()).map { nums[$0 % capacity()] }\n }\n}\n</code></pre> array_queue.js<pre><code>/* 循環配列で実装したキュー */\nclass ArrayQueue {\n #nums; // キューの要素を格納する配列\n #front = 0; // キューの先頭要素を指す先頭ポインタ\n #queSize = 0; // キューの長さ\n\n constructor(capacity) {\n this.#nums = new Array(capacity);\n }\n\n /* キューの容量を取得する */\n get capacity() {\n return this.#nums.length;\n }\n\n /* キューの長さを取得 */\n get size() {\n return this.#queSize;\n }\n\n /* キューが空か判定 */\n isEmpty() {\n return this.#queSize === 0;\n }\n\n /* エンキュー */\n push(num) {\n if (this.size === this.capacity) {\n console.log('キューが満杯です');\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n // 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n const rear = (this.#front + this.size) % this.capacity;\n // num をキューの末尾に追加する\n this.#nums[rear] = num;\n this.#queSize++;\n }\n\n /* デキュー */\n pop() {\n const num = this.peek();\n // 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n this.#front = (this.#front + 1) % this.capacity;\n this.#queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n peek() {\n if (this.isEmpty()) throw new Error('キューが空です');\n return this.#nums[this.#front];\n }\n\n /* Array を返す */\n toArray() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n const arr = new Array(this.size);\n for (let i = 0, j = this.#front; i &lt; this.size; i++, j++) {\n arr[i] = this.#nums[j % this.capacity];\n }\n return arr;\n }\n}\n</code></pre> array_queue.ts<pre><code>/* 循環配列で実装したキュー */\nclass ArrayQueue {\n private nums: number[]; // キューの要素を格納する配列\n private front: number; // キューの先頭要素を指す先頭ポインタ\n private queSize: number; // キューの長さ\n\n constructor(capacity: number) {\n this.nums = new Array(capacity);\n this.front = this.queSize = 0;\n }\n\n /* キューの容量を取得する */\n get capacity(): number {\n return this.nums.length;\n }\n\n /* キューの長さを取得 */\n get size(): number {\n return this.queSize;\n }\n\n /* キューが空か判定 */\n isEmpty(): boolean {\n return this.queSize === 0;\n }\n\n /* エンキュー */\n push(num: number): void {\n if (this.size === this.capacity) {\n console.log('キューが満杯です');\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n // 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n const rear = (this.front + this.queSize) % this.capacity;\n // num をキューの末尾に追加する\n this.nums[rear] = num;\n this.queSize++;\n }\n\n /* デキュー */\n pop(): number {\n const num = this.peek();\n // 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n this.front = (this.front + 1) % this.capacity;\n this.queSize--;\n return num;\n }\n\n /* キューの先頭要素にアクセス */\n peek(): number {\n if (this.isEmpty()) throw new Error('キューが空です');\n return this.nums[this.front];\n }\n\n /* Array を返す */\n toArray(): number[] {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n const arr = new Array(this.size);\n for (let i = 0, j = this.front; i &lt; this.size; i++, j++) {\n arr[i] = this.nums[j % this.capacity];\n }\n return arr;\n }\n}\n</code></pre> array_queue.dart<pre><code>/* 循環配列で実装したキュー */\nclass ArrayQueue {\n late List&lt;int&gt; _nums; // キューの要素を格納する配列\n late int _front; // キューの先頭要素を指す先頭ポインタ\n late int _queSize; // キューの長さ\n\n ArrayQueue(int capacity) {\n _nums = List.filled(capacity, 0);\n _front = _queSize = 0;\n }\n\n /* キューの容量を取得する */\n int capaCity() {\n return _nums.length;\n }\n\n /* キューの長さを取得 */\n int size() {\n return _queSize;\n }\n\n /* キューが空か判定 */\n bool isEmpty() {\n return _queSize == 0;\n }\n\n /* エンキュー */\n void push(int _num) {\n if (_queSize == capaCity()) {\n throw Exception(\"キューが満杯です\");\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n // 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n int rear = (_front + _queSize) % capaCity();\n // _num をキューの末尾に追加する\n _nums[rear] = _num;\n _queSize++;\n }\n\n /* デキュー */\n int pop() {\n int _num = peek();\n // 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n _front = (_front + 1) % capaCity();\n _queSize--;\n return _num;\n }\n\n /* キューの先頭要素にアクセス */\n int peek() {\n if (isEmpty()) {\n throw Exception(\"キューが空です\");\n }\n return _nums[_front];\n }\n\n /* Array を返す */\n List&lt;int&gt; toArray() {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n final List&lt;int&gt; res = List.filled(_queSize, 0);\n for (int i = 0, j = _front; i &lt; _queSize; i++, j++) {\n res[i] = _nums[j % capaCity()];\n }\n return res;\n }\n}\n</code></pre> array_queue.rs<pre><code>/* 循環配列で実装したキュー */\nstruct ArrayQueue&lt;T&gt; {\n nums: Vec&lt;T&gt;, // キューの要素を格納する配列\n front: i32, // キューの先頭要素を指す先頭ポインタ\n que_size: i32, // キューの長さ\n que_capacity: i32, // キューの容量\n}\n\nimpl&lt;T: Copy + Default&gt; ArrayQueue&lt;T&gt; {\n /* コンストラクタ */\n fn new(capacity: i32) -&gt; ArrayQueue&lt;T&gt; {\n ArrayQueue {\n nums: vec![T::default(); capacity as usize],\n front: 0,\n que_size: 0,\n que_capacity: capacity,\n }\n }\n\n /* キューの容量を取得する */\n fn capacity(&amp;self) -&gt; i32 {\n self.que_capacity\n }\n\n /* キューの長さを取得 */\n fn size(&amp;self) -&gt; i32 {\n self.que_size\n }\n\n /* キューが空か判定 */\n fn is_empty(&amp;self) -&gt; bool {\n self.que_size == 0\n }\n\n /* エンキュー */\n fn push(&amp;mut self, num: T) {\n if self.que_size == self.capacity() {\n println!(\"キューが満杯です\");\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n // 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n let rear = (self.front + self.que_size) % self.que_capacity;\n // num をキューの末尾に追加する\n self.nums[rear as usize] = num;\n self.que_size += 1;\n }\n\n /* デキュー */\n fn pop(&amp;mut self) -&gt; T {\n let num = self.peek();\n // 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n self.front = (self.front + 1) % self.que_capacity;\n self.que_size -= 1;\n num\n }\n\n /* キューの先頭要素にアクセス */\n fn peek(&amp;self) -&gt; T {\n if self.is_empty() {\n panic!(\"インデックスが範囲外です\");\n }\n self.nums[self.front as usize]\n }\n\n /* 配列を返す */\n fn to_vector(&amp;self) -&gt; Vec&lt;T&gt; {\n let cap = self.que_capacity;\n let mut j = self.front;\n let mut arr = vec![T::default(); cap as usize];\n for i in 0..self.que_size {\n arr[i as usize] = self.nums[(j % cap) as usize];\n j += 1;\n }\n arr\n }\n}\n</code></pre> array_queue.c<pre><code>/* 循環配列で実装したキュー */\ntypedef struct {\n int *nums; // キューの要素を格納する配列\n int front; // キューの先頭要素を指す先頭ポインタ\n int queSize; // 現在のキューの要素数\n int queCapacity; // キューの容量\n} ArrayQueue;\n\n/* コンストラクタ */\nArrayQueue *newArrayQueue(int capacity) {\n ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue));\n // 配列を初期化する\n queue-&gt;queCapacity = capacity;\n queue-&gt;nums = (int *)malloc(sizeof(int) * queue-&gt;queCapacity);\n queue-&gt;front = queue-&gt;queSize = 0;\n return queue;\n}\n\n/* デストラクタ */\nvoid delArrayQueue(ArrayQueue *queue) {\n free(queue-&gt;nums);\n free(queue);\n}\n\n/* キューの容量を取得する */\nint capacity(ArrayQueue *queue) {\n return queue-&gt;queCapacity;\n}\n\n/* キューの長さを取得 */\nint size(ArrayQueue *queue) {\n return queue-&gt;queSize;\n}\n\n/* キューが空か判定 */\nbool empty(ArrayQueue *queue) {\n return queue-&gt;queSize == 0;\n}\n\n/* キューの先頭要素にアクセス */\nint peek(ArrayQueue *queue) {\n assert(size(queue) != 0);\n return queue-&gt;nums[queue-&gt;front];\n}\n\n/* エンキュー */\nvoid push(ArrayQueue *queue, int num) {\n if (size(queue) == capacity(queue)) {\n printf(\"キューが満杯です\\r\\n\");\n return;\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n // 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n int rear = (queue-&gt;front + queue-&gt;queSize) % queue-&gt;queCapacity;\n // num をキューの末尾に追加する\n queue-&gt;nums[rear] = num;\n queue-&gt;queSize++;\n}\n\n/* デキュー */\nint pop(ArrayQueue *queue) {\n int num = peek(queue);\n // 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n queue-&gt;front = (queue-&gt;front + 1) % queue-&gt;queCapacity;\n queue-&gt;queSize--;\n return num;\n}\n\n/* 表示用の配列を返す */\nint *toArray(ArrayQueue *queue, int *queSize) {\n *queSize = queue-&gt;queSize;\n int *res = (int *)calloc(queue-&gt;queSize, sizeof(int));\n int j = queue-&gt;front;\n for (int i = 0; i &lt; queue-&gt;queSize; i++) {\n res[i] = queue-&gt;nums[j % queue-&gt;queCapacity];\n j++;\n }\n return res;\n}\n</code></pre> array_queue.kt<pre><code>/* 循環配列で実装したキュー */\nclass ArrayQueue(capacity: Int) {\n private val nums: IntArray = IntArray(capacity) // キューの要素を格納する配列\n private var front: Int = 0 // キューの先頭要素を指す先頭ポインタ\n private var queSize: Int = 0 // キューの長さ\n\n /* キューの容量を取得する */\n fun capacity(): Int {\n return nums.size\n }\n\n /* キューの長さを取得 */\n fun size(): Int {\n return queSize\n }\n\n /* キューが空か判定 */\n fun isEmpty(): Boolean {\n return queSize == 0\n }\n\n /* エンキュー */\n fun push(num: Int) {\n if (queSize == capacity()) {\n println(\"キューが満杯です\")\n return\n }\n // 末尾のインデックス + 1 を指す末尾ポインタを計算する\n // 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n val rear = (front + queSize) % capacity()\n // num をキューの末尾に追加する\n nums[rear] = num\n queSize++\n }\n\n /* デキュー */\n fun pop(): Int {\n val num = peek()\n // 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n front = (front + 1) % capacity()\n queSize--\n return num\n }\n\n /* キューの先頭要素にアクセス */\n fun peek(): Int {\n if (isEmpty()) throw IndexOutOfBoundsException()\n return nums[front]\n }\n\n /* 配列を返す */\n fun toArray(): IntArray {\n // 有効な長さの範囲内にあるリストの要素だけを変換する\n val res = IntArray(queSize)\n var i = 0\n var j = front\n while (i &lt; queSize) {\n res[i] = nums[j % capacity()]\n i++\n j++\n }\n return res\n }\n}\n</code></pre> array_queue.rb<pre><code>### 循環配列で実装したキュー ###\nclass ArrayQueue\n ### キューの長さを取得 ###\n attr_reader :size\n\n ### コンストラクタ ###\n def initialize(size)\n @nums = Array.new(size, 0) # キューの要素を格納する配列\n @front = 0 # キューの先頭要素を指す先頭ポインタ\n @size = 0 # キューの長さ\n end\n\n ### キューの容量を取得する ###\n def capacity\n @nums.length\n end\n\n ### キューが空か判定 ###\n def is_empty?\n size.zero?\n end\n\n ### エンキュー ###\n def push(num)\n raise IndexError, 'キューが満杯です' if size == capacity\n\n # 末尾のインデックス + 1 を指す末尾ポインタを計算する\n # 剰余演算で、rear が配列末尾を越えたら先頭へ戻る\n rear = (@front + size) % capacity\n # num をキューの末尾に追加する\n @nums[rear] = num\n @size += 1\n end\n\n ### デキュー ###\n def pop\n num = peek\n # 先頭ポインタを後ろに 1 つ移動し、末尾を越えたら配列の先頭に戻す\n @front = (@front + 1) % capacity\n @size -= 1\n num\n end\n\n ### キューの先頭要素にアクセス ###\n def peek\n raise IndexError, 'キューが空です' if is_empty?\n\n @nums[@front]\n end\n\n ### 表示用のリストを返す ###\n def to_array\n res = Array.new(size, 0)\n j = @front\n\n for i in 0...size\n res[i] = @nums[j % capacity]\n j += 1\n end\n\n res\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>この実装には、長さを変更できないという制約がまだあります。ただし、配列を動的配列に置き換え、容量拡張を導入すれば解決できます。興味があれば実装してみてください。</p> <p>二つの実装の比較結果はスタックと同じなので、ここでは繰り返しません。</p>","path":["第 5 章   スタックとキュー","5.2   キュー"],"tags":[]},{"location":"chapter_stack_and_queue/queue/#523","level":2,"title":"5.2.3   キューの代表的な用途","text":"<ul> <li>淘宝の注文。購入者が注文するとキューに追加され、システムが順に処理します。11 月 11 日のセールでは短時間に膨大な注文が発生するため、多数の同時処理がエンジニアにとって重要な課題となります。</li> <li>さまざまな待機タスク。プリンターの印刷待ちや飲食店の料理の提供待ちなど、到着順の処理が必要な場面で、キューは処理順を効果的に維持できます。</li> </ul>","path":["第 5 章   スタックとキュー","5.2   キュー"],"tags":[]},{"location":"chapter_stack_and_queue/stack/","level":1,"title":"5.1   スタック","text":"<p>スタック(stack)は、先入れ後出しの規則に従う線形データ構造です。</p> <p>机の上に重ねた皿に例えられます。一度に一枚しか動かせないなら、一番下の皿を取るには、上の皿から順に取り除く必要があります。皿を整数、文字、オブジェクトなどの要素に置き換えると、スタックというデータ構造になります。</p> <p>図 5-1 のように、積み重ねた要素の上端を「スタックの先頭」、下端を「底」と呼びます。先頭に要素を追加する操作を「プッシュ」、先頭要素を削除する操作を「ポップ」と呼びます。</p> <p></p> <p> 図 5-1   スタックの先入れ後出しの規則 </p>","path":["第 5 章   スタックとキュー","5.1   スタック"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#511","level":2,"title":"5.1.1   スタックの主な操作","text":"<p>主な操作を表 5-1 に示します。具体的なメソッド名は言語によりますが、ここでは一般的な <code>push()</code>、<code>pop()</code>、<code>peek()</code> を例にします。</p> <p> 表 5-1   スタックの操作効率 </p> メソッド 説明 時間計算量 <code>push()</code> 要素をプッシュする(先頭に追加) \\(O(1)\\) <code>pop()</code> 先頭要素をポップする \\(O(1)\\) <code>peek()</code> 先頭要素にアクセスする \\(O(1)\\) <p>通常は、言語に組み込まれたスタッククラスをそのまま使えます。専用クラスがない言語では、配列や連結リストをスタックとして使い、スタックに関係しない操作をプログラムの論理上使わないようにします。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby stack.py<pre><code># スタックを初期化\n# Python には組み込みのスタッククラスがないため、list をスタックとして使える\nstack: list[int] = []\n\n# 要素をプッシュ\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n\n# スタックの先頭要素にアクセス\npeek: int = stack[-1]\n\n# 要素をポップ\npop: int = stack.pop()\n\n# スタックの長さを取得\nsize: int = len(stack)\n\n# 空かどうかを判定\nis_empty: bool = len(stack) == 0\n</code></pre> stack.cpp<pre><code>/* スタックを初期化 */\nstack&lt;int&gt; stack;\n\n/* 要素をプッシュ */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* スタックの先頭要素にアクセス */\nint top = stack.top();\n\n/* 要素をポップ */\nstack.pop(); // 戻り値なし\n\n/* スタックの長さを取得 */\nint size = stack.size();\n\n/* 空かどうかを判定 */\nbool empty = stack.empty();\n</code></pre> stack.java<pre><code>/* スタックを初期化 */\nStack&lt;Integer&gt; stack = new Stack&lt;&gt;();\n\n/* 要素をプッシュ */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* スタックの先頭要素にアクセス */\nint peek = stack.peek();\n\n/* 要素をポップ */\nint pop = stack.pop();\n\n/* スタックの長さを取得 */\nint size = stack.size();\n\n/* 空かどうかを判定 */\nboolean isEmpty = stack.isEmpty();\n</code></pre> stack.cs<pre><code>/* スタックを初期化 */\nStack&lt;int&gt; stack = new();\n\n/* 要素をプッシュ */\nstack.Push(1);\nstack.Push(3);\nstack.Push(2);\nstack.Push(5);\nstack.Push(4);\n\n/* スタックの先頭要素にアクセス */\nint peek = stack.Peek();\n\n/* 要素をポップ */\nint pop = stack.Pop();\n\n/* スタックの長さを取得 */\nint size = stack.Count;\n\n/* 空かどうかを判定 */\nbool isEmpty = stack.Count == 0;\n</code></pre> stack_test.go<pre><code>/* スタックを初期化 */\n// Go では Slice をスタックとして使うことを推奨\nvar stack []int\n\n/* 要素をプッシュ */\nstack = append(stack, 1)\nstack = append(stack, 3)\nstack = append(stack, 2)\nstack = append(stack, 5)\nstack = append(stack, 4)\n\n/* スタックの先頭要素にアクセス */\npeek := stack[len(stack)-1]\n\n/* 要素をポップ */\npop := stack[len(stack)-1]\nstack = stack[:len(stack)-1]\n\n/* スタックの長さを取得 */\nsize := len(stack)\n\n/* 空かどうかを判定 */\nisEmpty := len(stack) == 0\n</code></pre> stack.swift<pre><code>/* スタックを初期化 */\n// Swift には組み込みのスタッククラスがないため、Array をスタックとして使える\nvar stack: [Int] = []\n\n/* 要素をプッシュ */\nstack.append(1)\nstack.append(3)\nstack.append(2)\nstack.append(5)\nstack.append(4)\n\n/* スタックの先頭要素にアクセス */\nlet peek = stack.last!\n\n/* 要素をポップ */\nlet pop = stack.removeLast()\n\n/* スタックの長さを取得 */\nlet size = stack.count\n\n/* 空かどうかを判定 */\nlet isEmpty = stack.isEmpty\n</code></pre> stack.js<pre><code>/* スタックを初期化 */\n// JavaScript には組み込みのスタッククラスがないため、Array をスタックとして使える\nconst stack = [];\n\n/* 要素をプッシュ */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* スタックの先頭要素にアクセス */\nconst peek = stack[stack.length-1];\n\n/* 要素をポップ */\nconst pop = stack.pop();\n\n/* スタックの長さを取得 */\nconst size = stack.length;\n\n/* 空かどうかを判定 */\nconst is_empty = stack.length === 0;\n</code></pre> stack.ts<pre><code>/* スタックを初期化 */\n// TypeScript には組み込みのスタッククラスがないため、Array をスタックとして使える\nconst stack: number[] = [];\n\n/* 要素をプッシュ */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* スタックの先頭要素にアクセス */\nconst peek = stack[stack.length - 1];\n\n/* 要素をポップ */\nconst pop = stack.pop();\n\n/* スタックの長さを取得 */\nconst size = stack.length;\n\n/* 空かどうかを判定 */\nconst is_empty = stack.length === 0;\n</code></pre> stack.dart<pre><code>/* スタックを初期化 */\n// Dart には組み込みのスタッククラスがないため、List をスタックとして使える\nList&lt;int&gt; stack = [];\n\n/* 要素をプッシュ */\nstack.add(1);\nstack.add(3);\nstack.add(2);\nstack.add(5);\nstack.add(4);\n\n/* スタックの先頭要素にアクセス */\nint peek = stack.last;\n\n/* 要素をポップ */\nint pop = stack.removeLast();\n\n/* スタックの長さを取得 */\nint size = stack.length;\n\n/* 空かどうかを判定 */\nbool isEmpty = stack.isEmpty;\n</code></pre> stack.rs<pre><code>/* スタックを初期化 */\n// Vec をスタックとして使う\nlet mut stack: Vec&lt;i32&gt; = Vec::new();\n\n/* 要素をプッシュ */\nstack.push(1);\nstack.push(3);\nstack.push(2);\nstack.push(5);\nstack.push(4);\n\n/* スタックの先頭要素にアクセス */\nlet top = stack.last().unwrap();\n\n/* 要素をポップ */\nlet pop = stack.pop().unwrap();\n\n/* スタックの長さを取得 */\nlet size = stack.len();\n\n/* 空かどうかを判定 */\nlet is_empty = stack.is_empty();\n</code></pre> stack.c<pre><code>// C には組み込みのスタックがない\n</code></pre> stack.kt<pre><code>/* スタックを初期化 */\nval stack = Stack&lt;Int&gt;()\n\n/* 要素をプッシュ */\nstack.push(1)\nstack.push(3)\nstack.push(2)\nstack.push(5)\nstack.push(4)\n\n/* スタックの先頭要素にアクセス */\nval peek = stack.peek()\n\n/* 要素をポップ */\nval pop = stack.pop()\n\n/* スタックの長さを取得 */\nval size = stack.size\n\n/* 空かどうかを判定 */\nval isEmpty = stack.isEmpty()\n</code></pre> stack.rb<pre><code># スタックを初期化\n# Ruby には組み込みのスタッククラスがないため、Array をスタックとして使える\nstack = []\n\n# 要素をプッシュ\nstack &lt;&lt; 1\nstack &lt;&lt; 3\nstack &lt;&lt; 2\nstack &lt;&lt; 5\nstack &lt;&lt; 4\n\n# スタックの先頭要素にアクセス\npeek = stack.last\n\n# 要素をポップ\npop = stack.pop\n\n# スタックの長さを取得\nsize = stack.length\n\n# 空かどうかを判定\nis_empty = stack.empty?\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 5 章   スタックとキュー","5.1   スタック"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#512","level":2,"title":"5.1.2   スタックの実装","text":"<p>動作の仕組みを深く理解するため、スタッククラスを自分で実装してみましょう。</p> <p>スタックは先入れ後出しなので、要素の追加と削除は先頭でしかできません。一方、配列や連結リストは任意の位置で追加や削除ができるため、スタックは制限を加えた配列や連結リストと見なせます。つまり、不要な操作を使えなくすることで、外から見た動作をスタックの性質に合わせられます。</p>","path":["第 5 章   スタックとキュー","5.1   スタック"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#1","level":3,"title":"1.   連結リストによる実装","text":"<p>連結リストを使う場合、先頭ノードをスタックの先頭、末尾ノードを底と見なせます。</p> <p>図 5-2 のように、プッシュではリストの先頭へ要素を挿入するだけです。この方法を「先頭挿入」と呼びます。ポップでは、先頭ノードをリストから削除するだけです。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt; <p></p> <p></p> <p></p> <p> 図 5-2   連結リストによるスタックのプッシュとポップ </p> <p>連結リストによる実装例を示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby linkedlist_stack.py<pre><code>class LinkedListStack:\n \"\"\"連結リストで実装したスタック\"\"\"\n\n def __init__(self):\n \"\"\"コンストラクタ\"\"\"\n self._peek: ListNode | None = None\n self._size: int = 0\n\n def size(self) -&gt; int:\n \"\"\"スタックの長さを取得する\"\"\"\n return self._size\n\n def is_empty(self) -&gt; bool:\n \"\"\"スタックが空かどうかを判定する\"\"\"\n return self._size == 0\n\n def push(self, val: int):\n \"\"\"プッシュ\"\"\"\n node = ListNode(val)\n node.next = self._peek\n self._peek = node\n self._size += 1\n\n def pop(self) -&gt; int:\n \"\"\"ポップ\"\"\"\n num = self.peek()\n self._peek = self._peek.next\n self._size -= 1\n return num\n\n def peek(self) -&gt; int:\n \"\"\"スタックの先頭要素にアクセスする\"\"\"\n if self.is_empty():\n raise IndexError(\"スタックが空です\")\n return self._peek.val\n\n def to_list(self) -&gt; list[int]:\n \"\"\"表示用のリストに変換する\"\"\"\n arr = []\n node = self._peek\n while node:\n arr.append(node.val)\n node = node.next\n arr.reverse()\n return arr\n</code></pre> linkedlist_stack.cpp<pre><code>/* 連結リストで実装したスタック */\nclass LinkedListStack {\n private:\n ListNode *stackTop; // 先頭ノードをスタックの先頭とする\n int stkSize; // スタックの長さ\n\n public:\n LinkedListStack() {\n stackTop = nullptr;\n stkSize = 0;\n }\n\n ~LinkedListStack() {\n // 連結リストを走査してノードを削除し、メモリを解放する\n freeMemoryLinkedList(stackTop);\n }\n\n /* スタックの長さを取得する */\n int size() {\n return stkSize;\n }\n\n /* スタックが空かどうかを判定する */\n bool isEmpty() {\n return size() == 0;\n }\n\n /* プッシュ */\n void push(int num) {\n ListNode *node = new ListNode(num);\n node-&gt;next = stackTop;\n stackTop = node;\n stkSize++;\n }\n\n /* ポップ */\n int pop() {\n int num = top();\n ListNode *tmp = stackTop;\n stackTop = stackTop-&gt;next;\n // メモリを解放する\n delete tmp;\n stkSize--;\n return num;\n }\n\n /* スタックの先頭要素にアクセスする */\n int top() {\n if (isEmpty())\n throw out_of_range(\"スタックが空です\");\n return stackTop-&gt;val;\n }\n\n /* List を Array に変換して返す */\n vector&lt;int&gt; toVector() {\n ListNode *node = stackTop;\n vector&lt;int&gt; res(size());\n for (int i = res.size() - 1; i &gt;= 0; i--) {\n res[i] = node-&gt;val;\n node = node-&gt;next;\n }\n return res;\n }\n};\n</code></pre> linkedlist_stack.java<pre><code>/* 連結リストで実装したスタック */\nclass LinkedListStack {\n private ListNode stackPeek; // 先頭ノードをスタックの先頭とする\n private int stkSize = 0; // スタックの長さ\n\n public LinkedListStack() {\n stackPeek = null;\n }\n\n /* スタックの長さを取得する */\n public int size() {\n return stkSize;\n }\n\n /* スタックが空かどうかを判定する */\n public boolean isEmpty() {\n return size() == 0;\n }\n\n /* プッシュ */\n public void push(int num) {\n ListNode node = new ListNode(num);\n node.next = stackPeek;\n stackPeek = node;\n stkSize++;\n }\n\n /* ポップ */\n public int pop() {\n int num = peek();\n stackPeek = stackPeek.next;\n stkSize--;\n return num;\n }\n\n /* スタックの先頭要素にアクセスする */\n public int peek() {\n if (isEmpty())\n throw new IndexOutOfBoundsException();\n return stackPeek.val;\n }\n\n /* List を Array に変換して返す */\n public int[] toArray() {\n ListNode node = stackPeek;\n int[] res = new int[size()];\n for (int i = res.length - 1; i &gt;= 0; i--) {\n res[i] = node.val;\n node = node.next;\n }\n return res;\n }\n}\n</code></pre> linkedlist_stack.cs<pre><code>/* 連結リストで実装したスタック */\nclass LinkedListStack {\n ListNode? stackPeek; // 先頭ノードをスタックの先頭とする\n int stkSize = 0; // スタックの長さ\n\n public LinkedListStack() {\n stackPeek = null;\n }\n\n /* スタックの長さを取得する */\n public int Size() {\n return stkSize;\n }\n\n /* スタックが空かどうかを判定する */\n public bool IsEmpty() {\n return Size() == 0;\n }\n\n /* プッシュ */\n public void Push(int num) {\n ListNode node = new(num) {\n next = stackPeek\n };\n stackPeek = node;\n stkSize++;\n }\n\n /* ポップ */\n public int Pop() {\n int num = Peek();\n stackPeek = stackPeek!.next;\n stkSize--;\n return num;\n }\n\n /* スタックの先頭要素にアクセスする */\n public int Peek() {\n if (IsEmpty())\n throw new Exception();\n return stackPeek!.val;\n }\n\n /* List を Array に変換して返す */\n public int[] ToArray() {\n if (stackPeek == null)\n return [];\n\n ListNode? node = stackPeek;\n int[] res = new int[Size()];\n for (int i = res.Length - 1; i &gt;= 0; i--) {\n res[i] = node!.val;\n node = node.next;\n }\n return res;\n }\n}\n</code></pre> linkedlist_stack.go<pre><code>/* 連結リストで実装したスタック */\ntype linkedListStack struct {\n // 組み込みパッケージ list でスタックを実装する\n data *list.List\n}\n\n/* スタックを初期化する */\nfunc newLinkedListStack() *linkedListStack {\n return &amp;linkedListStack{\n data: list.New(),\n }\n}\n\n/* プッシュ */\nfunc (s *linkedListStack) push(value int) {\n s.data.PushBack(value)\n}\n\n/* ポップ */\nfunc (s *linkedListStack) pop() any {\n if s.isEmpty() {\n return nil\n }\n e := s.data.Back()\n s.data.Remove(e)\n return e.Value\n}\n\n/* スタックの先頭要素にアクセスする */\nfunc (s *linkedListStack) peek() any {\n if s.isEmpty() {\n return nil\n }\n e := s.data.Back()\n return e.Value\n}\n\n/* スタックの長さを取得する */\nfunc (s *linkedListStack) size() int {\n return s.data.Len()\n}\n\n/* スタックが空かどうかを判定する */\nfunc (s *linkedListStack) isEmpty() bool {\n return s.data.Len() == 0\n}\n\n/* 表示用の List を取得する */\nfunc (s *linkedListStack) toList() *list.List {\n return s.data\n}\n</code></pre> linkedlist_stack.swift<pre><code>/* 連結リストで実装したスタック */\nclass LinkedListStack {\n private var _peek: ListNode? // 先頭ノードをスタックの先頭とする\n private var _size: Int // スタックの長さ\n\n init() {\n _size = 0\n }\n\n /* スタックの長さを取得する */\n func size() -&gt; Int {\n _size\n }\n\n /* スタックが空かどうかを判定する */\n func isEmpty() -&gt; Bool {\n size() == 0\n }\n\n /* プッシュ */\n func push(num: Int) {\n let node = ListNode(x: num)\n node.next = _peek\n _peek = node\n _size += 1\n }\n\n /* ポップ */\n @discardableResult\n func pop() -&gt; Int {\n let num = peek()\n _peek = _peek?.next\n _size -= 1\n return num\n }\n\n /* スタックの先頭要素にアクセスする */\n func peek() -&gt; Int {\n if isEmpty() {\n fatalError(\"スタックが空です\")\n }\n return _peek!.val\n }\n\n /* List を Array に変換して返す */\n func toArray() -&gt; [Int] {\n var node = _peek\n var res = Array(repeating: 0, count: size())\n for i in res.indices.reversed() {\n res[i] = node!.val\n node = node?.next\n }\n return res\n }\n}\n</code></pre> linkedlist_stack.js<pre><code>/* 連結リストで実装したスタック */\nclass LinkedListStack {\n #stackPeek; // 先頭ノードをスタックの先頭とする\n #stkSize = 0; // スタックの長さ\n\n constructor() {\n this.#stackPeek = null;\n }\n\n /* スタックの長さを取得する */\n get size() {\n return this.#stkSize;\n }\n\n /* スタックが空かどうかを判定する */\n isEmpty() {\n return this.size === 0;\n }\n\n /* プッシュ */\n push(num) {\n const node = new ListNode(num);\n node.next = this.#stackPeek;\n this.#stackPeek = node;\n this.#stkSize++;\n }\n\n /* ポップ */\n pop() {\n const num = this.peek();\n this.#stackPeek = this.#stackPeek.next;\n this.#stkSize--;\n return num;\n }\n\n /* スタックの先頭要素にアクセスする */\n peek() {\n if (!this.#stackPeek) throw new Error('スタックが空です');\n return this.#stackPeek.val;\n }\n\n /* 連結リストを Array に変換して返す */\n toArray() {\n let node = this.#stackPeek;\n const res = new Array(this.size);\n for (let i = res.length - 1; i &gt;= 0; i--) {\n res[i] = node.val;\n node = node.next;\n }\n return res;\n }\n}\n</code></pre> linkedlist_stack.ts<pre><code>/* 連結リストで実装したスタック */\nclass LinkedListStack {\n private stackPeek: ListNode | null; // 先頭ノードをスタックの先頭とする\n private stkSize: number = 0; // スタックの長さ\n\n constructor() {\n this.stackPeek = null;\n }\n\n /* スタックの長さを取得する */\n get size(): number {\n return this.stkSize;\n }\n\n /* スタックが空かどうかを判定する */\n isEmpty(): boolean {\n return this.size === 0;\n }\n\n /* プッシュ */\n push(num: number): void {\n const node = new ListNode(num);\n node.next = this.stackPeek;\n this.stackPeek = node;\n this.stkSize++;\n }\n\n /* ポップ */\n pop(): number {\n const num = this.peek();\n if (!this.stackPeek) throw new Error('スタックが空です');\n this.stackPeek = this.stackPeek.next;\n this.stkSize--;\n return num;\n }\n\n /* スタックの先頭要素にアクセスする */\n peek(): number {\n if (!this.stackPeek) throw new Error('スタックが空です');\n return this.stackPeek.val;\n }\n\n /* 連結リストを Array に変換して返す */\n toArray(): number[] {\n let node = this.stackPeek;\n const res = new Array&lt;number&gt;(this.size);\n for (let i = res.length - 1; i &gt;= 0; i--) {\n res[i] = node!.val;\n node = node!.next;\n }\n return res;\n }\n}\n</code></pre> linkedlist_stack.dart<pre><code>/* 連結リストクラスで実装したスタック */\nclass LinkedListStack {\n ListNode? _stackPeek; // 先頭ノードをスタックの先頭とする\n int _stkSize = 0; // スタックの長さ\n\n LinkedListStack() {\n _stackPeek = null;\n }\n\n /* スタックの長さを取得する */\n int size() {\n return _stkSize;\n }\n\n /* スタックが空かどうかを判定する */\n bool isEmpty() {\n return _stkSize == 0;\n }\n\n /* プッシュ */\n void push(int _num) {\n final ListNode node = ListNode(_num);\n node.next = _stackPeek;\n _stackPeek = node;\n _stkSize++;\n }\n\n /* ポップ */\n int pop() {\n final int _num = peek();\n _stackPeek = _stackPeek!.next;\n _stkSize--;\n return _num;\n }\n\n /* スタックの先頭要素にアクセスする */\n int peek() {\n if (_stackPeek == null) {\n throw Exception(\"スタックが空です\");\n }\n return _stackPeek!.val;\n }\n\n /* 連結リストを List に変換して返す */\n List&lt;int&gt; toList() {\n ListNode? node = _stackPeek;\n List&lt;int&gt; list = [];\n while (node != null) {\n list.add(node.val);\n node = node.next;\n }\n list = list.reversed.toList();\n return list;\n }\n}\n</code></pre> linkedlist_stack.rs<pre><code>/* 連結リストで実装したスタック */\n#[allow(dead_code)]\npub struct LinkedListStack&lt;T&gt; {\n stack_peek: Option&lt;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;, // 先頭ノードをスタックの先頭とする\n stk_size: usize, // スタックの長さ\n}\n\nimpl&lt;T: Copy&gt; LinkedListStack&lt;T&gt; {\n pub fn new() -&gt; Self {\n Self {\n stack_peek: None,\n stk_size: 0,\n }\n }\n\n /* スタックの長さを取得する */\n pub fn size(&amp;self) -&gt; usize {\n return self.stk_size;\n }\n\n /* スタックが空かどうかを判定する */\n pub fn is_empty(&amp;self) -&gt; bool {\n return self.size() == 0;\n }\n\n /* プッシュ */\n pub fn push(&amp;mut self, num: T) {\n let node = ListNode::new(num);\n node.borrow_mut().next = self.stack_peek.take();\n self.stack_peek = Some(node);\n self.stk_size += 1;\n }\n\n /* ポップ */\n pub fn pop(&amp;mut self) -&gt; Option&lt;T&gt; {\n self.stack_peek.take().map(|old_head| {\n self.stack_peek = old_head.borrow_mut().next.take();\n self.stk_size -= 1;\n\n old_head.borrow().val\n })\n }\n\n /* スタックの先頭要素にアクセスする */\n pub fn peek(&amp;self) -&gt; Option&lt;&amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt; {\n self.stack_peek.as_ref()\n }\n\n /* List を Array に変換して返す */\n pub fn to_array(&amp;self) -&gt; Vec&lt;T&gt; {\n fn _to_array&lt;T: Sized + Copy&gt;(head: Option&lt;&amp;Rc&lt;RefCell&lt;ListNode&lt;T&gt;&gt;&gt;&gt;) -&gt; Vec&lt;T&gt; {\n if let Some(node) = head {\n let mut nums = _to_array(node.borrow().next.as_ref());\n nums.push(node.borrow().val);\n return nums;\n }\n return Vec::new();\n }\n\n _to_array(self.peek())\n }\n}\n</code></pre> linkedlist_stack.c<pre><code>/* 連結リストで実装したスタック */\ntypedef struct {\n ListNode *top; // 先頭ノードをスタックの先頭とする\n int size; // スタックの長さ\n} LinkedListStack;\n\n/* コンストラクタ */\nLinkedListStack *newLinkedListStack() {\n LinkedListStack *s = malloc(sizeof(LinkedListStack));\n s-&gt;top = NULL;\n s-&gt;size = 0;\n return s;\n}\n\n/* デストラクタ */\nvoid delLinkedListStack(LinkedListStack *s) {\n while (s-&gt;top) {\n ListNode *n = s-&gt;top-&gt;next;\n free(s-&gt;top);\n s-&gt;top = n;\n }\n free(s);\n}\n\n/* スタックの長さを取得する */\nint size(LinkedListStack *s) {\n return s-&gt;size;\n}\n\n/* スタックが空かどうかを判定する */\nbool isEmpty(LinkedListStack *s) {\n return size(s) == 0;\n}\n\n/* プッシュ */\nvoid push(LinkedListStack *s, int num) {\n ListNode *node = (ListNode *)malloc(sizeof(ListNode));\n node-&gt;next = s-&gt;top; // 追加するノードのポインタ領域を更新する\n node-&gt;val = num; // 追加するノードのデータ領域を更新する\n s-&gt;top = node; // スタックの先頭を更新する\n s-&gt;size++; // スタックのサイズを更新する\n}\n\n/* スタックの先頭要素にアクセスする */\nint peek(LinkedListStack *s) {\n if (s-&gt;size == 0) {\n printf(\"スタックが空です\\n\");\n return INT_MAX;\n }\n return s-&gt;top-&gt;val;\n}\n\n/* ポップ */\nint pop(LinkedListStack *s) {\n int val = peek(s);\n ListNode *tmp = s-&gt;top;\n s-&gt;top = s-&gt;top-&gt;next;\n // メモリを解放する\n free(tmp);\n s-&gt;size--;\n return val;\n}\n</code></pre> linkedlist_stack.kt<pre><code>/* 連結リストで実装したスタック */\nclass LinkedListStack(\n private var stackPeek: ListNode? = null, // 先頭ノードをスタックの先頭とする\n private var stkSize: Int = 0 // スタックの長さ\n) {\n\n /* スタックの長さを取得する */\n fun size(): Int {\n return stkSize\n }\n\n /* スタックが空かどうかを判定する */\n fun isEmpty(): Boolean {\n return size() == 0\n }\n\n /* プッシュ */\n fun push(num: Int) {\n val node = ListNode(num)\n node.next = stackPeek\n stackPeek = node\n stkSize++\n }\n\n /* ポップ */\n fun pop(): Int? {\n val num = peek()\n stackPeek = stackPeek?.next\n stkSize--\n return num\n }\n\n /* スタックの先頭要素にアクセスする */\n fun peek(): Int? {\n if (isEmpty()) throw IndexOutOfBoundsException()\n return stackPeek?._val\n }\n\n /* List を Array に変換して返す */\n fun toArray(): IntArray {\n var node = stackPeek\n val res = IntArray(size())\n for (i in res.size - 1 downTo 0) {\n res[i] = node?._val!!\n node = node.next\n }\n return res\n }\n}\n</code></pre> linkedlist_stack.rb<pre><code>### 連結リストで実装したスタック ###\nclass LinkedListStack\n attr_reader :size\n\n ### コンストラクタ ###\n def initialize\n @size = 0\n end\n\n ### スタックが空かどうかを判定する ###\n def is_empty?\n @peek.nil?\n end\n\n ### プッシュ ###\n def push(val)\n node = ListNode.new(val)\n node.next = @peek\n @peek = node\n @size += 1\n end\n\n ### ポップ ###\n def pop\n num = peek\n @peek = @peek.next\n @size -= 1\n num\n end\n\n ### スタックの先頭要素にアクセスする ###\n def peek\n raise IndexError, 'スタックが空です' if is_empty?\n\n @peek.val\n end\n\n ### 連結リストを Array に変換して返す ###\n def to_array\n arr = []\n node = @peek\n while node\n arr &lt;&lt; node.val\n node = node.next\n end\n arr.reverse\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 5 章   スタックとキュー","5.1   スタック"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#2","level":3,"title":"2.   配列による実装","text":"<p>配列を使う場合、配列の末尾をスタックの先頭にできます。図 5-3 のように、プッシュとポップは配列末尾への追加と削除に対応し、どちらも時間計算量は \\(O(1)\\) です。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt; <p></p> <p></p> <p></p> <p> 図 5-3   配列によるスタックのプッシュとポップ </p> <p>プッシュする要素が増え続ける可能性があるため、動的配列を使うと、容量拡張を自分で処理せずに済みます。コード例を示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_stack.py<pre><code>class ArrayStack:\n \"\"\"配列で実装したスタック\"\"\"\n\n def __init__(self):\n \"\"\"コンストラクタ\"\"\"\n self._stack: list[int] = []\n\n def size(self) -&gt; int:\n \"\"\"スタックの長さを取得する\"\"\"\n return len(self._stack)\n\n def is_empty(self) -&gt; bool:\n \"\"\"スタックが空かどうかを判定する\"\"\"\n return self.size() == 0\n\n def push(self, item: int):\n \"\"\"プッシュ\"\"\"\n self._stack.append(item)\n\n def pop(self) -&gt; int:\n \"\"\"ポップ\"\"\"\n if self.is_empty():\n raise IndexError(\"スタックが空です\")\n return self._stack.pop()\n\n def peek(self) -&gt; int:\n \"\"\"スタックの先頭要素にアクセスする\"\"\"\n if self.is_empty():\n raise IndexError(\"スタックが空です\")\n return self._stack[-1]\n\n def to_list(self) -&gt; list[int]:\n \"\"\"表示用のリストを返す\"\"\"\n return self._stack\n</code></pre> array_stack.cpp<pre><code>/* 配列で実装したスタック */\nclass ArrayStack {\n private:\n vector&lt;int&gt; stack;\n\n public:\n /* スタックの長さを取得する */\n int size() {\n return stack.size();\n }\n\n /* スタックが空かどうかを判定する */\n bool isEmpty() {\n return stack.size() == 0;\n }\n\n /* プッシュ */\n void push(int num) {\n stack.push_back(num);\n }\n\n /* ポップ */\n int pop() {\n int num = top();\n stack.pop_back();\n return num;\n }\n\n /* スタックの先頭要素にアクセスする */\n int top() {\n if (isEmpty())\n throw out_of_range(\"スタックが空です\");\n return stack.back();\n }\n\n /* Vector を返す */\n vector&lt;int&gt; toVector() {\n return stack;\n }\n};\n</code></pre> array_stack.java<pre><code>/* 配列で実装したスタック */\nclass ArrayStack {\n private ArrayList&lt;Integer&gt; stack;\n\n public ArrayStack() {\n // リスト(動的配列)を初期化する\n stack = new ArrayList&lt;&gt;();\n }\n\n /* スタックの長さを取得する */\n public int size() {\n return stack.size();\n }\n\n /* スタックが空かどうかを判定する */\n public boolean isEmpty() {\n return size() == 0;\n }\n\n /* プッシュ */\n public void push(int num) {\n stack.add(num);\n }\n\n /* ポップ */\n public int pop() {\n if (isEmpty())\n throw new IndexOutOfBoundsException();\n return stack.remove(size() - 1);\n }\n\n /* スタックの先頭要素にアクセスする */\n public int peek() {\n if (isEmpty())\n throw new IndexOutOfBoundsException();\n return stack.get(size() - 1);\n }\n\n /* List を Array に変換して返す */\n public Object[] toArray() {\n return stack.toArray();\n }\n}\n</code></pre> array_stack.cs<pre><code>/* 配列で実装したスタック */\nclass ArrayStack {\n List&lt;int&gt; stack;\n public ArrayStack() {\n // リスト(動的配列)を初期化する\n stack = [];\n }\n\n /* スタックの長さを取得する */\n public int Size() {\n return stack.Count;\n }\n\n /* スタックが空かどうかを判定する */\n public bool IsEmpty() {\n return Size() == 0;\n }\n\n /* プッシュ */\n public void Push(int num) {\n stack.Add(num);\n }\n\n /* ポップ */\n public int Pop() {\n if (IsEmpty())\n throw new Exception();\n var val = Peek();\n stack.RemoveAt(Size() - 1);\n return val;\n }\n\n /* スタックの先頭要素にアクセスする */\n public int Peek() {\n if (IsEmpty())\n throw new Exception();\n return stack[Size() - 1];\n }\n\n /* List を Array に変換して返す */\n public int[] ToArray() {\n return [.. stack];\n }\n}\n</code></pre> array_stack.go<pre><code>/* 配列で実装したスタック */\ntype arrayStack struct {\n data []int // データ\n}\n\n/* スタックを初期化する */\nfunc newArrayStack() *arrayStack {\n return &amp;arrayStack{\n // スタックの長さを 0、容量を 16 に設定する\n data: make([]int, 0, 16),\n }\n}\n\n/* スタックの長さ */\nfunc (s *arrayStack) size() int {\n return len(s.data)\n}\n\n/* スタックが空かどうか */\nfunc (s *arrayStack) isEmpty() bool {\n return s.size() == 0\n}\n\n/* プッシュ */\nfunc (s *arrayStack) push(v int) {\n // スライスの容量は自動で拡張される\n s.data = append(s.data, v)\n}\n\n/* ポップ */\nfunc (s *arrayStack) pop() any {\n val := s.peek()\n s.data = s.data[:len(s.data)-1]\n return val\n}\n\n/* スタックの先頭要素を取得する */\nfunc (s *arrayStack) peek() any {\n if s.isEmpty() {\n return nil\n }\n val := s.data[len(s.data)-1]\n return val\n}\n\n/* 表示用の Slice を取得する */\nfunc (s *arrayStack) toSlice() []int {\n return s.data\n}\n</code></pre> array_stack.swift<pre><code>/* 配列で実装したスタック */\nclass ArrayStack {\n private var stack: [Int]\n\n init() {\n // リスト(動的配列)を初期化する\n stack = []\n }\n\n /* スタックの長さを取得する */\n func size() -&gt; Int {\n stack.count\n }\n\n /* スタックが空かどうかを判定する */\n func isEmpty() -&gt; Bool {\n stack.isEmpty\n }\n\n /* プッシュ */\n func push(num: Int) {\n stack.append(num)\n }\n\n /* ポップ */\n @discardableResult\n func pop() -&gt; Int {\n if isEmpty() {\n fatalError(\"スタックが空です\")\n }\n return stack.removeLast()\n }\n\n /* スタックの先頭要素にアクセスする */\n func peek() -&gt; Int {\n if isEmpty() {\n fatalError(\"スタックが空です\")\n }\n return stack.last!\n }\n\n /* List を Array に変換して返す */\n func toArray() -&gt; [Int] {\n stack\n }\n}\n</code></pre> array_stack.js<pre><code>/* 配列で実装したスタック */\nclass ArrayStack {\n #stack;\n constructor() {\n this.#stack = [];\n }\n\n /* スタックの長さを取得する */\n get size() {\n return this.#stack.length;\n }\n\n /* スタックが空かどうかを判定する */\n isEmpty() {\n return this.#stack.length === 0;\n }\n\n /* プッシュ */\n push(num) {\n this.#stack.push(num);\n }\n\n /* ポップ */\n pop() {\n if (this.isEmpty()) throw new Error('スタックが空です');\n return this.#stack.pop();\n }\n\n /* スタックの先頭要素にアクセスする */\n top() {\n if (this.isEmpty()) throw new Error('スタックが空です');\n return this.#stack[this.#stack.length - 1];\n }\n\n /* Array を返す */\n toArray() {\n return this.#stack;\n }\n}\n</code></pre> array_stack.ts<pre><code>/* 配列で実装したスタック */\nclass ArrayStack {\n private stack: number[];\n constructor() {\n this.stack = [];\n }\n\n /* スタックの長さを取得する */\n get size(): number {\n return this.stack.length;\n }\n\n /* スタックが空かどうかを判定する */\n isEmpty(): boolean {\n return this.stack.length === 0;\n }\n\n /* プッシュ */\n push(num: number): void {\n this.stack.push(num);\n }\n\n /* ポップ */\n pop(): number | undefined {\n if (this.isEmpty()) throw new Error('スタックが空です');\n return this.stack.pop();\n }\n\n /* スタックの先頭要素にアクセスする */\n top(): number | undefined {\n if (this.isEmpty()) throw new Error('スタックが空です');\n return this.stack[this.stack.length - 1];\n }\n\n /* Array を返す */\n toArray() {\n return this.stack;\n }\n}\n</code></pre> array_stack.dart<pre><code>/* 配列で実装したスタック */\nclass ArrayStack {\n late List&lt;int&gt; _stack;\n ArrayStack() {\n _stack = [];\n }\n\n /* スタックの長さを取得する */\n int size() {\n return _stack.length;\n }\n\n /* スタックが空かどうかを判定する */\n bool isEmpty() {\n return _stack.isEmpty;\n }\n\n /* プッシュ */\n void push(int _num) {\n _stack.add(_num);\n }\n\n /* ポップ */\n int pop() {\n if (isEmpty()) {\n throw Exception(\"スタックが空です\");\n }\n return _stack.removeLast();\n }\n\n /* スタックの先頭要素にアクセスする */\n int peek() {\n if (isEmpty()) {\n throw Exception(\"スタックが空です\");\n }\n return _stack.last;\n }\n\n /* スタックを Array に変換して返す */\n List&lt;int&gt; toArray() =&gt; _stack;\n}\n</code></pre> array_stack.rs<pre><code>/* 配列で実装したスタック */\nstruct ArrayStack&lt;T&gt; {\n stack: Vec&lt;T&gt;,\n}\n\nimpl&lt;T&gt; ArrayStack&lt;T&gt; {\n /* スタックを初期化する */\n fn new() -&gt; ArrayStack&lt;T&gt; {\n ArrayStack::&lt;T&gt; {\n stack: Vec::&lt;T&gt;::new(),\n }\n }\n\n /* スタックの長さを取得する */\n fn size(&amp;self) -&gt; usize {\n self.stack.len()\n }\n\n /* スタックが空かどうかを判定する */\n fn is_empty(&amp;self) -&gt; bool {\n self.size() == 0\n }\n\n /* プッシュ */\n fn push(&amp;mut self, num: T) {\n self.stack.push(num);\n }\n\n /* ポップ */\n fn pop(&amp;mut self) -&gt; Option&lt;T&gt; {\n self.stack.pop()\n }\n\n /* スタックの先頭要素にアクセスする */\n fn peek(&amp;self) -&gt; Option&lt;&amp;T&gt; {\n if self.is_empty() {\n panic!(\"スタックが空です\")\n };\n self.stack.last()\n }\n\n /* &amp;Vec を返す */\n fn to_array(&amp;self) -&gt; &amp;Vec&lt;T&gt; {\n &amp;self.stack\n }\n}\n</code></pre> array_stack.c<pre><code>/* 配列で実装したスタック */\ntypedef struct {\n int *data;\n int size;\n} ArrayStack;\n\n/* コンストラクタ */\nArrayStack *newArrayStack() {\n ArrayStack *stack = malloc(sizeof(ArrayStack));\n // 容量の拡張を避けるため、初期容量を大きくする\n stack-&gt;data = malloc(sizeof(int) * MAX_SIZE);\n stack-&gt;size = 0;\n return stack;\n}\n\n/* デストラクタ */\nvoid delArrayStack(ArrayStack *stack) {\n free(stack-&gt;data);\n free(stack);\n}\n\n/* スタックの長さを取得する */\nint size(ArrayStack *stack) {\n return stack-&gt;size;\n}\n\n/* スタックが空かどうかを判定する */\nbool isEmpty(ArrayStack *stack) {\n return stack-&gt;size == 0;\n}\n\n/* プッシュ */\nvoid push(ArrayStack *stack, int num) {\n if (stack-&gt;size == MAX_SIZE) {\n printf(\"スタックが満杯です\\n\");\n return;\n }\n stack-&gt;data[stack-&gt;size] = num;\n stack-&gt;size++;\n}\n\n/* スタックの先頭要素にアクセスする */\nint peek(ArrayStack *stack) {\n if (stack-&gt;size == 0) {\n printf(\"スタックが空です\\n\");\n return INT_MAX;\n }\n return stack-&gt;data[stack-&gt;size - 1];\n}\n\n/* ポップ */\nint pop(ArrayStack *stack) {\n int val = peek(stack);\n stack-&gt;size--;\n return val;\n}\n</code></pre> array_stack.kt<pre><code>/* 配列で実装したスタック */\nclass ArrayStack {\n // リスト(動的配列)を初期化する\n private val stack = mutableListOf&lt;Int&gt;()\n\n /* スタックの長さを取得する */\n fun size(): Int {\n return stack.size\n }\n\n /* スタックが空かどうかを判定する */\n fun isEmpty(): Boolean {\n return size() == 0\n }\n\n /* プッシュ */\n fun push(num: Int) {\n stack.add(num)\n }\n\n /* ポップ */\n fun pop(): Int {\n if (isEmpty()) throw IndexOutOfBoundsException()\n return stack.removeAt(size() - 1)\n }\n\n /* スタックの先頭要素にアクセスする */\n fun peek(): Int {\n if (isEmpty()) throw IndexOutOfBoundsException()\n return stack[size() - 1]\n }\n\n /* List を Array に変換して返す */\n fun toArray(): Array&lt;Any&gt; {\n return stack.toTypedArray()\n }\n}\n</code></pre> array_stack.rb<pre><code>### 配列で実装したスタック ###\nclass ArrayStack\n ### コンストラクタ ###\n def initialize\n @stack = []\n end\n\n ### スタックの長さを取得する ###\n def size\n @stack.length\n end\n\n ### スタックが空かどうかを判定する ###\n def is_empty?\n @stack.empty?\n end\n\n ### プッシュ ###\n def push(item)\n @stack &lt;&lt; item\n end\n\n ### ポップ ###\n def pop\n raise IndexError, 'スタックが空です' if is_empty?\n\n @stack.pop\n end\n\n ### スタックの先頭要素にアクセスする ###\n def peek\n raise IndexError, 'スタックが空です' if is_empty?\n\n @stack.last\n end\n\n ### 表示用のリストを返す ###\n def to_array\n @stack\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 5 章   スタックとキュー","5.1   スタック"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#513","level":2,"title":"5.1.3   二つの実装の比較","text":"<p>対応する操作</p> <p>どちらもスタックで定義された操作をすべて実現できます。配列の実装はランダムアクセスにも対応しますが、スタックの定義を超えるため、通常は使いません。</p> <p>時間効率</p> <p>配列の実装では、プッシュとポップは確保済みの連続したメモリ上で行われます。キャッシュの局所性がよいため、効率が高くなります。ただし、プッシュで容量を超えると拡張が発生し、その一回の時間計算量は \\(O(n)\\) になります。</p> <p>連結リストは柔軟に拡張できるため、配列の容量拡張による効率低下はありません。ただし、プッシュ時にノードの初期化とポインタの変更が必要なので、効率は比較的低くなります。要素自体がノードオブジェクトなら、初期化を省いて効率を高められます。</p> <p>以上より、<code>int</code> や <code>double</code> などの基本データ型をプッシュ・ポップする場合、次のように結論付けられます。</p> <ul> <li>配列によるスタックは、拡張時に効率が下がるものの、その頻度は低いため、平均的な効率は高くなります。</li> <li>連結リストによるスタックは、より安定した効率を提供できます。</li> </ul> <p>空間効率</p> <p>リストを初期化すると、実際の必要量を超える場合がある初期容量が割り当てられます。また、拡張は通常、2 倍など一定の倍率で行うため、拡張後も必要量を超える可能性があります。したがって、配列によるスタックでは、ある程度の領域が無駄になる場合があります。</p> <p>一方、連結リストのノードはポインタも格納するため、一つのノードが使う領域は比較的大きくなります。</p> <p>どちらがメモリを節約できるかは一概に決められず、具体的な状況を分析する必要があります。</p>","path":["第 5 章   スタックとキュー","5.1   スタック"],"tags":[]},{"location":"chapter_stack_and_queue/stack/#514","level":2,"title":"5.1.4   スタックの代表的な用途","text":"<ul> <li>ブラウザの戻る・進む、ソフトウェアの元に戻す・やり直す。新しいページを開くたびに、ブラウザは前のページをプッシュします。戻る操作で前のページに戻るときは、実際にはポップを行っています。戻ると進むの両方に対応するには、二つのスタックを組み合わせます。</li> <li>プログラムのメモリ管理。関数を呼ぶたびに、システムはスタックの先頭にスタックフレームを追加し、関数の実行状況を記録します。再帰関数では、呼び出しを深める段階でプッシュを繰り返し、戻る段階でポップを繰り返します。</li> </ul>","path":["第 5 章   スタックとキュー","5.1   スタック"],"tags":[]},{"location":"chapter_stack_and_queue/summary/","level":1,"title":"5.4   まとめ","text":"","path":["第 5 章   スタックとキュー","5.4   まとめ"],"tags":[]},{"location":"chapter_stack_and_queue/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>スタックは先入れ後出しの原則に従うデータ構造であり、配列や連結リストで実装できます。</li> <li>時間効率では、配列によるスタックの実装は平均的な効率が高いものの、容量拡張時には一回のプッシュの時間計算量が \\(O(n)\\) に悪化します。これに対し、連結リストによる実装は、より安定した効率を示します。</li> <li>空間効率では、配列によるスタックの実装はある程度の領域の無駄を生じる可能性があります。ただし、連結リストのノードが占めるメモリは配列の要素より大きい点に注意が必要です。</li> <li>キューは先入れ先出しの原則に従うデータ構造であり、同じく配列や連結リストで実装できます。時間効率と空間効率の比較については、前述のスタックと同様の結論になります。</li> <li>両端キューは自由度の高いキューであり、両端で要素を追加したり削除したりできます。</li> </ul>","path":["第 5 章   スタックとキュー","5.4   まとめ"],"tags":[]},{"location":"chapter_stack_and_queue/summary/#2-q-a","level":3,"title":"2.   Q &amp; A","text":"<p>Q:ブラウザの進む・戻る機能は双方向連結リストで実装されていますか?</p> <p>ブラウザの進む・戻る機能は、本質的に「スタック」の仕組みを表しています。新しいページを開くと、そのページがスタックの先頭に追加され、戻るボタンを押すと、先頭から取り出されます。両端キューを使うと、一部の追加操作も容易に実装できます。これについては「両端キュー」の節で触れています。</p> <p>Q:ポップした後は、取り出したノードのメモリを解放する必要がありますか?</p> <p>その後も取り出したノードを使うなら、メモリを解放する必要はありません。使わなくなった場合、<code>Java</code> や <code>Python</code> などには自動ガベージコレクションがあるため、手動での解放は不要です。<code>C</code> と <code>C++</code> では手動でメモリを解放する必要があります。</p> <p>Q:両端キューは二つのスタックをつなげたように見えますが、どのような用途がありますか?</p> <p>両端キューはスタックとキューの組み合わせ、あるいは二つのスタックをつなげたものに似ています。スタックとキューの両方の論理を持つため、それらのすべての用途を実現でき、さらに柔軟性も高いです。</p> <p>Q:元に戻す(undo)とやり直す(redo)は、具体的にどう実装しますか?</p> <p>二つのスタックを使い、スタック <code>A</code> を元に戻す操作に、スタック <code>B</code> をやり直す操作に用います。</p> <ol> <li>ユーザーが操作するたびに、その操作をスタック <code>A</code> にプッシュし、スタック <code>B</code> を空にします。</li> <li>ユーザーが「元に戻す」を実行したら、スタック <code>A</code> から直近の操作をポップし、スタック <code>B</code> にプッシュします。</li> <li>ユーザーが「やり直す」を実行したら、スタック <code>B</code> から直近の操作をポップし、スタック <code>A</code> にプッシュします。</li> </ol>","path":["第 5 章   スタックとキュー","5.4   まとめ"],"tags":[]},{"location":"chapter_tree/","level":1,"title":"第 7 章   木","text":"<p>概要</p> <p>天高くそびえる大樹は生命力にあふれ、深く根を張り、葉を茂らせ、枝を広げています。</p> <p>その姿は、データを分割統治する仕組みを生き生きと示しています。</p>","path":["第 7 章   木"],"tags":[]},{"location":"chapter_tree/#_1","level":2,"title":"この章の内容","text":"<ul> <li>7.1   二分木</li> <li>7.2   二分木の走査</li> <li>7.3   二分木の配列表現</li> <li>7.4   二分探索木</li> <li>7.5   AVL 木 *</li> <li>7.6   まとめ</li> <li>7.7   練習問題</li> </ul>","path":["第 7 章   木"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/","level":1,"title":"7.3   二分木の配列表現","text":"<p>連結リスト形式では、二分木の格納単位はノード <code>TreeNode</code> で、ノード同士はポインタでつながっています。前節では、この形式の二分木の基本操作を紹介しました。</p> <p>では、二分木を配列で表すこともできるでしょうか。答えは、できます。</p>","path":["第 7 章   木","7.3   二分木の配列表現"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#731","level":2,"title":"7.3.1   パーフェクト二分木の表現","text":"<p>まず単純な場合を考えます。パーフェクト二分木の全ノードをレベル順に配列へ格納すると、各ノードが一意のインデックスに対応します。</p> <p>レベル順走査の特徴から、親と子のインデックスの対応式を導けます。ノードのインデックスが \\(i\\) なら、左の子は \\(2i + 1\\)、右の子は \\(2i + 2\\) です。図 7-12 に対応関係を示します。</p> <p></p> <p> 図 7-12   パーフェクト二分木の配列表現 </p> <p>この対応式は、連結リストのノード参照(ポインタ)と同じ役割を果たします。配列内のどのノードからでも、式を使って左右の子にアクセスできます。</p>","path":["第 7 章   木","7.3   二分木の配列表現"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#732","level":2,"title":"7.3.2   任意の二分木の表現","text":"<p>パーフェクト二分木は特殊な場合です。一般の二分木では、中間の層にも多くの <code>None</code> があります。レベル順の列にはそれらが含まれないため、その列だけでは <code>None</code> の数や位置を推測できません。つまり、同じレベル順の列に対応する二分木の構造が複数あります。</p> <p>図 7-13 のように、パーフェクト二分木でない木には、上の配列表現をそのまま使えません。</p> <p></p> <p> 図 7-13   同じレベル順の列に対応する複数の二分木 </p> <p>解決するには、レベル順の列にすべての <code>None</code> を明示的に書き込みます。図 7-14 のように、これで二分木を一意に表せます。コード例を示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code># 二分木の配列表現\n# None で空の位置を表す\ntree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]\n</code></pre> <pre><code>/* 二分木の配列表現 */\n// int の最大値 INT_MAX で空の位置を示す\nvector&lt;int&gt; tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};\n</code></pre> <pre><code>/* 二分木の配列表現 */\n// int のラッパークラス Integer を使うと、null で空の位置を示せる\nInteger[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };\n</code></pre> <pre><code>/* 二分木の配列表現 */\n// null 許容型 int? を使うと、null で空の位置を示せる\nint?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n</code></pre> <pre><code>/* 二分木の配列表現 */\n// any 型のスライスを使うと、nil で空の位置を示せる\ntree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15}\n</code></pre> <pre><code>/* 二分木の配列表現 */\n// オプショナル型 Int? を使うと、nil で空の位置を示せる\nlet tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]\n</code></pre> <pre><code>/* 二分木の配列表現 */\n// null で空の位置を表す\nlet tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n</code></pre> <pre><code>/* 二分木の配列表現 */\n// null で空の位置を表す\nlet tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n</code></pre> <pre><code>/* 二分木の配列表現 */\n// null 許容型 int? を使うと、null で空の位置を示せる\nList&lt;int?&gt; tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];\n</code></pre> <pre><code>/* 二分木の配列表現 */\n// None で空の位置を示す\nlet tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)];\n</code></pre> <pre><code>/* 二分木の配列表現 */\n// int の最大値で空の位置を示すため、ノードの値に INT_MAX は使えない\nint tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};\n</code></pre> <pre><code>/* 二分木の配列表現 */\n// null で空の位置を表す\nval tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 )\n</code></pre> <pre><code>### 二分木の配列表現 ###\n# nil で空の位置を表す\ntree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]\n</code></pre> <p></p> <p> 図 7-14   任意の二分木の配列表現 </p> <p>完全二分木は、配列で表すのに非常に適しています。定義を思い出すと、<code>None</code> は最下層の右側にしかありません。したがって、すべての <code>None</code> はレベル順の列の末尾に現れます。</p> <p>つまり、完全二分木を配列で表す場合は、<code>None</code> の格納をすべて省けるので便利です。図 7-15 に例を示します。</p> <p></p> <p> 図 7-15   完全二分木の配列表現 </p> <p>以下は配列で二分木を実装したコードで、次の操作を含みます。</p> <ul> <li>指定したノードの値、左右の子、親を取得します。</li> <li>行きがけ順、通りがけ順、帰りがけ順、レベル順の走査列を取得します。</li> </ul> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby array_binary_tree.py<pre><code>class ArrayBinaryTree:\n \"\"\"配列で表した二分木クラス\"\"\"\n\n def __init__(self, arr: list[int | None]):\n \"\"\"コンストラクタ\"\"\"\n self._tree = list(arr)\n\n def size(self):\n \"\"\"リストの容量\"\"\"\n return len(self._tree)\n\n def val(self, i: int) -&gt; int | None:\n \"\"\"インデックス i のノードの値を取得する\"\"\"\n # インデックスが範囲外の場合、空きを表す None を返す\n if i &lt; 0 or i &gt;= self.size():\n return None\n return self._tree[i]\n\n def left(self, i: int) -&gt; int | None:\n \"\"\"インデックス i のノードの左の子ノードのインデックスを取得する\"\"\"\n return 2 * i + 1\n\n def right(self, i: int) -&gt; int | None:\n \"\"\"インデックス i のノードの右の子ノードのインデックスを取得する\"\"\"\n return 2 * i + 2\n\n def parent(self, i: int) -&gt; int | None:\n \"\"\"インデックス i のノードの親ノードのインデックスを取得する\"\"\"\n return (i - 1) // 2\n\n def level_order(self) -&gt; list[int]:\n \"\"\"レベル順走査\"\"\"\n self.res = []\n # 配列をそのまま走査する\n for i in range(self.size()):\n if self.val(i) is not None:\n self.res.append(self.val(i))\n return self.res\n\n def dfs(self, i: int, order: str):\n \"\"\"深さ優先走査\"\"\"\n if self.val(i) is None:\n return\n # 行きがけ順走査\n if order == \"pre\":\n self.res.append(self.val(i))\n self.dfs(self.left(i), order)\n # 通りがけ順走査\n if order == \"in\":\n self.res.append(self.val(i))\n self.dfs(self.right(i), order)\n # 帰りがけ順走査\n if order == \"post\":\n self.res.append(self.val(i))\n\n def pre_order(self) -&gt; list[int]:\n \"\"\"行きがけ順走査\"\"\"\n self.res = []\n self.dfs(0, order=\"pre\")\n return self.res\n\n def in_order(self) -&gt; list[int]:\n \"\"\"通りがけ順走査\"\"\"\n self.res = []\n self.dfs(0, order=\"in\")\n return self.res\n\n def post_order(self) -&gt; list[int]:\n \"\"\"帰りがけ順走査\"\"\"\n self.res = []\n self.dfs(0, order=\"post\")\n return self.res\n</code></pre> array_binary_tree.cpp<pre><code>/* 配列で表した二分木クラス */\nclass ArrayBinaryTree {\n public:\n /* コンストラクタ */\n ArrayBinaryTree(vector&lt;int&gt; arr) {\n tree = arr;\n }\n\n /* リストの容量 */\n int size() {\n return tree.size();\n }\n\n /* インデックス i のノードの値を取得する */\n int val(int i) {\n // インデックスが範囲外の場合、空きを表す INT_MAX を返す\n if (i &lt; 0 || i &gt;= size())\n return INT_MAX;\n return tree[i];\n }\n\n /* インデックス i のノードの左の子ノードのインデックスを取得する */\n int left(int i) {\n return 2 * i + 1;\n }\n\n /* インデックス i のノードの右の子ノードのインデックスを取得する */\n int right(int i) {\n return 2 * i + 2;\n }\n\n /* インデックス i のノードの親ノードのインデックスを取得する */\n int parent(int i) {\n return (i - 1) / 2;\n }\n\n /* レベル順走査 */\n vector&lt;int&gt; levelOrder() {\n vector&lt;int&gt; res;\n // 配列をそのまま走査する\n for (int i = 0; i &lt; size(); i++) {\n if (val(i) != INT_MAX)\n res.push_back(val(i));\n }\n return res;\n }\n\n /* 行きがけ順走査 */\n vector&lt;int&gt; preOrder() {\n vector&lt;int&gt; res;\n dfs(0, \"pre\", res);\n return res;\n }\n\n /* 通りがけ順走査 */\n vector&lt;int&gt; inOrder() {\n vector&lt;int&gt; res;\n dfs(0, \"in\", res);\n return res;\n }\n\n /* 帰りがけ順走査 */\n vector&lt;int&gt; postOrder() {\n vector&lt;int&gt; res;\n dfs(0, \"post\", res);\n return res;\n }\n\n private:\n vector&lt;int&gt; tree;\n\n /* 深さ優先走査 */\n void dfs(int i, string order, vector&lt;int&gt; &amp;res) {\n // 空きの場合、戻る\n if (val(i) == INT_MAX)\n return;\n // 行きがけ順走査\n if (order == \"pre\")\n res.push_back(val(i));\n dfs(left(i), order, res);\n // 通りがけ順走査\n if (order == \"in\")\n res.push_back(val(i));\n dfs(right(i), order, res);\n // 帰りがけ順走査\n if (order == \"post\")\n res.push_back(val(i));\n }\n};\n</code></pre> array_binary_tree.java<pre><code>/* 配列で表した二分木クラス */\nclass ArrayBinaryTree {\n private List&lt;Integer&gt; tree;\n\n /* コンストラクタ */\n public ArrayBinaryTree(List&lt;Integer&gt; arr) {\n tree = new ArrayList&lt;&gt;(arr);\n }\n\n /* リストの容量 */\n public int size() {\n return tree.size();\n }\n\n /* インデックス i のノードの値を取得する */\n public Integer val(int i) {\n // インデックスが範囲外の場合、空きを表す null を返す\n if (i &lt; 0 || i &gt;= size())\n return null;\n return tree.get(i);\n }\n\n /* インデックス i のノードの左の子ノードのインデックスを取得する */\n public Integer left(int i) {\n return 2 * i + 1;\n }\n\n /* インデックス i のノードの右の子ノードのインデックスを取得する */\n public Integer right(int i) {\n return 2 * i + 2;\n }\n\n /* インデックス i のノードの親ノードのインデックスを取得する */\n public Integer parent(int i) {\n return (i - 1) / 2;\n }\n\n /* レベル順走査 */\n public List&lt;Integer&gt; levelOrder() {\n List&lt;Integer&gt; res = new ArrayList&lt;&gt;();\n // 配列をそのまま走査する\n for (int i = 0; i &lt; size(); i++) {\n if (val(i) != null)\n res.add(val(i));\n }\n return res;\n }\n\n /* 深さ優先走査 */\n private void dfs(Integer i, String order, List&lt;Integer&gt; res) {\n // 空きの場合、戻る\n if (val(i) == null)\n return;\n // 行きがけ順走査\n if (\"pre\".equals(order))\n res.add(val(i));\n dfs(left(i), order, res);\n // 通りがけ順走査\n if (\"in\".equals(order))\n res.add(val(i));\n dfs(right(i), order, res);\n // 帰りがけ順走査\n if (\"post\".equals(order))\n res.add(val(i));\n }\n\n /* 行きがけ順走査 */\n public List&lt;Integer&gt; preOrder() {\n List&lt;Integer&gt; res = new ArrayList&lt;&gt;();\n dfs(0, \"pre\", res);\n return res;\n }\n\n /* 通りがけ順走査 */\n public List&lt;Integer&gt; inOrder() {\n List&lt;Integer&gt; res = new ArrayList&lt;&gt;();\n dfs(0, \"in\", res);\n return res;\n }\n\n /* 帰りがけ順走査 */\n public List&lt;Integer&gt; postOrder() {\n List&lt;Integer&gt; res = new ArrayList&lt;&gt;();\n dfs(0, \"post\", res);\n return res;\n }\n}\n</code></pre> array_binary_tree.cs<pre><code>/* 配列で表した二分木クラス */\nclass ArrayBinaryTree(List&lt;int?&gt; arr) {\n List&lt;int?&gt; tree = new(arr);\n\n /* リストの容量 */\n public int Size() {\n return tree.Count;\n }\n\n /* インデックス i のノードの値を取得する */\n public int? Val(int i) {\n // インデックスが範囲外の場合、空きを表す null を返す\n if (i &lt; 0 || i &gt;= Size())\n return null;\n return tree[i];\n }\n\n /* インデックス i のノードの左の子ノードのインデックスを取得する */\n public int Left(int i) {\n return 2 * i + 1;\n }\n\n /* インデックス i のノードの右の子ノードのインデックスを取得する */\n public int Right(int i) {\n return 2 * i + 2;\n }\n\n /* インデックス i のノードの親ノードのインデックスを取得する */\n public int Parent(int i) {\n return (i - 1) / 2;\n }\n\n /* レベル順走査 */\n public List&lt;int&gt; LevelOrder() {\n List&lt;int&gt; res = [];\n // 配列をそのまま走査する\n for (int i = 0; i &lt; Size(); i++) {\n if (Val(i).HasValue)\n res.Add(Val(i)!.Value);\n }\n return res;\n }\n\n /* 深さ優先走査 */\n void DFS(int i, string order, List&lt;int&gt; res) {\n // 空きの場合、戻る\n if (!Val(i).HasValue)\n return;\n // 行きがけ順走査\n if (order == \"pre\")\n res.Add(Val(i)!.Value);\n DFS(Left(i), order, res);\n // 通りがけ順走査\n if (order == \"in\")\n res.Add(Val(i)!.Value);\n DFS(Right(i), order, res);\n // 帰りがけ順走査\n if (order == \"post\")\n res.Add(Val(i)!.Value);\n }\n\n /* 行きがけ順走査 */\n public List&lt;int&gt; PreOrder() {\n List&lt;int&gt; res = [];\n DFS(0, \"pre\", res);\n return res;\n }\n\n /* 通りがけ順走査 */\n public List&lt;int&gt; InOrder() {\n List&lt;int&gt; res = [];\n DFS(0, \"in\", res);\n return res;\n }\n\n /* 帰りがけ順走査 */\n public List&lt;int&gt; PostOrder() {\n List&lt;int&gt; res = [];\n DFS(0, \"post\", res);\n return res;\n }\n}\n</code></pre> array_binary_tree.go<pre><code>/* 配列で表した二分木クラス */\ntype arrayBinaryTree struct {\n tree []any\n}\n\n/* コンストラクタ */\nfunc newArrayBinaryTree(arr []any) *arrayBinaryTree {\n return &amp;arrayBinaryTree{\n tree: arr,\n }\n}\n\n/* リストの容量 */\nfunc (abt *arrayBinaryTree) size() int {\n return len(abt.tree)\n}\n\n/* インデックス i のノードの値を取得する */\nfunc (abt *arrayBinaryTree) val(i int) any {\n // インデックスが範囲外の場合、空きを表す null を返す\n if i &lt; 0 || i &gt;= abt.size() {\n return nil\n }\n return abt.tree[i]\n}\n\n/* インデックス i のノードの左の子ノードのインデックスを取得する */\nfunc (abt *arrayBinaryTree) left(i int) int {\n return 2*i + 1\n}\n\n/* インデックス i のノードの右の子ノードのインデックスを取得する */\nfunc (abt *arrayBinaryTree) right(i int) int {\n return 2*i + 2\n}\n\n/* インデックス i のノードの親ノードのインデックスを取得する */\nfunc (abt *arrayBinaryTree) parent(i int) int {\n return (i - 1) / 2\n}\n\n/* レベル順走査 */\nfunc (abt *arrayBinaryTree) levelOrder() []any {\n var res []any\n // 配列をそのまま走査する\n for i := 0; i &lt; abt.size(); i++ {\n if abt.val(i) != nil {\n res = append(res, abt.val(i))\n }\n }\n return res\n}\n\n/* 深さ優先走査 */\nfunc (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) {\n // 空きの場合、戻る\n if abt.val(i) == nil {\n return\n }\n // 行きがけ順走査\n if order == \"pre\" {\n *res = append(*res, abt.val(i))\n }\n abt.dfs(abt.left(i), order, res)\n // 通りがけ順走査\n if order == \"in\" {\n *res = append(*res, abt.val(i))\n }\n abt.dfs(abt.right(i), order, res)\n // 帰りがけ順走査\n if order == \"post\" {\n *res = append(*res, abt.val(i))\n }\n}\n\n/* 行きがけ順走査 */\nfunc (abt *arrayBinaryTree) preOrder() []any {\n var res []any\n abt.dfs(0, \"pre\", &amp;res)\n return res\n}\n\n/* 通りがけ順走査 */\nfunc (abt *arrayBinaryTree) inOrder() []any {\n var res []any\n abt.dfs(0, \"in\", &amp;res)\n return res\n}\n\n/* 帰りがけ順走査 */\nfunc (abt *arrayBinaryTree) postOrder() []any {\n var res []any\n abt.dfs(0, \"post\", &amp;res)\n return res\n}\n</code></pre> array_binary_tree.swift<pre><code>/* 配列で表した二分木クラス */\nclass ArrayBinaryTree {\n private var tree: [Int?]\n\n /* コンストラクタ */\n init(arr: [Int?]) {\n tree = arr\n }\n\n /* リストの容量 */\n func size() -&gt; Int {\n tree.count\n }\n\n /* インデックス i のノードの値を取得する */\n func val(i: Int) -&gt; Int? {\n // インデックスが範囲外の場合、空きを表す null を返す\n if i &lt; 0 || i &gt;= size() {\n return nil\n }\n return tree[i]\n }\n\n /* インデックス i のノードの左の子ノードのインデックスを取得する */\n func left(i: Int) -&gt; Int {\n 2 * i + 1\n }\n\n /* インデックス i のノードの右の子ノードのインデックスを取得する */\n func right(i: Int) -&gt; Int {\n 2 * i + 2\n }\n\n /* インデックス i のノードの親ノードのインデックスを取得する */\n func parent(i: Int) -&gt; Int {\n (i - 1) / 2\n }\n\n /* レベル順走査 */\n func levelOrder() -&gt; [Int] {\n var res: [Int] = []\n // 配列をそのまま走査する\n for i in 0 ..&lt; size() {\n if let val = val(i: i) {\n res.append(val)\n }\n }\n return res\n }\n\n /* 深さ優先走査 */\n private func dfs(i: Int, order: String, res: inout [Int]) {\n // 空きの場合、戻る\n guard let val = val(i: i) else {\n return\n }\n // 行きがけ順走査\n if order == \"pre\" {\n res.append(val)\n }\n dfs(i: left(i: i), order: order, res: &amp;res)\n // 通りがけ順走査\n if order == \"in\" {\n res.append(val)\n }\n dfs(i: right(i: i), order: order, res: &amp;res)\n // 帰りがけ順走査\n if order == \"post\" {\n res.append(val)\n }\n }\n\n /* 行きがけ順走査 */\n func preOrder() -&gt; [Int] {\n var res: [Int] = []\n dfs(i: 0, order: \"pre\", res: &amp;res)\n return res\n }\n\n /* 通りがけ順走査 */\n func inOrder() -&gt; [Int] {\n var res: [Int] = []\n dfs(i: 0, order: \"in\", res: &amp;res)\n return res\n }\n\n /* 帰りがけ順走査 */\n func postOrder() -&gt; [Int] {\n var res: [Int] = []\n dfs(i: 0, order: \"post\", res: &amp;res)\n return res\n }\n}\n</code></pre> array_binary_tree.js<pre><code>/* 配列で表した二分木クラス */\nclass ArrayBinaryTree {\n #tree;\n\n /* コンストラクタ */\n constructor(arr) {\n this.#tree = arr;\n }\n\n /* リストの容量 */\n size() {\n return this.#tree.length;\n }\n\n /* インデックス i のノードの値を取得する */\n val(i) {\n // インデックスが範囲外の場合、空きを表す null を返す\n if (i &lt; 0 || i &gt;= this.size()) return null;\n return this.#tree[i];\n }\n\n /* インデックス i のノードの左の子ノードのインデックスを取得する */\n left(i) {\n return 2 * i + 1;\n }\n\n /* インデックス i のノードの右の子ノードのインデックスを取得する */\n right(i) {\n return 2 * i + 2;\n }\n\n /* インデックス i のノードの親ノードのインデックスを取得する */\n parent(i) {\n return Math.floor((i - 1) / 2); // 切り捨て除算\n }\n\n /* レベル順走査 */\n levelOrder() {\n let res = [];\n // 配列をそのまま走査する\n for (let i = 0; i &lt; this.size(); i++) {\n if (this.val(i) !== null) res.push(this.val(i));\n }\n return res;\n }\n\n /* 深さ優先走査 */\n #dfs(i, order, res) {\n // 空きの場合、戻る\n if (this.val(i) === null) return;\n // 行きがけ順走査\n if (order === 'pre') res.push(this.val(i));\n this.#dfs(this.left(i), order, res);\n // 通りがけ順走査\n if (order === 'in') res.push(this.val(i));\n this.#dfs(this.right(i), order, res);\n // 帰りがけ順走査\n if (order === 'post') res.push(this.val(i));\n }\n\n /* 行きがけ順走査 */\n preOrder() {\n const res = [];\n this.#dfs(0, 'pre', res);\n return res;\n }\n\n /* 通りがけ順走査 */\n inOrder() {\n const res = [];\n this.#dfs(0, 'in', res);\n return res;\n }\n\n /* 帰りがけ順走査 */\n postOrder() {\n const res = [];\n this.#dfs(0, 'post', res);\n return res;\n }\n}\n</code></pre> array_binary_tree.ts<pre><code>/* 配列で表した二分木クラス */\nclass ArrayBinaryTree {\n #tree: (number | null)[];\n\n /* コンストラクタ */\n constructor(arr: (number | null)[]) {\n this.#tree = arr;\n }\n\n /* リストの容量 */\n size(): number {\n return this.#tree.length;\n }\n\n /* インデックス i のノードの値を取得する */\n val(i: number): number | null {\n // インデックスが範囲外の場合、空きを表す null を返す\n if (i &lt; 0 || i &gt;= this.size()) return null;\n return this.#tree[i];\n }\n\n /* インデックス i のノードの左の子ノードのインデックスを取得する */\n left(i: number): number {\n return 2 * i + 1;\n }\n\n /* インデックス i のノードの右の子ノードのインデックスを取得する */\n right(i: number): number {\n return 2 * i + 2;\n }\n\n /* インデックス i のノードの親ノードのインデックスを取得する */\n parent(i: number): number {\n return Math.floor((i - 1) / 2); // 切り捨て除算\n }\n\n /* レベル順走査 */\n levelOrder(): number[] {\n let res = [];\n // 配列をそのまま走査する\n for (let i = 0; i &lt; this.size(); i++) {\n if (this.val(i) !== null) res.push(this.val(i));\n }\n return res;\n }\n\n /* 深さ優先走査 */\n #dfs(i: number, order: Order, res: (number | null)[]): void {\n // 空きの場合、戻る\n if (this.val(i) === null) return;\n // 行きがけ順走査\n if (order === 'pre') res.push(this.val(i));\n this.#dfs(this.left(i), order, res);\n // 通りがけ順走査\n if (order === 'in') res.push(this.val(i));\n this.#dfs(this.right(i), order, res);\n // 帰りがけ順走査\n if (order === 'post') res.push(this.val(i));\n }\n\n /* 行きがけ順走査 */\n preOrder(): (number | null)[] {\n const res = [];\n this.#dfs(0, 'pre', res);\n return res;\n }\n\n /* 通りがけ順走査 */\n inOrder(): (number | null)[] {\n const res = [];\n this.#dfs(0, 'in', res);\n return res;\n }\n\n /* 帰りがけ順走査 */\n postOrder(): (number | null)[] {\n const res = [];\n this.#dfs(0, 'post', res);\n return res;\n }\n}\n</code></pre> array_binary_tree.dart<pre><code>/* 配列で表した二分木クラス */\nclass ArrayBinaryTree {\n late List&lt;int?&gt; _tree;\n\n /* コンストラクタ */\n ArrayBinaryTree(this._tree);\n\n /* リストの容量 */\n int size() {\n return _tree.length;\n }\n\n /* インデックス i のノードの値を取得する */\n int? val(int i) {\n // インデックスが範囲外の場合、空きを表す null を返す\n if (i &lt; 0 || i &gt;= size()) {\n return null;\n }\n return _tree[i];\n }\n\n /* インデックス i のノードの左の子ノードのインデックスを取得する */\n int? left(int i) {\n return 2 * i + 1;\n }\n\n /* インデックス i のノードの右の子ノードのインデックスを取得する */\n int? right(int i) {\n return 2 * i + 2;\n }\n\n /* インデックス i のノードの親ノードのインデックスを取得する */\n int? parent(int i) {\n return (i - 1) ~/ 2;\n }\n\n /* レベル順走査 */\n List&lt;int&gt; levelOrder() {\n List&lt;int&gt; res = [];\n for (int i = 0; i &lt; size(); i++) {\n if (val(i) != null) {\n res.add(val(i)!);\n }\n }\n return res;\n }\n\n /* 深さ優先走査 */\n void dfs(int i, String order, List&lt;int?&gt; res) {\n // 空きの場合、戻る\n if (val(i) == null) {\n return;\n }\n // 行きがけ順走査\n if (order == 'pre') {\n res.add(val(i));\n }\n dfs(left(i)!, order, res);\n // 通りがけ順走査\n if (order == 'in') {\n res.add(val(i));\n }\n dfs(right(i)!, order, res);\n // 帰りがけ順走査\n if (order == 'post') {\n res.add(val(i));\n }\n }\n\n /* 行きがけ順走査 */\n List&lt;int?&gt; preOrder() {\n List&lt;int?&gt; res = [];\n dfs(0, 'pre', res);\n return res;\n }\n\n /* 通りがけ順走査 */\n List&lt;int?&gt; inOrder() {\n List&lt;int?&gt; res = [];\n dfs(0, 'in', res);\n return res;\n }\n\n /* 帰りがけ順走査 */\n List&lt;int?&gt; postOrder() {\n List&lt;int?&gt; res = [];\n dfs(0, 'post', res);\n return res;\n }\n}\n</code></pre> array_binary_tree.rs<pre><code>/* 配列で表した二分木クラス */\nstruct ArrayBinaryTree {\n tree: Vec&lt;Option&lt;i32&gt;&gt;,\n}\n\nimpl ArrayBinaryTree {\n /* コンストラクタ */\n fn new(arr: Vec&lt;Option&lt;i32&gt;&gt;) -&gt; Self {\n Self { tree: arr }\n }\n\n /* リストの容量 */\n fn size(&amp;self) -&gt; i32 {\n self.tree.len() as i32\n }\n\n /* インデックス i のノードの値を取得する */\n fn val(&amp;self, i: i32) -&gt; Option&lt;i32&gt; {\n // インデックスが範囲外の場合、空きを表す None を返す\n if i &lt; 0 || i &gt;= self.size() {\n None\n } else {\n self.tree[i as usize]\n }\n }\n\n /* インデックス i のノードの左の子ノードのインデックスを取得する */\n fn left(&amp;self, i: i32) -&gt; i32 {\n 2 * i + 1\n }\n\n /* インデックス i のノードの右の子ノードのインデックスを取得する */\n fn right(&amp;self, i: i32) -&gt; i32 {\n 2 * i + 2\n }\n\n /* インデックス i のノードの親ノードのインデックスを取得する */\n fn parent(&amp;self, i: i32) -&gt; i32 {\n (i - 1) / 2\n }\n\n /* レベル順走査 */\n fn level_order(&amp;self) -&gt; Vec&lt;i32&gt; {\n self.tree.iter().filter_map(|&amp;x| x).collect()\n }\n\n /* 深さ優先走査 */\n fn dfs(&amp;self, i: i32, order: &amp;'static str, res: &amp;mut Vec&lt;i32&gt;) {\n if self.val(i).is_none() {\n return;\n }\n let val = self.val(i).unwrap();\n // 行きがけ順走査\n if order == \"pre\" {\n res.push(val);\n }\n self.dfs(self.left(i), order, res);\n // 通りがけ順走査\n if order == \"in\" {\n res.push(val);\n }\n self.dfs(self.right(i), order, res);\n // 帰りがけ順走査\n if order == \"post\" {\n res.push(val);\n }\n }\n\n /* 行きがけ順走査 */\n fn pre_order(&amp;self) -&gt; Vec&lt;i32&gt; {\n let mut res = vec![];\n self.dfs(0, \"pre\", &amp;mut res);\n res\n }\n\n /* 通りがけ順走査 */\n fn in_order(&amp;self) -&gt; Vec&lt;i32&gt; {\n let mut res = vec![];\n self.dfs(0, \"in\", &amp;mut res);\n res\n }\n\n /* 帰りがけ順走査 */\n fn post_order(&amp;self) -&gt; Vec&lt;i32&gt; {\n let mut res = vec![];\n self.dfs(0, \"post\", &amp;mut res);\n res\n }\n}\n</code></pre> array_binary_tree.c<pre><code>/* 配列で表した二分木の構造体 */\ntypedef struct {\n int *tree;\n int size;\n} ArrayBinaryTree;\n\n/* コンストラクタ */\nArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) {\n ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree));\n abt-&gt;tree = malloc(sizeof(int) * arrSize);\n memcpy(abt-&gt;tree, arr, sizeof(int) * arrSize);\n abt-&gt;size = arrSize;\n return abt;\n}\n\n/* デストラクタ */\nvoid delArrayBinaryTree(ArrayBinaryTree *abt) {\n free(abt-&gt;tree);\n free(abt);\n}\n\n/* リストの容量 */\nint size(ArrayBinaryTree *abt) {\n return abt-&gt;size;\n}\n\n/* インデックス i のノードの値を取得する */\nint val(ArrayBinaryTree *abt, int i) {\n // インデックスが範囲外の場合、空きを表す INT_MAX を返す\n if (i &lt; 0 || i &gt;= size(abt))\n return INT_MAX;\n return abt-&gt;tree[i];\n}\n\n/* レベル順走査 */\nint *levelOrder(ArrayBinaryTree *abt, int *returnSize) {\n int *res = (int *)malloc(sizeof(int) * size(abt));\n int index = 0;\n // 配列をそのまま走査する\n for (int i = 0; i &lt; size(abt); i++) {\n if (val(abt, i) != INT_MAX)\n res[index++] = val(abt, i);\n }\n *returnSize = index;\n return res;\n}\n\n/* 深さ優先走査 */\nvoid dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) {\n // 空きの場合、戻る\n if (val(abt, i) == INT_MAX)\n return;\n // 行きがけ順走査\n if (strcmp(order, \"pre\") == 0)\n res[(*index)++] = val(abt, i);\n dfs(abt, left(i), order, res, index);\n // 通りがけ順走査\n if (strcmp(order, \"in\") == 0)\n res[(*index)++] = val(abt, i);\n dfs(abt, right(i), order, res, index);\n // 帰りがけ順走査\n if (strcmp(order, \"post\") == 0)\n res[(*index)++] = val(abt, i);\n}\n\n/* 行きがけ順走査 */\nint *preOrder(ArrayBinaryTree *abt, int *returnSize) {\n int *res = (int *)malloc(sizeof(int) * size(abt));\n int index = 0;\n dfs(abt, 0, \"pre\", res, &amp;index);\n *returnSize = index;\n return res;\n}\n\n/* 通りがけ順走査 */\nint *inOrder(ArrayBinaryTree *abt, int *returnSize) {\n int *res = (int *)malloc(sizeof(int) * size(abt));\n int index = 0;\n dfs(abt, 0, \"in\", res, &amp;index);\n *returnSize = index;\n return res;\n}\n\n/* 帰りがけ順走査 */\nint *postOrder(ArrayBinaryTree *abt, int *returnSize) {\n int *res = (int *)malloc(sizeof(int) * size(abt));\n int index = 0;\n dfs(abt, 0, \"post\", res, &amp;index);\n *returnSize = index;\n return res;\n}\n</code></pre> array_binary_tree.kt<pre><code>/* 配列で表した二分木クラス */\nclass ArrayBinaryTree(val tree: MutableList&lt;Int?&gt;) {\n /* リストの容量 */\n fun size(): Int {\n return tree.size\n }\n\n /* インデックス i のノードの値を取得する */\n fun _val(i: Int): Int? {\n // インデックスが範囲外の場合、空きを表す null を返す\n if (i &lt; 0 || i &gt;= size()) return null\n return tree[i]\n }\n\n /* インデックス i のノードの左の子ノードのインデックスを取得する */\n fun left(i: Int): Int {\n return 2 * i + 1\n }\n\n /* インデックス i のノードの右の子ノードのインデックスを取得する */\n fun right(i: Int): Int {\n return 2 * i + 2\n }\n\n /* インデックス i のノードの親ノードのインデックスを取得する */\n fun parent(i: Int): Int {\n return (i - 1) / 2\n }\n\n /* レベル順走査 */\n fun levelOrder(): MutableList&lt;Int?&gt; {\n val res = mutableListOf&lt;Int?&gt;()\n // 配列をそのまま走査する\n for (i in 0..&lt;size()) {\n if (_val(i) != null)\n res.add(_val(i))\n }\n return res\n }\n\n /* 深さ優先走査 */\n fun dfs(i: Int, order: String, res: MutableList&lt;Int?&gt;) {\n // 空きの場合、戻る\n if (_val(i) == null)\n return\n // 行きがけ順走査\n if (\"pre\" == order)\n res.add(_val(i))\n dfs(left(i), order, res)\n // 通りがけ順走査\n if (\"in\" == order)\n res.add(_val(i))\n dfs(right(i), order, res)\n // 帰りがけ順走査\n if (\"post\" == order)\n res.add(_val(i))\n }\n\n /* 行きがけ順走査 */\n fun preOrder(): MutableList&lt;Int?&gt; {\n val res = mutableListOf&lt;Int?&gt;()\n dfs(0, \"pre\", res)\n return res\n }\n\n /* 通りがけ順走査 */\n fun inOrder(): MutableList&lt;Int?&gt; {\n val res = mutableListOf&lt;Int?&gt;()\n dfs(0, \"in\", res)\n return res\n }\n\n /* 帰りがけ順走査 */\n fun postOrder(): MutableList&lt;Int?&gt; {\n val res = mutableListOf&lt;Int?&gt;()\n dfs(0, \"post\", res)\n return res\n }\n}\n</code></pre> array_binary_tree.rb<pre><code>### 配列で表した二分木クラス ###\nclass ArrayBinaryTree\n ### コンストラクタ ###\n def initialize(arr)\n @tree = arr.to_a\n end\n\n ### リストの容量 ###\n def size\n @tree.length\n end\n\n ### インデックス i のノードの値を取得する ###\n def val(i)\n # インデックスが範囲外の場合、空きを表す nil を返す\n return if i &lt; 0 || i &gt;= size\n\n @tree[i]\n end\n\n ### インデックス i のノードの左の子ノードのインデックスを取得する ###\n def left(i)\n 2 * i + 1\n end\n\n ### インデックス i のノードの右の子ノードのインデックスを取得する ###\n def right(i)\n 2 * i + 2\n end\n\n ### インデックス i のノードの親ノードのインデックスを取得する ###\n def parent(i)\n (i - 1) / 2\n end\n\n ### レベル順走査 ###\n def level_order\n @res = []\n\n # 配列をそのまま走査する\n for i in 0...size\n @res &lt;&lt; val(i) unless val(i).nil?\n end\n\n @res\n end\n\n ### 深さ優先走査 ###\n def dfs(i, order)\n return if val(i).nil?\n # 行きがけ順走査\n @res &lt;&lt; val(i) if order == :pre\n dfs(left(i), order)\n # 通りがけ順走査\n @res &lt;&lt; val(i) if order == :in\n dfs(right(i), order)\n # 帰りがけ順走査\n @res &lt;&lt; val(i) if order == :post\n end\n\n ### 行きがけ順走査 ###\n def pre_order\n @res = []\n dfs(0, :pre)\n @res\n end\n\n ### 通りがけ順走査 ###\n def in_order\n @res = []\n dfs(0, :in)\n @res\n end\n\n ### 帰りがけ順走査 ###\n def post_order\n @res = []\n dfs(0, :post)\n @res\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 7 章   木","7.3   二分木の配列表現"],"tags":[]},{"location":"chapter_tree/array_representation_of_tree/#733","level":2,"title":"7.3.3   長所と制約","text":"<p>配列表現の主な長所は次のとおりです。</p> <ul> <li>連続したメモリに格納されるのでキャッシュとの相性がよく、アクセスと走査が高速です。</li> <li>ポインタの格納が不要なので、領域を節約できます。</li> <li>ノードにランダムアクセスできます。</li> </ul> <p>一方、次の制約もあります。</p> <ul> <li>連続したメモリが必要なので、データ量が非常に多い木には適しません。</li> <li>ノードの追加・削除には配列の挿入・削除操作が必要で、効率が低くなります。</li> <li><code>None</code> が多い二分木では、配列内で実際のノードデータが占める割合が低く、領域の利用効率が下がります。</li> </ul>","path":["第 7 章   木","7.3   二分木の配列表現"],"tags":[]},{"location":"chapter_tree/avl_tree/","level":1,"title":"7.5   AVL 木 *","text":"<p>「二分探索木」の節で述べたように、挿入と削除を繰り返すと、二分探索木は連結リストに退化する可能性があります。その場合、すべての操作の時間計算量が \\(O(\\log n)\\) から \\(O(n)\\) に悪化します。</p> <p>図 7-24 の二分探索木は、二回の削除だけで連結リストに退化します。</p> <p></p> <p> 図 7-24   ノードの削除による AVL 木の退化 </p> <p>また、図 7-25 のパーフェクト二分木に二つのノードを挿入すると、木が大きく左へ偏り、探索の時間計算量も悪化します。</p> <p></p> <p> 図 7-25   ノードの挿入による AVL 木の退化 </p> <p>1962 年、G. M. Adelson-Velsky と E. M. Landis は論文「An algorithm for the organization of information」でAVL 木を提案しました。論文では、追加や削除を続けても退化せず、各操作の時間計算量を \\(O(\\log n)\\) に保つ一連の操作を詳しく述べています。つまり、追加、削除、照会、更新が頻繁な場面でも、常に効率的に操作できるため、高い実用性があります。</p>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#751-avl","level":2,"title":"7.5.1   AVL 木の主な用語","text":"<p>AVL 木は二分探索木であると同時に平衡二分木でもあり、両者の性質をすべて満たします。そのため、平衡二分探索木(balanced binary search tree)の一種です。</p>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1","level":3,"title":"1.   ノードの高さ","text":"<p>AVL 木の操作ではノードの高さが必要なので、ノードクラスに変数 <code>height</code> を追加します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code>class TreeNode:\n \"\"\"AVL 木のノードクラス\"\"\"\n def __init__(self, val: int):\n self.val: int = val # ノードの値\n self.height: int = 0 # ノードの高さ\n self.left: TreeNode | None = None # 左の子への参照\n self.right: TreeNode | None = None # 右の子への参照\n</code></pre> <pre><code>/* AVL 木のノードクラス */\nstruct TreeNode {\n int val{}; // ノードの値\n int height = 0; // ノードの高さ\n TreeNode *left{}; // 左の子\n TreeNode *right{}; // 右の子\n TreeNode() = default;\n explicit TreeNode(int x) : val(x){}\n};\n</code></pre> <pre><code>/* AVL 木のノードクラス */\nclass TreeNode {\n public int val; // ノードの値\n public int height; // ノードの高さ\n public TreeNode left; // 左の子\n public TreeNode right; // 右の子\n public TreeNode(int x) { val = x; }\n}\n</code></pre> <pre><code>/* AVL 木のノードクラス */\nclass TreeNode(int? x) {\n public int? val = x; // ノードの値\n public int height; // ノードの高さ\n public TreeNode? left; // 左の子への参照\n public TreeNode? right; // 右の子への参照\n}\n</code></pre> <pre><code>/* AVL 木のノード構造体 */\ntype TreeNode struct {\n Val int // ノードの値\n Height int // ノードの高さ\n Left *TreeNode // 左の子への参照\n Right *TreeNode // 右の子への参照\n}\n</code></pre> <pre><code>/* AVL 木のノードクラス */\nclass TreeNode {\n var val: Int // ノードの値\n var height: Int // ノードの高さ\n var left: TreeNode? // 左の子\n var right: TreeNode? // 右の子\n\n init(x: Int) {\n val = x\n height = 0\n }\n}\n</code></pre> <pre><code>/* AVL 木のノードクラス */\nclass TreeNode {\n val; // ノードの値\n height; //ノードの高さ\n left; // 左の子へのポインタ\n right; // 右の子へのポインタ\n constructor(val, left, right, height) {\n this.val = val === undefined ? 0 : val;\n this.height = height === undefined ? 0 : height;\n this.left = left === undefined ? null : left;\n this.right = right === undefined ? null : right;\n }\n}\n</code></pre> <pre><code>/* AVL 木のノードクラス */\nclass TreeNode {\n val: number; // ノードの値\n height: number; // ノードの高さ\n left: TreeNode | null; // 左の子へのポインタ\n right: TreeNode | null; // 右の子へのポインタ\n constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) {\n this.val = val === undefined ? 0 : val;\n this.height = height === undefined ? 0 : height;\n this.left = left === undefined ? null : left;\n this.right = right === undefined ? null : right;\n }\n}\n</code></pre> <pre><code>/* AVL 木のノードクラス */\nclass TreeNode {\n int val; // ノードの値\n int height; // ノードの高さ\n TreeNode? left; // 左の子\n TreeNode? right; // 右の子\n TreeNode(this.val, [this.height = 0, this.left, this.right]);\n}\n</code></pre> <pre><code>use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* AVL 木のノード構造体 */\nstruct TreeNode {\n val: i32, // ノードの値\n height: i32, // ノードの高さ\n left: Option&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;, // 左の子\n right: Option&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;, // 右の子\n}\n\nimpl TreeNode {\n /* コンストラクタ */\n fn new(val: i32) -&gt; Rc&lt;RefCell&lt;Self&gt;&gt; {\n Rc::new(RefCell::new(Self {\n val,\n height: 0,\n left: None,\n right: None\n }))\n }\n}\n</code></pre> <pre><code>/* AVL 木のノード構造体 */\ntypedef struct TreeNode {\n int val;\n int height;\n struct TreeNode *left;\n struct TreeNode *right;\n} TreeNode;\n\n/* コンストラクタ */\nTreeNode *newTreeNode(int val) {\n TreeNode *node;\n\n node = (TreeNode *)malloc(sizeof(TreeNode));\n node-&gt;val = val;\n node-&gt;height = 0;\n node-&gt;left = NULL;\n node-&gt;right = NULL;\n return node;\n}\n</code></pre> <pre><code>/* AVL 木のノードクラス */\nclass TreeNode(val _val: Int) { // ノードの値\n var height: Int = 0 // ノードの高さ\n var left: TreeNode? = null // 左の子\n var right: TreeNode? = null // 右の子\n}\n</code></pre> <pre><code>### AVL 木のノードクラス ###\nclass TreeNode\n attr_accessor :val # ノードの値\n attr_accessor :height # ノードの高さ\n attr_accessor :left # 左の子への参照\n attr_accessor :right # 右の子への参照\n\n def initialize(val)\n @val = val\n @height = 0\n end\nend\n</code></pre> <p>「ノードの高さ」は、そのノードから最も遠い葉までの距離、つまり通る辺の数です。葉の高さは \\(0\\)、空のノードの高さは \\(-1\\) であることに、特に注意してください。高さの取得と更新を行う二つの補助関数を作ります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py<pre><code>def height(self, node: TreeNode | None) -&gt; int:\n \"\"\"ノードの高さを取得する\"\"\"\n # 空ノードの高さは -1 、葉ノードの高さは 0\n if node is not None:\n return node.height\n return -1\n\ndef update_height(self, node: TreeNode | None):\n \"\"\"ノードの高さを更新する\"\"\"\n # ノードの高さは、最も高い部分木の高さ + 1\n node.height = max([self.height(node.left), self.height(node.right)]) + 1\n</code></pre> avl_tree.cpp<pre><code>/* ノードの高さを取得する */\nint height(TreeNode *node) {\n // 空ノードの高さは -1 、葉ノードの高さは 0\n return node == nullptr ? -1 : node-&gt;height;\n}\n\n/* ノードの高さを更新する */\nvoid updateHeight(TreeNode *node) {\n // ノードの高さは、最も高い部分木の高さ + 1\n node-&gt;height = max(height(node-&gt;left), height(node-&gt;right)) + 1;\n}\n</code></pre> avl_tree.java<pre><code>/* ノードの高さを取得する */\nint height(TreeNode node) {\n // 空ノードの高さは -1 、葉ノードの高さは 0\n return node == null ? -1 : node.height;\n}\n\n/* ノードの高さを更新する */\nvoid updateHeight(TreeNode node) {\n // ノードの高さは、最も高い部分木の高さ + 1\n node.height = Math.max(height(node.left), height(node.right)) + 1;\n}\n</code></pre> avl_tree.cs<pre><code>/* ノードの高さを取得する */\nint Height(TreeNode? node) {\n // 空ノードの高さは -1 、葉ノードの高さは 0\n return node == null ? -1 : node.height;\n}\n\n/* ノードの高さを更新する */\nvoid UpdateHeight(TreeNode node) {\n // ノードの高さは、最も高い部分木の高さ + 1\n node.height = Math.Max(Height(node.left), Height(node.right)) + 1;\n}\n</code></pre> avl_tree.go<pre><code>/* ノードの高さを取得する */\nfunc (t *aVLTree) height(node *TreeNode) int {\n // 空ノードの高さは -1 、葉ノードの高さは 0\n if node != nil {\n return node.Height\n }\n return -1\n}\n\n/* ノードの高さを更新する */\nfunc (t *aVLTree) updateHeight(node *TreeNode) {\n lh := t.height(node.Left)\n rh := t.height(node.Right)\n // ノードの高さは、最も高い部分木の高さ + 1\n if lh &gt; rh {\n node.Height = lh + 1\n } else {\n node.Height = rh + 1\n }\n}\n</code></pre> avl_tree.swift<pre><code>/* ノードの高さを取得する */\nfunc height(node: TreeNode?) -&gt; Int {\n // 空ノードの高さは -1 、葉ノードの高さは 0\n node?.height ?? -1\n}\n\n/* ノードの高さを更新する */\nfunc updateHeight(node: TreeNode?) {\n // ノードの高さは、最も高い部分木の高さ + 1\n node?.height = max(height(node: node?.left), height(node: node?.right)) + 1\n}\n</code></pre> avl_tree.js<pre><code>/* ノードの高さを取得する */\nheight(node) {\n // 空ノードの高さは -1 、葉ノードの高さは 0\n return node === null ? -1 : node.height;\n}\n\n/* ノードの高さを更新する */\n#updateHeight(node) {\n // ノードの高さは、最も高い部分木の高さ + 1\n node.height =\n Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n</code></pre> avl_tree.ts<pre><code>/* ノードの高さを取得する */\nheight(node: TreeNode): number {\n // 空ノードの高さは -1 、葉ノードの高さは 0\n return node === null ? -1 : node.height;\n}\n\n/* ノードの高さを更新する */\nupdateHeight(node: TreeNode): void {\n // ノードの高さは、最も高い部分木の高さ + 1\n node.height =\n Math.max(this.height(node.left), this.height(node.right)) + 1;\n}\n</code></pre> avl_tree.dart<pre><code>/* ノードの高さを取得する */\nint height(TreeNode? node) {\n // 空ノードの高さは -1 、葉ノードの高さは 0\n return node == null ? -1 : node.height;\n}\n\n/* ノードの高さを更新する */\nvoid updateHeight(TreeNode? node) {\n // ノードの高さは、最も高い部分木の高さ + 1\n node!.height = max(height(node.left), height(node.right)) + 1;\n}\n</code></pre> avl_tree.rs<pre><code>/* ノードの高さを取得する */\nfn height(node: OptionTreeNodeRc) -&gt; i32 {\n // 空ノードの高さは -1 、葉ノードの高さは 0\n match node {\n Some(node) =&gt; node.borrow().height,\n None =&gt; -1,\n }\n}\n\n/* ノードの高さを更新する */\nfn update_height(node: OptionTreeNodeRc) {\n if let Some(node) = node {\n let left = node.borrow().left.clone();\n let right = node.borrow().right.clone();\n // ノードの高さは、最も高い部分木の高さ + 1\n node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1;\n }\n}\n</code></pre> avl_tree.c<pre><code>/* ノードの高さを取得する */\nint height(TreeNode *node) {\n // 空ノードの高さは -1 、葉ノードの高さは 0\n if (node != NULL) {\n return node-&gt;height;\n }\n return -1;\n}\n\n/* ノードの高さを更新する */\nvoid updateHeight(TreeNode *node) {\n int lh = height(node-&gt;left);\n int rh = height(node-&gt;right);\n // ノードの高さは、最も高い部分木の高さ + 1\n if (lh &gt; rh) {\n node-&gt;height = lh + 1;\n } else {\n node-&gt;height = rh + 1;\n }\n}\n</code></pre> avl_tree.kt<pre><code>/* ノードの高さを取得する */\nfun height(node: TreeNode?): Int {\n // 空ノードの高さは -1 、葉ノードの高さは 0\n return node?.height ?: -1\n}\n\n/* ノードの高さを更新する */\nfun updateHeight(node: TreeNode?) {\n // ノードの高さは、最も高い部分木の高さ + 1\n node?.height = max(height(node?.left), height(node?.right)) + 1\n}\n</code></pre> avl_tree.rb<pre><code>### ノードの高さを取得する ###\ndef height(node)\n # 空ノードの高さは -1 、葉ノードの高さは 0\n return node.height unless node.nil?\n\n -1\nend\n\n### ノードの高さを更新する ###\ndef update_height(node)\n # ノードの高さは、最も高い部分木の高さ + 1\n node.height = [height(node.left), height(node.right)].max + 1\nend\n</code></pre>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2","level":3,"title":"2.   ノードの平衡係数","text":"<p>平衡係数(balance factor)は、左の部分木の高さから右の部分木の高さを引いた値です。空のノードの平衡係数は \\(0\\) とします。取得処理も関数にまとめておくと、後で使いやすくなります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py<pre><code>def balance_factor(self, node: TreeNode | None) -&gt; int:\n \"\"\"平衡係数を取得する\"\"\"\n # 空ノードの平衡係数は 0\n if node is None:\n return 0\n # ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n return self.height(node.left) - self.height(node.right)\n</code></pre> avl_tree.cpp<pre><code>/* 平衡係数を取得する */\nint balanceFactor(TreeNode *node) {\n // 空ノードの平衡係数は 0\n if (node == nullptr)\n return 0;\n // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n return height(node-&gt;left) - height(node-&gt;right);\n}\n</code></pre> avl_tree.java<pre><code>/* 平衡係数を取得する */\nint balanceFactor(TreeNode node) {\n // 空ノードの平衡係数は 0\n if (node == null)\n return 0;\n // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n return height(node.left) - height(node.right);\n}\n</code></pre> avl_tree.cs<pre><code>/* 平衡係数を取得する */\nint BalanceFactor(TreeNode? node) {\n // 空ノードの平衡係数は 0\n if (node == null) return 0;\n // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n return Height(node.left) - Height(node.right);\n}\n</code></pre> avl_tree.go<pre><code>/* 平衡係数を取得する */\nfunc (t *aVLTree) balanceFactor(node *TreeNode) int {\n // 空ノードの平衡係数は 0\n if node == nil {\n return 0\n }\n // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n return t.height(node.Left) - t.height(node.Right)\n}\n</code></pre> avl_tree.swift<pre><code>/* 平衡係数を取得する */\nfunc balanceFactor(node: TreeNode?) -&gt; Int {\n // 空ノードの平衡係数は 0\n guard let node = node else { return 0 }\n // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n return height(node: node.left) - height(node: node.right)\n}\n</code></pre> avl_tree.js<pre><code>/* 平衡係数を取得する */\nbalanceFactor(node) {\n // 空ノードの平衡係数は 0\n if (node === null) return 0;\n // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n return this.height(node.left) - this.height(node.right);\n}\n</code></pre> avl_tree.ts<pre><code>/* 平衡係数を取得する */\nbalanceFactor(node: TreeNode): number {\n // 空ノードの平衡係数は 0\n if (node === null) return 0;\n // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n return this.height(node.left) - this.height(node.right);\n}\n</code></pre> avl_tree.dart<pre><code>/* 平衡係数を取得する */\nint balanceFactor(TreeNode? node) {\n // 空ノードの平衡係数は 0\n if (node == null) return 0;\n // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n return height(node.left) - height(node.right);\n}\n</code></pre> avl_tree.rs<pre><code>/* 平衡係数を取得する */\nfn balance_factor(node: OptionTreeNodeRc) -&gt; i32 {\n match node {\n // 空ノードの平衡係数は 0\n None =&gt; 0,\n // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n Some(node) =&gt; {\n Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone())\n }\n }\n}\n</code></pre> avl_tree.c<pre><code>/* 平衡係数を取得する */\nint balanceFactor(TreeNode *node) {\n // 空ノードの平衡係数は 0\n if (node == NULL) {\n return 0;\n }\n // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n return height(node-&gt;left) - height(node-&gt;right);\n}\n</code></pre> avl_tree.kt<pre><code>/* 平衡係数を取得する */\nfun balanceFactor(node: TreeNode?): Int {\n // 空ノードの平衡係数は 0\n if (node == null) return 0\n // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n return height(node.left) - height(node.right)\n}\n</code></pre> avl_tree.rb<pre><code>### 平衡係数を取得する ###\ndef balance_factor(node)\n # 空ノードの平衡係数は 0\n return 0 if node.nil?\n\n # ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ\n height(node.left) - height(node.right)\nend\n</code></pre> <p>ヒント</p> <p>平衡係数を \\(f\\) とすると、AVL 木のすべてのノードで \\(-1 \\le f \\le 1\\) が成り立ちます。</p>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#752-avl","level":2,"title":"7.5.2   AVL 木の回転","text":"<p>AVL 木の特徴は「回転」です。通りがけ順の列を変えずに、不均衡なノードの平衡を取り戻せます。つまり、回転は二分探索木の性質を保ちつつ、木を再び平衡二分木にできます。</p> <p>平衡係数の絶対値が \\(&gt; 1\\) のノードを「不均衡なノード」と呼びます。状態に応じて、右回転、左回転、右回転してから左回転、左回転してから右回転の四種類を使います。順に詳しく説明します。</p>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1_1","level":3,"title":"1.   右回転","text":"<p>図 7-26 では、ノードの下の数値が平衡係数です。下から上へ見ると、最初の不均衡なノードは「ノード 3」です。そこを根とする部分木に注目し、そのノードを <code>node</code>、左の子を <code>child</code> として右回転します。すると、二分探索木の性質を保ったまま、部分木が平衡に戻ります。</p> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt; <p></p> <p></p> <p></p> <p></p> <p> 図 7-26   右回転の手順 </p> <p>図 7-27 のように、<code>child</code> に右の子 <code>grand_child</code> がある場合は、それを <code>node</code> の左の子にする手順を追加します。</p> <p></p> <p> 図 7-27   grand_child がある場合の右回転 </p> <p>「右へ回転する」は見た目の表現であり、実際にはノードのポインタを変更して実現します。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py<pre><code>def right_rotate(self, node: TreeNode | None) -&gt; TreeNode | None:\n \"\"\"右回転操作\"\"\"\n child = node.left\n grand_child = child.right\n # child を中心に node を右回転する\n child.right = node\n node.left = grand_child\n # ノードの高さを更新する\n self.update_height(node)\n self.update_height(child)\n # 回転後の部分木の根ノードを返す\n return child\n</code></pre> avl_tree.cpp<pre><code>/* 右回転操作 */\nTreeNode *rightRotate(TreeNode *node) {\n TreeNode *child = node-&gt;left;\n TreeNode *grandChild = child-&gt;right;\n // child を中心に node を右回転する\n child-&gt;right = node;\n node-&gt;left = grandChild;\n // ノードの高さを更新する\n updateHeight(node);\n updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.java<pre><code>/* 右回転操作 */\nTreeNode rightRotate(TreeNode node) {\n TreeNode child = node.left;\n TreeNode grandChild = child.right;\n // child を中心に node を右回転する\n child.right = node;\n node.left = grandChild;\n // ノードの高さを更新する\n updateHeight(node);\n updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.cs<pre><code>/* 右回転操作 */\nTreeNode? RightRotate(TreeNode? node) {\n TreeNode? child = node?.left;\n TreeNode? grandChild = child?.right;\n // child を中心に node を右回転する\n child.right = node;\n node.left = grandChild;\n // ノードの高さを更新する\n UpdateHeight(node);\n UpdateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.go<pre><code>/* 右回転操作 */\nfunc (t *aVLTree) rightRotate(node *TreeNode) *TreeNode {\n child := node.Left\n grandChild := child.Right\n // child を中心に node を右回転する\n child.Right = node\n node.Left = grandChild\n // ノードの高さを更新する\n t.updateHeight(node)\n t.updateHeight(child)\n // 回転後の部分木の根ノードを返す\n return child\n}\n</code></pre> avl_tree.swift<pre><code>/* 右回転操作 */\nfunc rightRotate(node: TreeNode?) -&gt; TreeNode? {\n let child = node?.left\n let grandChild = child?.right\n // child を中心に node を右回転する\n child?.right = node\n node?.left = grandChild\n // ノードの高さを更新する\n updateHeight(node: node)\n updateHeight(node: child)\n // 回転後の部分木の根ノードを返す\n return child\n}\n</code></pre> avl_tree.js<pre><code>/* 右回転操作 */\n#rightRotate(node) {\n const child = node.left;\n const grandChild = child.right;\n // child を中心に node を右回転する\n child.right = node;\n node.left = grandChild;\n // ノードの高さを更新する\n this.#updateHeight(node);\n this.#updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.ts<pre><code>/* 右回転操作 */\nrightRotate(node: TreeNode): TreeNode {\n const child = node.left;\n const grandChild = child.right;\n // child を中心に node を右回転する\n child.right = node;\n node.left = grandChild;\n // ノードの高さを更新する\n this.updateHeight(node);\n this.updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.dart<pre><code>/* 右回転操作 */\nTreeNode? rightRotate(TreeNode? node) {\n TreeNode? child = node!.left;\n TreeNode? grandChild = child!.right;\n // child を中心に node を右回転する\n child.right = node;\n node.left = grandChild;\n // ノードの高さを更新する\n updateHeight(node);\n updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.rs<pre><code>/* 右回転操作 */\nfn right_rotate(node: OptionTreeNodeRc) -&gt; OptionTreeNodeRc {\n match node {\n Some(node) =&gt; {\n let child = node.borrow().left.clone().unwrap();\n let grand_child = child.borrow().right.clone();\n // child を中心に node を右回転する\n child.borrow_mut().right = Some(node.clone());\n node.borrow_mut().left = grand_child;\n // ノードの高さを更新する\n Self::update_height(Some(node));\n Self::update_height(Some(child.clone()));\n // 回転後の部分木の根ノードを返す\n Some(child)\n }\n None =&gt; None,\n }\n}\n</code></pre> avl_tree.c<pre><code>/* 右回転操作 */\nTreeNode *rightRotate(TreeNode *node) {\n TreeNode *child, *grandChild;\n child = node-&gt;left;\n grandChild = child-&gt;right;\n // child を中心に node を右回転する\n child-&gt;right = node;\n node-&gt;left = grandChild;\n // ノードの高さを更新する\n updateHeight(node);\n updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.kt<pre><code>/* 右回転操作 */\nfun rightRotate(node: TreeNode?): TreeNode {\n val child = node!!.left\n val grandChild = child!!.right\n // child を中心に node を右回転する\n child.right = node\n node.left = grandChild\n // ノードの高さを更新する\n updateHeight(node)\n updateHeight(child)\n // 回転後の部分木の根ノードを返す\n return child\n}\n</code></pre> avl_tree.rb<pre><code>### 右回転操作 ###\ndef right_rotate(node)\n child = node.left\n grand_child = child.right\n # child を中心に node を右回転する\n child.right = node\n node.left = grand_child\n # ノードの高さを更新する\n update_height(node)\n update_height(child)\n # 回転後の部分木の根ノードを返す\n child\nend\n</code></pre>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2_1","level":3,"title":"2.   左回転","text":"<p>これに対し、先ほどの不均衡な木を鏡に映した形では、図 7-28 の左回転が必要です。</p> <p></p> <p> 図 7-28   左回転 </p> <p>同様に、図 7-29 のように <code>child</code> に左の子 <code>grand_child</code> がある場合は、それを <code>node</code> の右の子にする手順を追加します。</p> <p></p> <p> 図 7-29   grand_child がある場合の左回転 </p> <p>右回転と左回転は論理的に鏡映対称で、対処する不均衡の形も対称です。この対称性から、右回転のコードの <code>left</code> をすべて <code>right</code> に、<code>right</code> をすべて <code>left</code> に置き換えると、左回転のコードになります。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py<pre><code>def left_rotate(self, node: TreeNode | None) -&gt; TreeNode | None:\n \"\"\"左回転操作\"\"\"\n child = node.right\n grand_child = child.left\n # child を中心に node を左回転する\n child.left = node\n node.right = grand_child\n # ノードの高さを更新する\n self.update_height(node)\n self.update_height(child)\n # 回転後の部分木の根ノードを返す\n return child\n</code></pre> avl_tree.cpp<pre><code>/* 左回転操作 */\nTreeNode *leftRotate(TreeNode *node) {\n TreeNode *child = node-&gt;right;\n TreeNode *grandChild = child-&gt;left;\n // child を中心に node を左回転する\n child-&gt;left = node;\n node-&gt;right = grandChild;\n // ノードの高さを更新する\n updateHeight(node);\n updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.java<pre><code>/* 左回転操作 */\nTreeNode leftRotate(TreeNode node) {\n TreeNode child = node.right;\n TreeNode grandChild = child.left;\n // child を中心に node を左回転する\n child.left = node;\n node.right = grandChild;\n // ノードの高さを更新する\n updateHeight(node);\n updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.cs<pre><code>/* 左回転操作 */\nTreeNode? LeftRotate(TreeNode? node) {\n TreeNode? child = node?.right;\n TreeNode? grandChild = child?.left;\n // child を中心に node を左回転する\n child.left = node;\n node.right = grandChild;\n // ノードの高さを更新する\n UpdateHeight(node);\n UpdateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.go<pre><code>/* 左回転操作 */\nfunc (t *aVLTree) leftRotate(node *TreeNode) *TreeNode {\n child := node.Right\n grandChild := child.Left\n // child を中心に node を左回転する\n child.Left = node\n node.Right = grandChild\n // ノードの高さを更新する\n t.updateHeight(node)\n t.updateHeight(child)\n // 回転後の部分木の根ノードを返す\n return child\n}\n</code></pre> avl_tree.swift<pre><code>/* 左回転操作 */\nfunc leftRotate(node: TreeNode?) -&gt; TreeNode? {\n let child = node?.right\n let grandChild = child?.left\n // child を中心に node を左回転する\n child?.left = node\n node?.right = grandChild\n // ノードの高さを更新する\n updateHeight(node: node)\n updateHeight(node: child)\n // 回転後の部分木の根ノードを返す\n return child\n}\n</code></pre> avl_tree.js<pre><code>/* 左回転操作 */\n#leftRotate(node) {\n const child = node.right;\n const grandChild = child.left;\n // child を中心に node を左回転する\n child.left = node;\n node.right = grandChild;\n // ノードの高さを更新する\n this.#updateHeight(node);\n this.#updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.ts<pre><code>/* 左回転操作 */\nleftRotate(node: TreeNode): TreeNode {\n const child = node.right;\n const grandChild = child.left;\n // child を中心に node を左回転する\n child.left = node;\n node.right = grandChild;\n // ノードの高さを更新する\n this.updateHeight(node);\n this.updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.dart<pre><code>/* 左回転操作 */\nTreeNode? leftRotate(TreeNode? node) {\n TreeNode? child = node!.right;\n TreeNode? grandChild = child!.left;\n // child を中心に node を左回転する\n child.left = node;\n node.right = grandChild;\n // ノードの高さを更新する\n updateHeight(node);\n updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.rs<pre><code>/* 左回転操作 */\nfn left_rotate(node: OptionTreeNodeRc) -&gt; OptionTreeNodeRc {\n match node {\n Some(node) =&gt; {\n let child = node.borrow().right.clone().unwrap();\n let grand_child = child.borrow().left.clone();\n // child を中心に node を左回転する\n child.borrow_mut().left = Some(node.clone());\n node.borrow_mut().right = grand_child;\n // ノードの高さを更新する\n Self::update_height(Some(node));\n Self::update_height(Some(child.clone()));\n // 回転後の部分木の根ノードを返す\n Some(child)\n }\n None =&gt; None,\n }\n}\n</code></pre> avl_tree.c<pre><code>/* 左回転操作 */\nTreeNode *leftRotate(TreeNode *node) {\n TreeNode *child, *grandChild;\n child = node-&gt;right;\n grandChild = child-&gt;left;\n // child を中心に node を左回転する\n child-&gt;left = node;\n node-&gt;right = grandChild;\n // ノードの高さを更新する\n updateHeight(node);\n updateHeight(child);\n // 回転後の部分木の根ノードを返す\n return child;\n}\n</code></pre> avl_tree.kt<pre><code>/* 左回転操作 */\nfun leftRotate(node: TreeNode?): TreeNode {\n val child = node!!.right\n val grandChild = child!!.left\n // child を中心に node を左回転する\n child.left = node\n node.right = grandChild\n // ノードの高さを更新する\n updateHeight(node)\n updateHeight(child)\n // 回転後の部分木の根ノードを返す\n return child\n}\n</code></pre> avl_tree.rb<pre><code>### 左回転操作 ###\ndef left_rotate(node)\n child = node.right\n grand_child = child.left\n # child を中心に node を左回転する\n child.left = node\n node.right = grand_child\n # ノードの高さを更新する\n update_height(node)\n update_height(child)\n # 回転後の部分木の根ノードを返す\n child\nend\n</code></pre>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#3","level":3,"title":"3.   左回転してから右回転","text":"<p>図 7-30 の不均衡なノード 3 は、左回転か右回転の一方だけでは平衡に戻りません。まず <code>child</code> を左回転し、次に <code>node</code> を右回転する必要があります。</p> <p></p> <p> 図 7-30   左回転してから右回転 </p>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#4","level":3,"title":"4.   右回転してから左回転","text":"<p>図 7-31 のように、先ほどの木を鏡に映した形では、まず <code>child</code> を右回転し、次に <code>node</code> を左回転します。</p> <p></p> <p> 図 7-31   右回転してから左回転 </p>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#5","level":3,"title":"5.   回転の選択","text":"<p>図 7-32 の四つの不均衡は、これまでの例にそれぞれ対応します。必要な操作は順に、右回転、左回転してから右回転、右回転してから左回転、左回転です。</p> <p></p> <p> 図 7-32   AVL 木の四つの回転の場合 </p> <p>下表のように、不均衡なノードの平衡係数と、高い側の子の平衡係数の符号から、図 7-32 のどの場合かを判定します。</p> <p> 表 7-3   四つの回転の選択条件 </p> 不均衡なノードの平衡係数 子の平衡係数 選ぶ回転方法 \\(&gt; 1\\) (左に偏る木) \\(\\geq 0\\) 右回転 \\(&gt; 1\\) (左に偏る木) \\(&lt;0\\) 左回転してから右回転 \\(&lt; -1\\) (右に偏る木) \\(\\leq 0\\) 左回転 \\(&lt; -1\\) (右に偏る木) \\(&gt;0\\) 右回転してから左回転 <p>使いやすいように、回転処理を一つの関数にまとめます。この関数でさまざまな不均衡に応じた回転を行い、平衡を取り戻せます。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py<pre><code>def rotate(self, node: TreeNode | None) -&gt; TreeNode | None:\n \"\"\"回転操作によって、この部分木の平衡を回復する\"\"\"\n # ノード node の平衡係数を取得する\n balance_factor = self.balance_factor(node)\n # 左に偏った木\n if balance_factor &gt; 1:\n if self.balance_factor(node.left) &gt;= 0:\n # 右回転\n return self.right_rotate(node)\n else:\n # 左回転してから右回転する\n node.left = self.left_rotate(node.left)\n return self.right_rotate(node)\n # 右に偏った木\n elif balance_factor &lt; -1:\n if self.balance_factor(node.right) &lt;= 0:\n # 左回転\n return self.left_rotate(node)\n else:\n # 右回転してから左回転する\n node.right = self.right_rotate(node.right)\n return self.left_rotate(node)\n # 平衡な木なので、回転せずにそのまま返す\n return node\n</code></pre> avl_tree.cpp<pre><code>/* 回転操作によって、この部分木の平衡を回復する */\nTreeNode *rotate(TreeNode *node) {\n // ノード node の平衡係数を取得する\n int _balanceFactor = balanceFactor(node);\n // 左に偏った木\n if (_balanceFactor &gt; 1) {\n if (balanceFactor(node-&gt;left) &gt;= 0) {\n // 右回転\n return rightRotate(node);\n } else {\n // 左回転してから右回転する\n node-&gt;left = leftRotate(node-&gt;left);\n return rightRotate(node);\n }\n }\n // 右に偏った木\n if (_balanceFactor &lt; -1) {\n if (balanceFactor(node-&gt;right) &lt;= 0) {\n // 左回転\n return leftRotate(node);\n } else {\n // 右回転してから左回転する\n node-&gt;right = rightRotate(node-&gt;right);\n return leftRotate(node);\n }\n }\n // 平衡な木なので、回転せずにそのまま返す\n return node;\n}\n</code></pre> avl_tree.java<pre><code>/* 回転操作によって、この部分木の平衡を回復する */\nTreeNode rotate(TreeNode node) {\n // ノード node の平衡係数を取得する\n int balanceFactor = balanceFactor(node);\n // 左に偏った木\n if (balanceFactor &gt; 1) {\n if (balanceFactor(node.left) &gt;= 0) {\n // 右回転\n return rightRotate(node);\n } else {\n // 左回転してから右回転する\n node.left = leftRotate(node.left);\n return rightRotate(node);\n }\n }\n // 右に偏った木\n if (balanceFactor &lt; -1) {\n if (balanceFactor(node.right) &lt;= 0) {\n // 左回転\n return leftRotate(node);\n } else {\n // 右回転してから左回転する\n node.right = rightRotate(node.right);\n return leftRotate(node);\n }\n }\n // 平衡な木なので、回転せずにそのまま返す\n return node;\n}\n</code></pre> avl_tree.cs<pre><code>/* 回転操作によって、この部分木の平衡を回復する */\nTreeNode? Rotate(TreeNode? node) {\n // ノード node の平衡係数を取得する\n int balanceFactorInt = BalanceFactor(node);\n // 左に偏った木\n if (balanceFactorInt &gt; 1) {\n if (BalanceFactor(node?.left) &gt;= 0) {\n // 右回転\n return RightRotate(node);\n } else {\n // 左回転してから右回転する\n node!.left = LeftRotate(node!.left);\n return RightRotate(node);\n }\n }\n // 右に偏った木\n if (balanceFactorInt &lt; -1) {\n if (BalanceFactor(node?.right) &lt;= 0) {\n // 左回転\n return LeftRotate(node);\n } else {\n // 右回転してから左回転する\n node!.right = RightRotate(node!.right);\n return LeftRotate(node);\n }\n }\n // 平衡な木なので、回転せずにそのまま返す\n return node;\n}\n</code></pre> avl_tree.go<pre><code>/* 回転操作によって、この部分木の平衡を回復する */\nfunc (t *aVLTree) rotate(node *TreeNode) *TreeNode {\n // ノード node の平衡係数を取得する\n // Go では短い変数名が推奨されるため、ここでは bf で t.balanceFactor を表す\n bf := t.balanceFactor(node)\n // 左に偏った木\n if bf &gt; 1 {\n if t.balanceFactor(node.Left) &gt;= 0 {\n // 右回転\n return t.rightRotate(node)\n } else {\n // 左回転してから右回転する\n node.Left = t.leftRotate(node.Left)\n return t.rightRotate(node)\n }\n }\n // 右に偏った木\n if bf &lt; -1 {\n if t.balanceFactor(node.Right) &lt;= 0 {\n // 左回転\n return t.leftRotate(node)\n } else {\n // 右回転してから左回転する\n node.Right = t.rightRotate(node.Right)\n return t.leftRotate(node)\n }\n }\n // 平衡な木なので、回転せずにそのまま返す\n return node\n}\n</code></pre> avl_tree.swift<pre><code>/* 回転操作によって、この部分木の平衡を回復する */\nfunc rotate(node: TreeNode?) -&gt; TreeNode? {\n // ノード node の平衡係数を取得する\n let balanceFactor = balanceFactor(node: node)\n // 左に偏った木\n if balanceFactor &gt; 1 {\n if self.balanceFactor(node: node?.left) &gt;= 0 {\n // 右回転\n return rightRotate(node: node)\n } else {\n // 左回転してから右回転する\n node?.left = leftRotate(node: node?.left)\n return rightRotate(node: node)\n }\n }\n // 右に偏った木\n if balanceFactor &lt; -1 {\n if self.balanceFactor(node: node?.right) &lt;= 0 {\n // 左回転\n return leftRotate(node: node)\n } else {\n // 右回転してから左回転する\n node?.right = rightRotate(node: node?.right)\n return leftRotate(node: node)\n }\n }\n // 平衡な木なので、回転せずにそのまま返す\n return node\n}\n</code></pre> avl_tree.js<pre><code>/* 回転操作によって、この部分木の平衡を回復する */\n#rotate(node) {\n // ノード node の平衡係数を取得する\n const balanceFactor = this.balanceFactor(node);\n // 左に偏った木\n if (balanceFactor &gt; 1) {\n if (this.balanceFactor(node.left) &gt;= 0) {\n // 右回転\n return this.#rightRotate(node);\n } else {\n // 左回転してから右回転する\n node.left = this.#leftRotate(node.left);\n return this.#rightRotate(node);\n }\n }\n // 右に偏った木\n if (balanceFactor &lt; -1) {\n if (this.balanceFactor(node.right) &lt;= 0) {\n // 左回転\n return this.#leftRotate(node);\n } else {\n // 右回転してから左回転する\n node.right = this.#rightRotate(node.right);\n return this.#leftRotate(node);\n }\n }\n // 平衡な木なので、回転せずにそのまま返す\n return node;\n}\n</code></pre> avl_tree.ts<pre><code>/* 回転操作によって、この部分木の平衡を回復する */\nrotate(node: TreeNode): TreeNode {\n // ノード node の平衡係数を取得する\n const balanceFactor = this.balanceFactor(node);\n // 左に偏った木\n if (balanceFactor &gt; 1) {\n if (this.balanceFactor(node.left) &gt;= 0) {\n // 右回転\n return this.rightRotate(node);\n } else {\n // 左回転してから右回転する\n node.left = this.leftRotate(node.left);\n return this.rightRotate(node);\n }\n }\n // 右に偏った木\n if (balanceFactor &lt; -1) {\n if (this.balanceFactor(node.right) &lt;= 0) {\n // 左回転\n return this.leftRotate(node);\n } else {\n // 右回転してから左回転する\n node.right = this.rightRotate(node.right);\n return this.leftRotate(node);\n }\n }\n // 平衡な木なので、回転せずにそのまま返す\n return node;\n}\n</code></pre> avl_tree.dart<pre><code>/* 回転操作によって、この部分木の平衡を回復する */\nTreeNode? rotate(TreeNode? node) {\n // ノード node の平衡係数を取得する\n int factor = balanceFactor(node);\n // 左に偏った木\n if (factor &gt; 1) {\n if (balanceFactor(node!.left) &gt;= 0) {\n // 右回転\n return rightRotate(node);\n } else {\n // 左回転してから右回転する\n node.left = leftRotate(node.left);\n return rightRotate(node);\n }\n }\n // 右に偏った木\n if (factor &lt; -1) {\n if (balanceFactor(node!.right) &lt;= 0) {\n // 左回転\n return leftRotate(node);\n } else {\n // 右回転してから左回転する\n node.right = rightRotate(node.right);\n return leftRotate(node);\n }\n }\n // 平衡な木なので、回転せずにそのまま返す\n return node;\n}\n</code></pre> avl_tree.rs<pre><code>/* 回転操作によって、この部分木の平衡を回復する */\nfn rotate(node: OptionTreeNodeRc) -&gt; OptionTreeNodeRc {\n // ノード node の平衡係数を取得する\n let balance_factor = Self::balance_factor(node.clone());\n // 左に偏った木\n if balance_factor &gt; 1 {\n let node = node.unwrap();\n if Self::balance_factor(node.borrow().left.clone()) &gt;= 0 {\n // 右回転\n Self::right_rotate(Some(node))\n } else {\n // 左回転してから右回転する\n let left = node.borrow().left.clone();\n node.borrow_mut().left = Self::left_rotate(left);\n Self::right_rotate(Some(node))\n }\n }\n // 右に偏った木\n else if balance_factor &lt; -1 {\n let node = node.unwrap();\n if Self::balance_factor(node.borrow().right.clone()) &lt;= 0 {\n // 左回転\n Self::left_rotate(Some(node))\n } else {\n // 右回転してから左回転する\n let right = node.borrow().right.clone();\n node.borrow_mut().right = Self::right_rotate(right);\n Self::left_rotate(Some(node))\n }\n } else {\n // 平衡な木なので、回転せずにそのまま返す\n node\n }\n}\n</code></pre> avl_tree.c<pre><code>/* 回転操作によって、この部分木の平衡を回復する */\nTreeNode *rotate(TreeNode *node) {\n // ノード node の平衡係数を取得する\n int bf = balanceFactor(node);\n // 左に偏った木\n if (bf &gt; 1) {\n if (balanceFactor(node-&gt;left) &gt;= 0) {\n // 右回転\n return rightRotate(node);\n } else {\n // 左回転してから右回転する\n node-&gt;left = leftRotate(node-&gt;left);\n return rightRotate(node);\n }\n }\n // 右に偏った木\n if (bf &lt; -1) {\n if (balanceFactor(node-&gt;right) &lt;= 0) {\n // 左回転\n return leftRotate(node);\n } else {\n // 右回転してから左回転する\n node-&gt;right = rightRotate(node-&gt;right);\n return leftRotate(node);\n }\n }\n // 平衡な木なので、回転せずにそのまま返す\n return node;\n}\n</code></pre> avl_tree.kt<pre><code>/* 回転操作によって、この部分木の平衡を回復する */\nfun rotate(node: TreeNode): TreeNode {\n // ノード node の平衡係数を取得する\n val balanceFactor = balanceFactor(node)\n // 左に偏った木\n if (balanceFactor &gt; 1) {\n if (balanceFactor(node.left) &gt;= 0) {\n // 右回転\n return rightRotate(node)\n } else {\n // 左回転してから右回転する\n node.left = leftRotate(node.left)\n return rightRotate(node)\n }\n }\n // 右に偏った木\n if (balanceFactor &lt; -1) {\n if (balanceFactor(node.right) &lt;= 0) {\n // 左回転\n return leftRotate(node)\n } else {\n // 右回転してから左回転する\n node.right = rightRotate(node.right)\n return leftRotate(node)\n }\n }\n // 平衡な木なので、回転せずにそのまま返す\n return node\n}\n</code></pre> avl_tree.rb<pre><code>### 回転操作によって、この部分木の平衡を回復する ###\ndef rotate(node)\n # ノード node の平衡係数を取得する\n balance_factor = balance_factor(node)\n # 左に偏った木\n if balance_factor &gt; 1\n if balance_factor(node.left) &gt;= 0\n # 右回転\n return right_rotate(node)\n else\n # 左回転してから右回転する\n node.left = left_rotate(node.left)\n return right_rotate(node)\n end\n # 右に偏った木\n elsif balance_factor &lt; -1\n if balance_factor(node.right) &lt;= 0\n # 左回転\n return left_rotate(node)\n else\n # 右回転してから左回転する\n node.right = right_rotate(node.right)\n return left_rotate(node)\n end\n end\n # 平衡な木なので、回転せずにそのまま返す\n node\nend\n</code></pre>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#753-avl","level":2,"title":"7.5.3   AVL 木の主な操作","text":"","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#1_2","level":3,"title":"1.   ノードの挿入","text":"<p>基本的には二分探索木の挿入と似ています。違いは、挿入後、そのノードから根までの経路上に不均衡なノードが生じる可能性があることです。したがって、挿入したノードを起点に下から上へ回転を行い、すべての不均衡を解消する必要があります。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py<pre><code>def insert(self, val):\n \"\"\"ノードを挿入する\"\"\"\n self._root = self.insert_helper(self._root, val)\n\ndef insert_helper(self, node: TreeNode | None, val: int) -&gt; TreeNode:\n \"\"\"ノードを再帰的に挿入する(補助メソッド)\"\"\"\n if node is None:\n return TreeNode(val)\n # 1. 挿入位置を探してノードを挿入する\n if val &lt; node.val:\n node.left = self.insert_helper(node.left, val)\n elif val &gt; node.val:\n node.right = self.insert_helper(node.right, val)\n else:\n # 重複したノードは挿入せず、そのまま戻る\n return node\n # ノードの高さを更新する\n self.update_height(node)\n # 2. 回転操作によって、この部分木の平衡を回復する\n return self.rotate(node)\n</code></pre> avl_tree.cpp<pre><code>/* ノードを挿入する */\nvoid insert(int val) {\n root = insertHelper(root, val);\n}\n\n/* ノードを再帰的に挿入する(補助メソッド) */\nTreeNode *insertHelper(TreeNode *node, int val) {\n if (node == nullptr)\n return new TreeNode(val);\n /* 1. 挿入位置を探してノードを挿入する */\n if (val &lt; node-&gt;val)\n node-&gt;left = insertHelper(node-&gt;left, val);\n else if (val &gt; node-&gt;val)\n node-&gt;right = insertHelper(node-&gt;right, val);\n else\n return node; // 重複したノードは挿入せず、そのまま戻る\n updateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.java<pre><code>/* ノードを挿入する */\nvoid insert(int val) {\n root = insertHelper(root, val);\n}\n\n/* ノードを再帰的に挿入する(補助メソッド) */\nTreeNode insertHelper(TreeNode node, int val) {\n if (node == null)\n return new TreeNode(val);\n /* 1. 挿入位置を探してノードを挿入する */\n if (val &lt; node.val)\n node.left = insertHelper(node.left, val);\n else if (val &gt; node.val)\n node.right = insertHelper(node.right, val);\n else\n return node; // 重複したノードは挿入せず、そのまま戻る\n updateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.cs<pre><code>/* ノードを挿入する */\nvoid Insert(int val) {\n root = InsertHelper(root, val);\n}\n\n/* ノードを再帰的に挿入する(補助メソッド) */\nTreeNode? InsertHelper(TreeNode? node, int val) {\n if (node == null) return new TreeNode(val);\n /* 1. 挿入位置を探してノードを挿入する */\n if (val &lt; node.val)\n node.left = InsertHelper(node.left, val);\n else if (val &gt; node.val)\n node.right = InsertHelper(node.right, val);\n else\n return node; // 重複したノードは挿入せず、そのまま戻る\n UpdateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = Rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.go<pre><code>/* ノードを挿入する */\nfunc (t *aVLTree) insert(val int) {\n t.root = t.insertHelper(t.root, val)\n}\n\n/* ノードを再帰的に挿入する(補助関数) */\nfunc (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode {\n if node == nil {\n return NewTreeNode(val)\n }\n /* 1. 挿入位置を探してノードを挿入する */\n if val &lt; node.Val.(int) {\n node.Left = t.insertHelper(node.Left, val)\n } else if val &gt; node.Val.(int) {\n node.Right = t.insertHelper(node.Right, val)\n } else {\n // 重複したノードは挿入せず、そのまま戻る\n return node\n }\n // ノードの高さを更新する\n t.updateHeight(node)\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = t.rotate(node)\n // 部分木の根ノードを返す\n return node\n}\n</code></pre> avl_tree.swift<pre><code>/* ノードを挿入する */\nfunc insert(val: Int) {\n root = insertHelper(node: root, val: val)\n}\n\n/* ノードを再帰的に挿入する(補助メソッド) */\nfunc insertHelper(node: TreeNode?, val: Int) -&gt; TreeNode? {\n var node = node\n if node == nil {\n return TreeNode(x: val)\n }\n /* 1. 挿入位置を探してノードを挿入する */\n if val &lt; node!.val {\n node?.left = insertHelper(node: node?.left, val: val)\n } else if val &gt; node!.val {\n node?.right = insertHelper(node: node?.right, val: val)\n } else {\n return node // 重複したノードは挿入せず、そのまま戻る\n }\n updateHeight(node: node) // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node: node)\n // 部分木の根ノードを返す\n return node\n}\n</code></pre> avl_tree.js<pre><code>/* ノードを挿入する */\ninsert(val) {\n this.root = this.#insertHelper(this.root, val);\n}\n\n/* ノードを再帰的に挿入する(補助メソッド) */\n#insertHelper(node, val) {\n if (node === null) return new TreeNode(val);\n /* 1. 挿入位置を探してノードを挿入する */\n if (val &lt; node.val) node.left = this.#insertHelper(node.left, val);\n else if (val &gt; node.val)\n node.right = this.#insertHelper(node.right, val);\n else return node; // 重複したノードは挿入せず、そのまま戻る\n this.#updateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = this.#rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.ts<pre><code>/* ノードを挿入する */\ninsert(val: number): void {\n this.root = this.insertHelper(this.root, val);\n}\n\n/* ノードを再帰的に挿入する(補助メソッド) */\ninsertHelper(node: TreeNode, val: number): TreeNode {\n if (node === null) return new TreeNode(val);\n /* 1. 挿入位置を探してノードを挿入する */\n if (val &lt; node.val) {\n node.left = this.insertHelper(node.left, val);\n } else if (val &gt; node.val) {\n node.right = this.insertHelper(node.right, val);\n } else {\n return node; // 重複したノードは挿入せず、そのまま戻る\n }\n this.updateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = this.rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.dart<pre><code>/* ノードを挿入する */\nvoid insert(int val) {\n root = insertHelper(root, val);\n}\n\n/* ノードを再帰的に挿入する(補助メソッド) */\nTreeNode? insertHelper(TreeNode? node, int val) {\n if (node == null) return TreeNode(val);\n /* 1. 挿入位置を探してノードを挿入する */\n if (val &lt; node.val)\n node.left = insertHelper(node.left, val);\n else if (val &gt; node.val)\n node.right = insertHelper(node.right, val);\n else\n return node; // 重複したノードは挿入せず、そのまま戻る\n updateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.rs<pre><code>/* ノードを挿入する */\nfn insert(&amp;mut self, val: i32) {\n self.root = Self::insert_helper(self.root.clone(), val);\n}\n\n/* ノードを再帰的に挿入する(補助メソッド) */\nfn insert_helper(node: OptionTreeNodeRc, val: i32) -&gt; OptionTreeNodeRc {\n match node {\n Some(mut node) =&gt; {\n /* 1. 挿入位置を探してノードを挿入する */\n match {\n let node_val = node.borrow().val;\n node_val\n }\n .cmp(&amp;val)\n {\n Ordering::Greater =&gt; {\n let left = node.borrow().left.clone();\n node.borrow_mut().left = Self::insert_helper(left, val);\n }\n Ordering::Less =&gt; {\n let right = node.borrow().right.clone();\n node.borrow_mut().right = Self::insert_helper(right, val);\n }\n Ordering::Equal =&gt; {\n return Some(node); // 重複したノードは挿入せず、そのまま戻る\n }\n }\n Self::update_height(Some(node.clone())); // ノードの高さを更新する\n\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = Self::rotate(Some(node)).unwrap();\n // 部分木の根ノードを返す\n Some(node)\n }\n None =&gt; Some(TreeNode::new(val)),\n }\n}\n</code></pre> avl_tree.c<pre><code>/* ノードを挿入する */\nvoid insert(AVLTree *tree, int val) {\n tree-&gt;root = insertHelper(tree-&gt;root, val);\n}\n\n/* ノードを再帰的に挿入する(補助関数) */\nTreeNode *insertHelper(TreeNode *node, int val) {\n if (node == NULL) {\n return newTreeNode(val);\n }\n /* 1. 挿入位置を探してノードを挿入する */\n if (val &lt; node-&gt;val) {\n node-&gt;left = insertHelper(node-&gt;left, val);\n } else if (val &gt; node-&gt;val) {\n node-&gt;right = insertHelper(node-&gt;right, val);\n } else {\n // 重複したノードは挿入せず、そのまま戻る\n return node;\n }\n // ノードの高さを更新する\n updateHeight(node);\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.kt<pre><code>/* ノードを挿入する */\nfun insert(_val: Int) {\n root = insertHelper(root, _val)\n}\n\n/* ノードを再帰的に挿入する(補助メソッド) */\nfun insertHelper(n: TreeNode?, _val: Int): TreeNode {\n if (n == null)\n return TreeNode(_val)\n var node = n\n /* 1. 挿入位置を探してノードを挿入する */\n if (_val &lt; node._val)\n node.left = insertHelper(node.left, _val)\n else if (_val &gt; node._val)\n node.right = insertHelper(node.right, _val)\n else\n return node // 重複したノードは挿入せず、そのまま戻る\n updateHeight(node) // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node)\n // 部分木の根ノードを返す\n return node\n}\n</code></pre> avl_tree.rb<pre><code>### ノードを挿入する ###\ndef insert(val)\n @root = insert_helper(@root, val)\nend\n\n### ノードを再帰的に挿入する(補助メソッド)###\ndef insert_helper(node, val)\n return TreeNode.new(val) if node.nil?\n # 1. 挿入位置を探してノードを挿入する\n if val &lt; node.val\n node.left = insert_helper(node.left, val)\n elsif val &gt; node.val\n node.right = insert_helper(node.right, val)\n else\n # 重複したノードは挿入せず、そのまま戻る\n return node\n end\n # ノードの高さを更新する\n update_height(node)\n # 2. 回転操作によって、この部分木の平衡を回復する\n rotate(node)\nend\n</code></pre>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#2_2","level":3,"title":"2.   ノードの削除","text":"<p>同様に、二分探索木の削除に加えて、下から上へ回転を行い、すべての不均衡を解消する必要があります。コードは次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby avl_tree.py<pre><code>def remove(self, val: int):\n \"\"\"ノードを削除する\"\"\"\n self._root = self.remove_helper(self._root, val)\n\ndef remove_helper(self, node: TreeNode | None, val: int) -&gt; TreeNode | None:\n \"\"\"ノードを再帰的に削除する(補助メソッド)\"\"\"\n if node is None:\n return None\n # 1. ノードを探して削除する\n if val &lt; node.val:\n node.left = self.remove_helper(node.left, val)\n elif val &gt; node.val:\n node.right = self.remove_helper(node.right, val)\n else:\n if node.left is None or node.right is None:\n child = node.left or node.right\n # 子ノード数 = 0 の場合、node を削除して戻る\n if child is None:\n return None\n # 子ノード数 = 1 の場合、node を削除する\n else:\n node = child\n else:\n # 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n temp = node.right\n while temp.left is not None:\n temp = temp.left\n node.right = self.remove_helper(node.right, temp.val)\n node.val = temp.val\n # ノードの高さを更新する\n self.update_height(node)\n # 2. 回転操作によって、この部分木の平衡を回復する\n return self.rotate(node)\n</code></pre> avl_tree.cpp<pre><code>/* ノードを削除する */\nvoid remove(int val) {\n root = removeHelper(root, val);\n}\n\n/* ノードを再帰的に削除する(補助メソッド) */\nTreeNode *removeHelper(TreeNode *node, int val) {\n if (node == nullptr)\n return nullptr;\n /* 1. ノードを探して削除する */\n if (val &lt; node-&gt;val)\n node-&gt;left = removeHelper(node-&gt;left, val);\n else if (val &gt; node-&gt;val)\n node-&gt;right = removeHelper(node-&gt;right, val);\n else {\n if (node-&gt;left == nullptr || node-&gt;right == nullptr) {\n TreeNode *child = node-&gt;left != nullptr ? node-&gt;left : node-&gt;right;\n // 子ノード数 = 0 の場合、node を削除して戻る\n if (child == nullptr) {\n delete node;\n return nullptr;\n }\n // 子ノード数 = 1 の場合、node を削除する\n else {\n delete node;\n node = child;\n }\n } else {\n // 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n TreeNode *temp = node-&gt;right;\n while (temp-&gt;left != nullptr) {\n temp = temp-&gt;left;\n }\n int tempVal = temp-&gt;val;\n node-&gt;right = removeHelper(node-&gt;right, temp-&gt;val);\n node-&gt;val = tempVal;\n }\n }\n updateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.java<pre><code>/* ノードを削除する */\nvoid remove(int val) {\n root = removeHelper(root, val);\n}\n\n/* ノードを再帰的に削除する(補助メソッド) */\nTreeNode removeHelper(TreeNode node, int val) {\n if (node == null)\n return null;\n /* 1. ノードを探して削除する */\n if (val &lt; node.val)\n node.left = removeHelper(node.left, val);\n else if (val &gt; node.val)\n node.right = removeHelper(node.right, val);\n else {\n if (node.left == null || node.right == null) {\n TreeNode child = node.left != null ? node.left : node.right;\n // 子ノード数 = 0 の場合、node を削除して戻る\n if (child == null)\n return null;\n // 子ノード数 = 1 の場合、node を削除する\n else\n node = child;\n } else {\n // 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n TreeNode temp = node.right;\n while (temp.left != null) {\n temp = temp.left;\n }\n node.right = removeHelper(node.right, temp.val);\n node.val = temp.val;\n }\n }\n updateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.cs<pre><code>/* ノードを削除する */\nvoid Remove(int val) {\n root = RemoveHelper(root, val);\n}\n\n/* ノードを再帰的に削除する(補助メソッド) */\nTreeNode? RemoveHelper(TreeNode? node, int val) {\n if (node == null) return null;\n /* 1. ノードを探して削除する */\n if (val &lt; node.val)\n node.left = RemoveHelper(node.left, val);\n else if (val &gt; node.val)\n node.right = RemoveHelper(node.right, val);\n else {\n if (node.left == null || node.right == null) {\n TreeNode? child = node.left ?? node.right;\n // 子ノード数 = 0 の場合、node を削除して戻る\n if (child == null)\n return null;\n // 子ノード数 = 1 の場合、node を削除する\n else\n node = child;\n } else {\n // 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n TreeNode? temp = node.right;\n while (temp.left != null) {\n temp = temp.left;\n }\n node.right = RemoveHelper(node.right, temp.val!.Value);\n node.val = temp.val;\n }\n }\n UpdateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = Rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.go<pre><code>/* ノードを削除する */\nfunc (t *aVLTree) remove(val int) {\n t.root = t.removeHelper(t.root, val)\n}\n\n/* ノードを再帰的に削除する(補助関数) */\nfunc (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode {\n if node == nil {\n return nil\n }\n /* 1. ノードを探して削除する */\n if val &lt; node.Val.(int) {\n node.Left = t.removeHelper(node.Left, val)\n } else if val &gt; node.Val.(int) {\n node.Right = t.removeHelper(node.Right, val)\n } else {\n if node.Left == nil || node.Right == nil {\n child := node.Left\n if node.Right != nil {\n child = node.Right\n }\n if child == nil {\n // 子ノード数 = 0 の場合、node を削除して戻る\n return nil\n } else {\n // 子ノード数 = 1 の場合、node を削除する\n node = child\n }\n } else {\n // 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n temp := node.Right\n for temp.Left != nil {\n temp = temp.Left\n }\n node.Right = t.removeHelper(node.Right, temp.Val.(int))\n node.Val = temp.Val\n }\n }\n // ノードの高さを更新する\n t.updateHeight(node)\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = t.rotate(node)\n // 部分木の根ノードを返す\n return node\n}\n</code></pre> avl_tree.swift<pre><code>/* ノードを削除する */\nfunc remove(val: Int) {\n root = removeHelper(node: root, val: val)\n}\n\n/* ノードを再帰的に削除する(補助メソッド) */\nfunc removeHelper(node: TreeNode?, val: Int) -&gt; TreeNode? {\n var node = node\n if node == nil {\n return nil\n }\n /* 1. ノードを探して削除する */\n if val &lt; node!.val {\n node?.left = removeHelper(node: node?.left, val: val)\n } else if val &gt; node!.val {\n node?.right = removeHelper(node: node?.right, val: val)\n } else {\n if node?.left == nil || node?.right == nil {\n let child = node?.left ?? node?.right\n // 子ノード数 = 0 の場合、node を削除して戻る\n if child == nil {\n return nil\n }\n // 子ノード数 = 1 の場合、node を削除する\n else {\n node = child\n }\n } else {\n // 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n var temp = node?.right\n while temp?.left != nil {\n temp = temp?.left\n }\n node?.right = removeHelper(node: node?.right, val: temp!.val)\n node?.val = temp!.val\n }\n }\n updateHeight(node: node) // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node: node)\n // 部分木の根ノードを返す\n return node\n}\n</code></pre> avl_tree.js<pre><code>/* ノードを削除する */\nremove(val) {\n this.root = this.#removeHelper(this.root, val);\n}\n\n/* ノードを再帰的に削除する(補助メソッド) */\n#removeHelper(node, val) {\n if (node === null) return null;\n /* 1. ノードを探して削除する */\n if (val &lt; node.val) node.left = this.#removeHelper(node.left, val);\n else if (val &gt; node.val)\n node.right = this.#removeHelper(node.right, val);\n else {\n if (node.left === null || node.right === null) {\n const child = node.left !== null ? node.left : node.right;\n // 子ノード数 = 0 の場合、node を削除して戻る\n if (child === null) return null;\n // 子ノード数 = 1 の場合、node を削除する\n else node = child;\n } else {\n // 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n let temp = node.right;\n while (temp.left !== null) {\n temp = temp.left;\n }\n node.right = this.#removeHelper(node.right, temp.val);\n node.val = temp.val;\n }\n }\n this.#updateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = this.#rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.ts<pre><code>/* ノードを削除する */\nremove(val: number): void {\n this.root = this.removeHelper(this.root, val);\n}\n\n/* ノードを再帰的に削除する(補助メソッド) */\nremoveHelper(node: TreeNode, val: number): TreeNode {\n if (node === null) return null;\n /* 1. ノードを探して削除する */\n if (val &lt; node.val) {\n node.left = this.removeHelper(node.left, val);\n } else if (val &gt; node.val) {\n node.right = this.removeHelper(node.right, val);\n } else {\n if (node.left === null || node.right === null) {\n const child = node.left !== null ? node.left : node.right;\n // 子ノード数 = 0 の場合、node を削除して戻る\n if (child === null) {\n return null;\n } else {\n // 子ノード数 = 1 の場合、node を削除する\n node = child;\n }\n } else {\n // 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n let temp = node.right;\n while (temp.left !== null) {\n temp = temp.left;\n }\n node.right = this.removeHelper(node.right, temp.val);\n node.val = temp.val;\n }\n }\n this.updateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = this.rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.dart<pre><code>/* ノードを削除する */\nvoid remove(int val) {\n root = removeHelper(root, val);\n}\n\n/* ノードを再帰的に削除する(補助メソッド) */\nTreeNode? removeHelper(TreeNode? node, int val) {\n if (node == null) return null;\n /* 1. ノードを探して削除する */\n if (val &lt; node.val)\n node.left = removeHelper(node.left, val);\n else if (val &gt; node.val)\n node.right = removeHelper(node.right, val);\n else {\n if (node.left == null || node.right == null) {\n TreeNode? child = node.left ?? node.right;\n // 子ノード数 = 0 の場合、node を削除して戻る\n if (child == null)\n return null;\n // 子ノード数 = 1 の場合、node を削除する\n else\n node = child;\n } else {\n // 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n TreeNode? temp = node.right;\n while (temp!.left != null) {\n temp = temp.left;\n }\n node.right = removeHelper(node.right, temp.val);\n node.val = temp.val;\n }\n }\n updateHeight(node); // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.rs<pre><code>/* ノードを削除する */\nfn remove(&amp;self, val: i32) {\n Self::remove_helper(self.root.clone(), val);\n}\n\n/* ノードを再帰的に削除する(補助メソッド) */\nfn remove_helper(node: OptionTreeNodeRc, val: i32) -&gt; OptionTreeNodeRc {\n match node {\n Some(mut node) =&gt; {\n /* 1. ノードを探して削除する */\n if val &lt; node.borrow().val {\n let left = node.borrow().left.clone();\n node.borrow_mut().left = Self::remove_helper(left, val);\n } else if val &gt; node.borrow().val {\n let right = node.borrow().right.clone();\n node.borrow_mut().right = Self::remove_helper(right, val);\n } else if node.borrow().left.is_none() || node.borrow().right.is_none() {\n let child = if node.borrow().left.is_some() {\n node.borrow().left.clone()\n } else {\n node.borrow().right.clone()\n };\n match child {\n // 子ノード数 = 0 の場合、node を削除して戻る\n None =&gt; {\n return None;\n }\n // 子ノード数 = 1 の場合、node を削除する\n Some(child) =&gt; node = child,\n }\n } else {\n // 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n let mut temp = node.borrow().right.clone().unwrap();\n loop {\n let temp_left = temp.borrow().left.clone();\n if temp_left.is_none() {\n break;\n }\n temp = temp_left.unwrap();\n }\n let right = node.borrow().right.clone();\n node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val);\n node.borrow_mut().val = temp.borrow().val;\n }\n Self::update_height(Some(node.clone())); // ノードの高さを更新する\n\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = Self::rotate(Some(node)).unwrap();\n // 部分木の根ノードを返す\n Some(node)\n }\n None =&gt; None,\n }\n}\n</code></pre> avl_tree.c<pre><code>/* ノードを削除する */\n// stdio.h を読み込んでいるため、ここでは remove という名前は使えない\nvoid removeItem(AVLTree *tree, int val) {\n TreeNode *root = removeHelper(tree-&gt;root, val);\n}\n\n/* ノードを再帰的に削除する(補助関数) */\nTreeNode *removeHelper(TreeNode *node, int val) {\n TreeNode *child, *grandChild;\n if (node == NULL) {\n return NULL;\n }\n /* 1. ノードを探して削除する */\n if (val &lt; node-&gt;val) {\n node-&gt;left = removeHelper(node-&gt;left, val);\n } else if (val &gt; node-&gt;val) {\n node-&gt;right = removeHelper(node-&gt;right, val);\n } else {\n if (node-&gt;left == NULL || node-&gt;right == NULL) {\n child = node-&gt;left;\n if (node-&gt;right != NULL) {\n child = node-&gt;right;\n }\n // 子ノード数 = 0 の場合、node を削除して戻る\n if (child == NULL) {\n return NULL;\n } else {\n // 子ノード数 = 1 の場合、node を削除する\n node = child;\n }\n } else {\n // 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n TreeNode *temp = node-&gt;right;\n while (temp-&gt;left != NULL) {\n temp = temp-&gt;left;\n }\n int tempVal = temp-&gt;val;\n node-&gt;right = removeHelper(node-&gt;right, temp-&gt;val);\n node-&gt;val = tempVal;\n }\n }\n // ノードの高さを更新する\n updateHeight(node);\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node);\n // 部分木の根ノードを返す\n return node;\n}\n</code></pre> avl_tree.kt<pre><code>/* ノードを削除する */\nfun remove(_val: Int) {\n root = removeHelper(root, _val)\n}\n\n/* ノードを再帰的に削除する(補助メソッド) */\nfun removeHelper(n: TreeNode?, _val: Int): TreeNode? {\n var node = n ?: return null\n /* 1. ノードを探して削除する */\n if (_val &lt; node._val)\n node.left = removeHelper(node.left, _val)\n else if (_val &gt; node._val)\n node.right = removeHelper(node.right, _val)\n else {\n if (node.left == null || node.right == null) {\n val child = if (node.left != null)\n node.left\n else\n node.right\n // 子ノード数 = 0 の場合、node を削除して戻る\n if (child == null)\n return null\n // 子ノード数 = 1 の場合、node を削除する\n else\n node = child\n } else {\n // 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n var temp = node.right\n while (temp!!.left != null) {\n temp = temp.left\n }\n node.right = removeHelper(node.right, temp._val)\n node._val = temp._val\n }\n }\n updateHeight(node) // ノードの高さを更新する\n /* 2. 回転操作によって、この部分木の平衡を回復する */\n node = rotate(node)\n // 部分木の根ノードを返す\n return node\n}\n</code></pre> avl_tree.rb<pre><code>### ノードを削除する ###\ndef remove(val)\n @root = remove_helper(@root, val)\nend\n\n### ノードを再帰的に削除する(補助メソッド)###\ndef remove_helper(node, val)\n return if node.nil?\n # 1. ノードを探して削除する\n if val &lt; node.val\n node.left = remove_helper(node.left, val)\n elsif val &gt; node.val\n node.right = remove_helper(node.right, val)\n else\n if node.left.nil? || node.right.nil?\n child = node.left || node.right\n # 子ノード数 = 0 の場合、node を削除して戻る\n return if child.nil?\n # 子ノード数 = 1 の場合、node を削除する\n node = child\n else\n # 子ノード数 = 2 の場合、通りがけ順で次のノードを削除し、そのノードで現在のノードを置き換える\n temp = node.right\n while !temp.left.nil?\n temp = temp.left\n end\n node.right = remove_helper(node.right, temp.val)\n node.val = temp.val\n end\n end\n # ノードの高さを更新する\n update_height(node)\n # 2. 回転操作によって、この部分木の平衡を回復する\n rotate(node)\nend\n</code></pre>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#3_1","level":3,"title":"3.   ノードの探索","text":"<p>探索は二分探索木と同じなので、ここでは繰り返しません。</p>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/avl_tree/#754-avl","level":2,"title":"7.5.4   AVL 木の代表的な用途","text":"<ul> <li>大規模なデータの組織化と格納に使い、探索が多く追加・削除が少ない場面に適しています。</li> <li>データベースのインデックスシステムを構築するために使います。</li> <li>赤黒木も代表的な平衡二分探索木です。AVL 木より平衡条件が緩く、挿入・削除に必要な回転が少ないため、追加・削除の平均的な効率が高くなります。</li> </ul>","path":["第 7 章   木","7.5   AVL 木 *"],"tags":[]},{"location":"chapter_tree/binary_search_tree/","level":1,"title":"7.4   二分探索木","text":"<p>図 7-16 の二分探索木(binary search tree)は、次の条件を満たします。</p> <ol> <li>根について、左の部分木のすべての値 \\(&lt;\\) 根の値 \\(&lt;\\) 右の部分木のすべての値が成り立ちます。</li> <li>どのノードの左右の部分木も二分探索木であり、同じく条件 <code>1.</code> を満たします。</li> </ol> <p></p> <p> 図 7-16   二分探索木 </p>","path":["第 7 章   木","7.4   二分探索木"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#741","level":2,"title":"7.4.1   二分探索木の操作","text":"<p>クラス <code>BinarySearchTree</code> にまとめ、根ノードを指すメンバー変数 <code>root</code> を宣言します。</p>","path":["第 7 章   木","7.4   二分探索木"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#1","level":3,"title":"1.   ノードの探索","text":"<p>目的の値 <code>num</code> が与えられたら、二分探索木の性質を使って探せます。図 7-17 のように、ノード <code>cur</code> を宣言して根 <code>root</code> から出発し、<code>cur.val</code> と <code>num</code> の大小を繰り返し比較します。</p> <ul> <li><code>cur.val &lt; num</code> なら、目的のノードは右の部分木にあるので、<code>cur = cur.right</code> とします。</li> <li><code>cur.val &gt; num</code> なら、目的のノードは左の部分木にあるので、<code>cur = cur.left</code> とします。</li> <li><code>cur.val = num</code> なら、見つかったのでループを抜け、そのノードを返します。</li> </ul> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt; <p></p> <p></p> <p></p> <p></p> <p> 図 7-17   二分探索木でノードを探す例 </p> <p>二分探索木の探索は、二分探索と同じく、各反復で候補の半分を除外する仕組みです。反復回数は最大で木の高さとなり、木が平衡なら \\(O(\\log n)\\) 時間です。コード例を示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py<pre><code>def search(self, num: int) -&gt; TreeNode | None:\n \"\"\"ノードを探索する\"\"\"\n cur = self._root\n # 繰り返し探索し、葉ノードを越えたら終了する\n while cur is not None:\n # 目的のノードは cur の右部分木にある\n if cur.val &lt; num:\n cur = cur.right\n # 目的のノードは cur の左部分木にある\n elif cur.val &gt; num:\n cur = cur.left\n # 目的のノードが見つかったので、ループを終了する\n else:\n break\n return cur\n</code></pre> binary_search_tree.cpp<pre><code>/* ノードを探索する */\nTreeNode *search(int num) {\n TreeNode *cur = root;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != nullptr) {\n // 目的のノードは cur の右部分木にある\n if (cur-&gt;val &lt; num)\n cur = cur-&gt;right;\n // 目的のノードは cur の左部分木にある\n else if (cur-&gt;val &gt; num)\n cur = cur-&gt;left;\n // 目的のノードが見つかったので、ループを終了する\n else\n break;\n }\n // 目的のノードを返す\n return cur;\n}\n</code></pre> binary_search_tree.java<pre><code>/* ノードを探索する */\nTreeNode search(int num) {\n TreeNode cur = root;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 目的のノードは cur の右部分木にある\n if (cur.val &lt; num)\n cur = cur.right;\n // 目的のノードは cur の左部分木にある\n else if (cur.val &gt; num)\n cur = cur.left;\n // 目的のノードが見つかったので、ループを終了する\n else\n break;\n }\n // 目的のノードを返す\n return cur;\n}\n</code></pre> binary_search_tree.cs<pre><code>/* ノードを探索する */\nTreeNode? Search(int num) {\n TreeNode? cur = root;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 目的のノードは cur の右部分木にある\n if (cur.val &lt; num) cur =\n cur.right;\n // 目的のノードは cur の左部分木にある\n else if (cur.val &gt; num)\n cur = cur.left;\n // 目的のノードが見つかったので、ループを終了する\n else\n break;\n }\n // 目的のノードを返す\n return cur;\n}\n</code></pre> binary_search_tree.go<pre><code>/* ノードを探索する */\nfunc (bst *binarySearchTree) search(num int) *TreeNode {\n node := bst.root\n // 繰り返し探索し、葉ノードを越えたら終了する\n for node != nil {\n if node.Val.(int) &lt; num {\n // 目的のノードは cur の右部分木にある\n node = node.Right\n } else if node.Val.(int) &gt; num {\n // 目的のノードは cur の左部分木にある\n node = node.Left\n } else {\n // 目的のノードが見つかったので、ループを終了する\n break\n }\n }\n // 目的のノードを返す\n return node\n}\n</code></pre> binary_search_tree.swift<pre><code>/* ノードを探索する */\nfunc search(num: Int) -&gt; TreeNode? {\n var cur = root\n // 繰り返し探索し、葉ノードを越えたら終了する\n while cur != nil {\n // 目的のノードは cur の右部分木にある\n if cur!.val &lt; num {\n cur = cur?.right\n }\n // 目的のノードは cur の左部分木にある\n else if cur!.val &gt; num {\n cur = cur?.left\n }\n // 目的のノードが見つかったので、ループを終了する\n else {\n break\n }\n }\n // 目的のノードを返す\n return cur\n}\n</code></pre> binary_search_tree.js<pre><code>/* ノードを探索する */\nsearch(num) {\n let cur = this.root;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur !== null) {\n // 目的のノードは cur の右部分木にある\n if (cur.val &lt; num) cur = cur.right;\n // 目的のノードは cur の左部分木にある\n else if (cur.val &gt; num) cur = cur.left;\n // 目的のノードが見つかったので、ループを終了する\n else break;\n }\n // 目的のノードを返す\n return cur;\n}\n</code></pre> binary_search_tree.ts<pre><code>/* ノードを探索する */\nsearch(num: number): TreeNode | null {\n let cur = this.root;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur !== null) {\n // 目的のノードは cur の右部分木にある\n if (cur.val &lt; num) cur = cur.right;\n // 目的のノードは cur の左部分木にある\n else if (cur.val &gt; num) cur = cur.left;\n // 目的のノードが見つかったので、ループを終了する\n else break;\n }\n // 目的のノードを返す\n return cur;\n}\n</code></pre> binary_search_tree.dart<pre><code>/* ノードを探索する */\nTreeNode? search(int _num) {\n TreeNode? cur = _root;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 目的のノードは cur の右部分木にある\n if (cur.val &lt; _num)\n cur = cur.right;\n // 目的のノードは cur の左部分木にある\n else if (cur.val &gt; _num)\n cur = cur.left;\n // 目的のノードが見つかったので、ループを終了する\n else\n break;\n }\n // 目的のノードを返す\n return cur;\n}\n</code></pre> binary_search_tree.rs<pre><code>/* ノードを探索する */\npub fn search(&amp;self, num: i32) -&gt; OptionTreeNodeRc {\n let mut cur = self.root.clone();\n // 繰り返し探索し、葉ノードを越えたら終了する\n while let Some(node) = cur.clone() {\n match num.cmp(&amp;node.borrow().val) {\n // 目的のノードは cur の右部分木にある\n Ordering::Greater =&gt; cur = node.borrow().right.clone(),\n // 目的のノードは cur の左部分木にある\n Ordering::Less =&gt; cur = node.borrow().left.clone(),\n // 目的のノードが見つかったので、ループを終了する\n Ordering::Equal =&gt; break,\n }\n }\n\n // 目的のノードを返す\n cur\n}\n</code></pre> binary_search_tree.c<pre><code>/* ノードを探索する */\nTreeNode *search(BinarySearchTree *bst, int num) {\n TreeNode *cur = bst-&gt;root;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != NULL) {\n if (cur-&gt;val &lt; num) {\n // 目的のノードは cur の右部分木にある\n cur = cur-&gt;right;\n } else if (cur-&gt;val &gt; num) {\n // 目的のノードは cur の左部分木にある\n cur = cur-&gt;left;\n } else {\n // 目的のノードが見つかったので、ループを終了する\n break;\n }\n }\n // 目的のノードを返す\n return cur;\n}\n</code></pre> binary_search_tree.kt<pre><code>/* ノードを探索する */\nfun search(num: Int): TreeNode? {\n var cur = root\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 目的のノードは cur の右部分木にある\n cur = if (cur._val &lt; num)\n cur.right\n // 目的のノードは cur の左部分木にある\n else if (cur._val &gt; num)\n cur.left\n // 目的のノードが見つかったので、ループを終了する\n else\n break\n }\n // 目的のノードを返す\n return cur\n}\n</code></pre> binary_search_tree.rb<pre><code>### ノードを探索する ###\ndef search(num)\n cur = @root\n\n # 繰り返し探索し、葉ノードを越えたら終了する\n while !cur.nil?\n # 目的のノードは cur の右部分木にある\n if cur.val &lt; num\n cur = cur.right\n # 目的のノードは cur の左部分木にある\n elsif cur.val &gt; num\n cur = cur.left\n # 目的のノードが見つかったので、ループを終了する\n else\n break\n end\n end\n\n cur\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 7 章   木","7.4   二分探索木"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#2","level":3,"title":"2.   ノードの挿入","text":"<p>挿入する要素 <code>num</code> に対して、「左の部分木 &lt; 根 &lt; 右の部分木」という性質を保つため、図 7-18 の手順で挿入します。</p> <ol> <li>挿入位置を探す:探索と同じく根から出発し、現在の値と <code>num</code> を比較しながら下へ進みます。葉を越えて <code>None</code> に達したらループを抜けます。</li> <li>その位置に挿入する:値 <code>num</code> のノードを初期化し、<code>None</code> の位置へ置きます。</li> </ol> <p></p> <p> 図 7-18   二分探索木へのノードの挿入 </p> <p>実装では、次の二点に注意します。</p> <ul> <li>重複するノードは定義に反するため許しません。挿入するノードがすでに存在すれば、何もせずに戻ります。</li> <li>挿入のため、ノード <code>pre</code> に前回の反復のノードを保存します。これにより、<code>None</code> に達したときに親を取得して挿入できます。</li> </ul> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py<pre><code>def insert(self, num: int):\n \"\"\"ノードを挿入する\"\"\"\n # 木が空の場合、根ノードを初期化する\n if self._root is None:\n self._root = TreeNode(num)\n return\n # 繰り返し探索し、葉ノードを越えたら終了する\n cur, pre = self._root, None\n while cur is not None:\n # 重複したノードが見つかったので、そのまま戻る\n if cur.val == num:\n return\n pre = cur\n # 挿入位置は cur の右部分木にある\n if cur.val &lt; num:\n cur = cur.right\n # 挿入位置は cur の左部分木にある\n else:\n cur = cur.left\n # ノードを挿入する\n node = TreeNode(num)\n if pre.val &lt; num:\n pre.right = node\n else:\n pre.left = node\n</code></pre> binary_search_tree.cpp<pre><code>/* ノードを挿入する */\nvoid insert(int num) {\n // 木が空の場合、根ノードを初期化する\n if (root == nullptr) {\n root = new TreeNode(num);\n return;\n }\n TreeNode *cur = root, *pre = nullptr;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != nullptr) {\n // 重複したノードが見つかったので、そのまま戻る\n if (cur-&gt;val == num)\n return;\n pre = cur;\n // 挿入位置は cur の右部分木にある\n if (cur-&gt;val &lt; num)\n cur = cur-&gt;right;\n // 挿入位置は cur の左部分木にある\n else\n cur = cur-&gt;left;\n }\n // ノードを挿入する\n TreeNode *node = new TreeNode(num);\n if (pre-&gt;val &lt; num)\n pre-&gt;right = node;\n else\n pre-&gt;left = node;\n}\n</code></pre> binary_search_tree.java<pre><code>/* ノードを挿入する */\nvoid insert(int num) {\n // 木が空の場合、根ノードを初期化する\n if (root == null) {\n root = new TreeNode(num);\n return;\n }\n TreeNode cur = root, pre = null;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 重複したノードが見つかったので、そのまま戻る\n if (cur.val == num)\n return;\n pre = cur;\n // 挿入位置は cur の右部分木にある\n if (cur.val &lt; num)\n cur = cur.right;\n // 挿入位置は cur の左部分木にある\n else\n cur = cur.left;\n }\n // ノードを挿入する\n TreeNode node = new TreeNode(num);\n if (pre.val &lt; num)\n pre.right = node;\n else\n pre.left = node;\n}\n</code></pre> binary_search_tree.cs<pre><code>/* ノードを挿入する */\nvoid Insert(int num) {\n // 木が空の場合、根ノードを初期化する\n if (root == null) {\n root = new TreeNode(num);\n return;\n }\n TreeNode? cur = root, pre = null;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 重複したノードが見つかったので、そのまま戻る\n if (cur.val == num)\n return;\n pre = cur;\n // 挿入位置は cur の右部分木にある\n if (cur.val &lt; num)\n cur = cur.right;\n // 挿入位置は cur の左部分木にある\n else\n cur = cur.left;\n }\n\n // ノードを挿入する\n TreeNode node = new(num);\n if (pre != null) {\n if (pre.val &lt; num)\n pre.right = node;\n else\n pre.left = node;\n }\n}\n</code></pre> binary_search_tree.go<pre><code>/* ノードを挿入する */\nfunc (bst *binarySearchTree) insert(num int) {\n cur := bst.root\n // 木が空の場合、根ノードを初期化する\n if cur == nil {\n bst.root = NewTreeNode(num)\n return\n }\n // 挿入対象ノードの直前のノードの位置\n var pre *TreeNode = nil\n // 繰り返し探索し、葉ノードを越えたら終了する\n for cur != nil {\n if cur.Val == num {\n return\n }\n pre = cur\n if cur.Val.(int) &lt; num {\n cur = cur.Right\n } else {\n cur = cur.Left\n }\n }\n // ノードを挿入する\n node := NewTreeNode(num)\n if pre.Val.(int) &lt; num {\n pre.Right = node\n } else {\n pre.Left = node\n }\n}\n</code></pre> binary_search_tree.swift<pre><code>/* ノードを挿入する */\nfunc insert(num: Int) {\n // 木が空の場合、根ノードを初期化する\n if root == nil {\n root = TreeNode(x: num)\n return\n }\n var cur = root\n var pre: TreeNode?\n // 繰り返し探索し、葉ノードを越えたら終了する\n while cur != nil {\n // 重複したノードが見つかったので、そのまま戻る\n if cur!.val == num {\n return\n }\n pre = cur\n // 挿入位置は cur の右部分木にある\n if cur!.val &lt; num {\n cur = cur?.right\n }\n // 挿入位置は cur の左部分木にある\n else {\n cur = cur?.left\n }\n }\n // ノードを挿入する\n let node = TreeNode(x: num)\n if pre!.val &lt; num {\n pre?.right = node\n } else {\n pre?.left = node\n }\n}\n</code></pre> binary_search_tree.js<pre><code>/* ノードを挿入する */\ninsert(num) {\n // 木が空の場合、根ノードを初期化する\n if (this.root === null) {\n this.root = new TreeNode(num);\n return;\n }\n let cur = this.root,\n pre = null;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur !== null) {\n // 重複したノードが見つかったので、そのまま戻る\n if (cur.val === num) return;\n pre = cur;\n // 挿入位置は cur の右部分木にある\n if (cur.val &lt; num) cur = cur.right;\n // 挿入位置は cur の左部分木にある\n else cur = cur.left;\n }\n // ノードを挿入する\n const node = new TreeNode(num);\n if (pre.val &lt; num) pre.right = node;\n else pre.left = node;\n}\n</code></pre> binary_search_tree.ts<pre><code>/* ノードを挿入する */\ninsert(num: number): void {\n // 木が空の場合、根ノードを初期化する\n if (this.root === null) {\n this.root = new TreeNode(num);\n return;\n }\n let cur: TreeNode | null = this.root,\n pre: TreeNode | null = null;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur !== null) {\n // 重複したノードが見つかったので、そのまま戻る\n if (cur.val === num) return;\n pre = cur;\n // 挿入位置は cur の右部分木にある\n if (cur.val &lt; num) cur = cur.right;\n // 挿入位置は cur の左部分木にある\n else cur = cur.left;\n }\n // ノードを挿入する\n const node = new TreeNode(num);\n if (pre!.val &lt; num) pre!.right = node;\n else pre!.left = node;\n}\n</code></pre> binary_search_tree.dart<pre><code>/* ノードを挿入する */\nvoid insert(int _num) {\n // 木が空の場合、根ノードを初期化する\n if (_root == null) {\n _root = TreeNode(_num);\n return;\n }\n TreeNode? cur = _root;\n TreeNode? pre = null;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 重複したノードが見つかったので、そのまま戻る\n if (cur.val == _num) return;\n pre = cur;\n // 挿入位置は cur の右部分木にある\n if (cur.val &lt; _num)\n cur = cur.right;\n // 挿入位置は cur の左部分木にある\n else\n cur = cur.left;\n }\n // ノードを挿入する\n TreeNode? node = TreeNode(_num);\n if (pre!.val &lt; _num)\n pre.right = node;\n else\n pre.left = node;\n}\n</code></pre> binary_search_tree.rs<pre><code>/* ノードを挿入する */\npub fn insert(&amp;mut self, num: i32) {\n // 木が空の場合、根ノードを初期化する\n if self.root.is_none() {\n self.root = Some(TreeNode::new(num));\n return;\n }\n let mut cur = self.root.clone();\n let mut pre = None;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while let Some(node) = cur.clone() {\n match num.cmp(&amp;node.borrow().val) {\n // 重複したノードが見つかったので、そのまま戻る\n Ordering::Equal =&gt; return,\n // 挿入位置は cur の右部分木にある\n Ordering::Greater =&gt; {\n pre = cur.clone();\n cur = node.borrow().right.clone();\n }\n // 挿入位置は cur の左部分木にある\n Ordering::Less =&gt; {\n pre = cur.clone();\n cur = node.borrow().left.clone();\n }\n }\n }\n // ノードを挿入する\n let pre = pre.unwrap();\n let node = Some(TreeNode::new(num));\n if num &gt; pre.borrow().val {\n pre.borrow_mut().right = node;\n } else {\n pre.borrow_mut().left = node;\n }\n}\n</code></pre> binary_search_tree.c<pre><code>/* ノードを挿入する */\nvoid insert(BinarySearchTree *bst, int num) {\n // 木が空の場合、根ノードを初期化する\n if (bst-&gt;root == NULL) {\n bst-&gt;root = newTreeNode(num);\n return;\n }\n TreeNode *cur = bst-&gt;root, *pre = NULL;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != NULL) {\n // 重複したノードが見つかったので、そのまま戻る\n if (cur-&gt;val == num) {\n return;\n }\n pre = cur;\n if (cur-&gt;val &lt; num) {\n // 挿入位置は cur の右部分木にある\n cur = cur-&gt;right;\n } else {\n // 挿入位置は cur の左部分木にある\n cur = cur-&gt;left;\n }\n }\n // ノードを挿入する\n TreeNode *node = newTreeNode(num);\n if (pre-&gt;val &lt; num) {\n pre-&gt;right = node;\n } else {\n pre-&gt;left = node;\n }\n}\n</code></pre> binary_search_tree.kt<pre><code>/* ノードを挿入する */\nfun insert(num: Int) {\n // 木が空の場合、根ノードを初期化する\n if (root == null) {\n root = TreeNode(num)\n return\n }\n var cur = root\n var pre: TreeNode? = null\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 重複したノードが見つかったので、そのまま戻る\n if (cur._val == num)\n return\n pre = cur\n // 挿入位置は cur の右部分木にある\n cur = if (cur._val &lt; num)\n cur.right\n // 挿入位置は cur の左部分木にある\n else\n cur.left\n }\n // ノードを挿入する\n val node = TreeNode(num)\n if (pre?._val!! &lt; num)\n pre.right = node\n else\n pre.left = node\n}\n</code></pre> binary_search_tree.rb<pre><code>### ノードを挿入する ###\ndef insert(num)\n # 木が空の場合、根ノードを初期化する\n if @root.nil?\n @root = TreeNode.new(num)\n return\n end\n\n # 繰り返し探索し、葉ノードを越えたら終了する\n cur, pre = @root, nil\n while !cur.nil?\n # 重複したノードが見つかったので、そのまま戻る\n return if cur.val == num\n\n pre = cur\n # 挿入位置は cur の右部分木にある\n if cur.val &lt; num\n cur = cur.right\n # 挿入位置は cur の左部分木にある\n else\n cur = cur.left\n end\n end\n\n # ノードを挿入する\n node = TreeNode.new(num)\n if pre.val &lt; num\n pre.right = node\n else\n pre.left = node\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>探索と同じく、挿入には \\(O(\\log n)\\) 時間かかります。</p>","path":["第 7 章   木","7.4   二分探索木"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#3","level":3,"title":"3.   ノードの削除","text":"<p>まず目的のノードを見つけてから削除します。挿入と同じく、削除後も「左の部分木 &lt; 根 &lt; 右の部分木」を保つ必要があります。そのため、子の数が 0、1、2 の三つの場合に分けて処理します。</p> <p>図 7-19 のように、削除するノードの次数が \\(0\\) なら葉なので、直接削除できます。</p> <p></p> <p> 図 7-19   二分探索木のノードの削除(次数 0) </p> <p>図 7-20 のように、次数が \\(1\\) なら、そのノードを子に置き換えれば済みます。</p> <p></p> <p> 図 7-20   二分探索木のノードの削除(次数 1) </p> <p>次数が \\(2\\) の場合は直接削除できず、別のノードで置き換える必要があります。「左の部分木 \\(&lt;\\) 根 \\(&lt;\\) 右の部分木」を保つため、右の部分木の最小ノードか、左の部分木の最大ノードを使えます。</p> <p>右の部分木の最小ノード、つまり通りがけ順で次のノードを選ぶとすると、手順は図 7-21 のとおりです。</p> <ol> <li>削除するノードの次に通りがけ順で現れるノードを探し、<code>tmp</code> とします。</li> <li>削除するノードの値を <code>tmp</code> の値で上書きし、木から <code>tmp</code> を再帰的に削除します。</li> </ol> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt; <p></p> <p></p> <p></p> <p></p> <p> 図 7-21   二分探索木のノードの削除(次数 2) </p> <p>削除も \\(O(\\log n)\\) 時間です。削除対象の探索に \\(O(\\log n)\\)、通りがけ順の後続ノードの取得に \\(O(\\log n)\\) 時間かかります。コード例を示します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_search_tree.py<pre><code>def remove(self, num: int):\n \"\"\"ノードを削除する\"\"\"\n # 木が空の場合、そのまま早期リターンする\n if self._root is None:\n return\n # 繰り返し探索し、葉ノードを越えたら終了する\n cur, pre = self._root, None\n while cur is not None:\n # 削除対象のノードが見つかったので、ループを終了する\n if cur.val == num:\n break\n pre = cur\n # 削除対象のノードは cur の右部分木にある\n if cur.val &lt; num:\n cur = cur.right\n # 削除対象のノードは cur の左部分木にある\n else:\n cur = cur.left\n # 削除対象のノードが存在しない場合、そのまま戻る\n if cur is None:\n return\n\n # 子ノード数 = 0 または 1\n if cur.left is None or cur.right is None:\n # 子ノード数 = 0 / 1 の場合、child = null / その子ノード\n child = cur.left or cur.right\n # ノード cur を削除する\n if cur != self._root:\n if pre.left == cur:\n pre.left = child\n else:\n pre.right = child\n else:\n # 削除するノードが根ノードの場合、根ノードを指定し直す\n self._root = child\n # 子ノード数 = 2\n else:\n # 通りがけ順で cur の次のノードを取得する\n tmp: TreeNode = cur.right\n while tmp.left is not None:\n tmp = tmp.left\n # ノード tmp を再帰的に削除する\n self.remove(tmp.val)\n # tmp で cur を上書きする\n cur.val = tmp.val\n</code></pre> binary_search_tree.cpp<pre><code>/* ノードを削除する */\nvoid remove(int num) {\n // 木が空の場合、そのまま早期リターンする\n if (root == nullptr)\n return;\n TreeNode *cur = root, *pre = nullptr;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != nullptr) {\n // 削除対象のノードが見つかったので、ループを終了する\n if (cur-&gt;val == num)\n break;\n pre = cur;\n // 削除対象のノードは cur の右部分木にある\n if (cur-&gt;val &lt; num)\n cur = cur-&gt;right;\n // 削除対象のノードは cur の左部分木にある\n else\n cur = cur-&gt;left;\n }\n // 削除対象のノードが存在しない場合、そのまま戻る\n if (cur == nullptr)\n return;\n // 子ノード数 = 0 または 1\n if (cur-&gt;left == nullptr || cur-&gt;right == nullptr) {\n // 子ノード数 = 0 / 1 の場合、child = nullptr / その子ノード\n TreeNode *child = cur-&gt;left != nullptr ? cur-&gt;left : cur-&gt;right;\n // ノード cur を削除する\n if (cur != root) {\n if (pre-&gt;left == cur)\n pre-&gt;left = child;\n else\n pre-&gt;right = child;\n } else {\n // 削除するノードが根ノードの場合、根ノードを指定し直す\n root = child;\n }\n // メモリを解放する\n delete cur;\n }\n // 子ノード数 = 2\n else {\n // 通りがけ順で cur の次のノードを取得する\n TreeNode *tmp = cur-&gt;right;\n while (tmp-&gt;left != nullptr) {\n tmp = tmp-&gt;left;\n }\n int tmpVal = tmp-&gt;val;\n // ノード tmp を再帰的に削除する\n remove(tmp-&gt;val);\n // tmp で cur を上書きする\n cur-&gt;val = tmpVal;\n }\n}\n</code></pre> binary_search_tree.java<pre><code>/* ノードを削除する */\nvoid remove(int num) {\n // 木が空の場合、そのまま早期リターンする\n if (root == null)\n return;\n TreeNode cur = root, pre = null;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 削除対象のノードが見つかったので、ループを終了する\n if (cur.val == num)\n break;\n pre = cur;\n // 削除対象のノードは cur の右部分木にある\n if (cur.val &lt; num)\n cur = cur.right;\n // 削除対象のノードは cur の左部分木にある\n else\n cur = cur.left;\n }\n // 削除対象のノードが存在しない場合、そのまま戻る\n if (cur == null)\n return;\n // 子ノード数 = 0 または 1\n if (cur.left == null || cur.right == null) {\n // 子ノード数 = 0 / 1 の場合、child = null / その子ノード\n TreeNode child = cur.left != null ? cur.left : cur.right;\n // ノード cur を削除する\n if (cur != root) {\n if (pre.left == cur)\n pre.left = child;\n else\n pre.right = child;\n } else {\n // 削除するノードが根ノードの場合、根ノードを指定し直す\n root = child;\n }\n }\n // 子ノード数 = 2\n else {\n // 通りがけ順で cur の次のノードを取得する\n TreeNode tmp = cur.right;\n while (tmp.left != null) {\n tmp = tmp.left;\n }\n // ノード tmp を再帰的に削除する\n remove(tmp.val);\n // tmp で cur を上書きする\n cur.val = tmp.val;\n }\n}\n</code></pre> binary_search_tree.cs<pre><code>/* ノードを削除する */\nvoid Remove(int num) {\n // 木が空の場合、そのまま早期リターンする\n if (root == null)\n return;\n TreeNode? cur = root, pre = null;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 削除対象のノードが見つかったので、ループを終了する\n if (cur.val == num)\n break;\n pre = cur;\n // 削除対象のノードは cur の右部分木にある\n if (cur.val &lt; num)\n cur = cur.right;\n // 削除対象のノードは cur の左部分木にある\n else\n cur = cur.left;\n }\n // 削除対象のノードが存在しない場合、そのまま戻る\n if (cur == null)\n return;\n // 子ノード数 = 0 または 1\n if (cur.left == null || cur.right == null) {\n // 子ノード数 = 0 / 1 の場合、child = null / その子ノード\n TreeNode? child = cur.left ?? cur.right;\n // ノード cur を削除する\n if (cur != root) {\n if (pre!.left == cur)\n pre.left = child;\n else\n pre.right = child;\n } else {\n // 削除するノードが根ノードの場合、根ノードを指定し直す\n root = child;\n }\n }\n // 子ノード数 = 2\n else {\n // 通りがけ順で cur の次のノードを取得する\n TreeNode? tmp = cur.right;\n while (tmp.left != null) {\n tmp = tmp.left;\n }\n // ノード tmp を再帰的に削除する\n Remove(tmp.val!.Value);\n // tmp で cur を上書きする\n cur.val = tmp.val;\n }\n}\n</code></pre> binary_search_tree.go<pre><code>/* ノードを削除する */\nfunc (bst *binarySearchTree) remove(num int) {\n cur := bst.root\n // 木が空の場合、そのまま早期リターンする\n if cur == nil {\n return\n }\n // 削除対象ノードの直前のノードの位置\n var pre *TreeNode = nil\n // 繰り返し探索し、葉ノードを越えたら終了する\n for cur != nil {\n if cur.Val == num {\n break\n }\n pre = cur\n if cur.Val.(int) &lt; num {\n // 削除対象のノードは右部分木にある\n cur = cur.Right\n } else {\n // 削除対象のノードは左部分木にある\n cur = cur.Left\n }\n }\n // 削除対象のノードが存在しない場合、そのまま戻る\n if cur == nil {\n return\n }\n // 子ノード数は 0 または 1\n if cur.Left == nil || cur.Right == nil {\n var child *TreeNode = nil\n // 削除対象のノードの子ノードを取り出す\n if cur.Left != nil {\n child = cur.Left\n } else {\n child = cur.Right\n }\n // ノード cur を削除する\n if cur != bst.root {\n if pre.Left == cur {\n pre.Left = child\n } else {\n pre.Right = child\n }\n } else {\n // 削除するノードが根ノードの場合、根ノードを指定し直す\n bst.root = child\n }\n // 子ノード数は 2\n } else {\n // 通りがけ順で削除対象ノード cur の次のノードを取得する\n tmp := cur.Right\n for tmp.Left != nil {\n tmp = tmp.Left\n }\n // ノード tmp を再帰的に削除する\n bst.remove(tmp.Val.(int))\n // tmp で cur を上書きする\n cur.Val = tmp.Val\n }\n}\n</code></pre> binary_search_tree.swift<pre><code>/* ノードを削除する */\nfunc remove(num: Int) {\n // 木が空の場合、そのまま早期リターンする\n if root == nil {\n return\n }\n var cur = root\n var pre: TreeNode?\n // 繰り返し探索し、葉ノードを越えたら終了する\n while cur != nil {\n // 削除対象のノードが見つかったので、ループを終了する\n if cur!.val == num {\n break\n }\n pre = cur\n // 削除対象のノードは cur の右部分木にある\n if cur!.val &lt; num {\n cur = cur?.right\n }\n // 削除対象のノードは cur の左部分木にある\n else {\n cur = cur?.left\n }\n }\n // 削除対象のノードが存在しない場合、そのまま戻る\n if cur == nil {\n return\n }\n // 子ノード数 = 0 または 1\n if cur?.left == nil || cur?.right == nil {\n // 子ノード数 = 0 / 1 の場合、child = null / その子ノード\n let child = cur?.left ?? cur?.right\n // ノード cur を削除する\n if cur !== root {\n if pre?.left === cur {\n pre?.left = child\n } else {\n pre?.right = child\n }\n } else {\n // 削除するノードが根ノードの場合、根ノードを指定し直す\n root = child\n }\n }\n // 子ノード数 = 2\n else {\n // 通りがけ順で cur の次のノードを取得する\n var tmp = cur?.right\n while tmp?.left != nil {\n tmp = tmp?.left\n }\n // ノード tmp を再帰的に削除する\n remove(num: tmp!.val)\n // tmp で cur を上書きする\n cur?.val = tmp!.val\n }\n}\n</code></pre> binary_search_tree.js<pre><code>/* ノードを削除する */\nremove(num) {\n // 木が空の場合、そのまま早期リターンする\n if (this.root === null) return;\n let cur = this.root,\n pre = null;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur !== null) {\n // 削除対象のノードが見つかったので、ループを終了する\n if (cur.val === num) break;\n pre = cur;\n // 削除対象のノードは cur の右部分木にある\n if (cur.val &lt; num) cur = cur.right;\n // 削除対象のノードは cur の左部分木にある\n else cur = cur.left;\n }\n // 削除対象のノードが存在しない場合、そのまま戻る\n if (cur === null) return;\n // 子ノード数 = 0 または 1\n if (cur.left === null || cur.right === null) {\n // 子ノード数 = 0 / 1 の場合、child = null / その子ノード\n const child = cur.left !== null ? cur.left : cur.right;\n // ノード cur を削除する\n if (cur !== this.root) {\n if (pre.left === cur) pre.left = child;\n else pre.right = child;\n } else {\n // 削除するノードが根ノードの場合、根ノードを指定し直す\n this.root = child;\n }\n }\n // 子ノード数 = 2\n else {\n // 通りがけ順で cur の次のノードを取得する\n let tmp = cur.right;\n while (tmp.left !== null) {\n tmp = tmp.left;\n }\n // ノード tmp を再帰的に削除する\n this.remove(tmp.val);\n // tmp で cur を上書きする\n cur.val = tmp.val;\n }\n}\n</code></pre> binary_search_tree.ts<pre><code>/* ノードを削除する */\nremove(num: number): void {\n // 木が空の場合、そのまま早期リターンする\n if (this.root === null) return;\n let cur: TreeNode | null = this.root,\n pre: TreeNode | null = null;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur !== null) {\n // 削除対象のノードが見つかったので、ループを終了する\n if (cur.val === num) break;\n pre = cur;\n // 削除対象のノードは cur の右部分木にある\n if (cur.val &lt; num) cur = cur.right;\n // 削除対象のノードは cur の左部分木にある\n else cur = cur.left;\n }\n // 削除対象のノードが存在しない場合、そのまま戻る\n if (cur === null) return;\n // 子ノード数 = 0 または 1\n if (cur.left === null || cur.right === null) {\n // 子ノード数 = 0 / 1 の場合、child = null / その子ノード\n const child: TreeNode | null =\n cur.left !== null ? cur.left : cur.right;\n // ノード cur を削除する\n if (cur !== this.root) {\n if (pre!.left === cur) pre!.left = child;\n else pre!.right = child;\n } else {\n // 削除するノードが根ノードの場合、根ノードを指定し直す\n this.root = child;\n }\n }\n // 子ノード数 = 2\n else {\n // 通りがけ順で cur の次のノードを取得する\n let tmp: TreeNode | null = cur.right;\n while (tmp!.left !== null) {\n tmp = tmp!.left;\n }\n // ノード tmp を再帰的に削除する\n this.remove(tmp!.val);\n // tmp で cur を上書きする\n cur.val = tmp!.val;\n }\n}\n</code></pre> binary_search_tree.dart<pre><code>/* ノードを削除する */\nvoid remove(int _num) {\n // 木が空の場合、そのまま早期リターンする\n if (_root == null) return;\n TreeNode? cur = _root;\n TreeNode? pre = null;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 削除対象のノードが見つかったので、ループを終了する\n if (cur.val == _num) break;\n pre = cur;\n // 削除対象のノードは cur の右部分木にある\n if (cur.val &lt; _num)\n cur = cur.right;\n // 削除対象のノードは cur の左部分木にある\n else\n cur = cur.left;\n }\n // 削除対象のノードが存在しない場合、そのまま戻る\n if (cur == null) return;\n // 子ノード数 = 0 または 1\n if (cur.left == null || cur.right == null) {\n // 子ノード数 = 0 / 1 の場合、child = null / その子ノード\n TreeNode? child = cur.left ?? cur.right;\n // ノード cur を削除する\n if (cur != _root) {\n if (pre!.left == cur)\n pre.left = child;\n else\n pre.right = child;\n } else {\n // 削除するノードが根ノードの場合、根ノードを指定し直す\n _root = child;\n }\n } else {\n // 子ノード数 = 2\n // 通りがけ順で cur の次のノードを取得する\n TreeNode? tmp = cur.right;\n while (tmp!.left != null) {\n tmp = tmp.left;\n }\n // ノード tmp を再帰的に削除する\n remove(tmp.val);\n // tmp で cur を上書きする\n cur.val = tmp.val;\n }\n}\n</code></pre> binary_search_tree.rs<pre><code>/* ノードを削除する */\npub fn remove(&amp;mut self, num: i32) {\n // 木が空の場合、そのまま早期リターンする\n if self.root.is_none() {\n return;\n }\n let mut cur = self.root.clone();\n let mut pre = None;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while let Some(node) = cur.clone() {\n match num.cmp(&amp;node.borrow().val) {\n // 削除対象のノードが見つかったので、ループを終了する\n Ordering::Equal =&gt; break,\n // 削除対象のノードは cur の右部分木にある\n Ordering::Greater =&gt; {\n pre = cur.clone();\n cur = node.borrow().right.clone();\n }\n // 削除対象のノードは cur の左部分木にある\n Ordering::Less =&gt; {\n pre = cur.clone();\n cur = node.borrow().left.clone();\n }\n }\n }\n // 削除対象のノードが存在しない場合、そのまま戻る\n if cur.is_none() {\n return;\n }\n let cur = cur.unwrap();\n let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone());\n match (left_child.clone(), right_child.clone()) {\n // 子ノード数 = 0 または 1\n (None, None) | (Some(_), None) | (None, Some(_)) =&gt; {\n // 子ノード数 = 0 / 1 の場合、child = nullptr / その子ノード\n let child = left_child.or(right_child);\n let pre = pre.unwrap();\n // ノード cur を削除する\n if !Rc::ptr_eq(&amp;cur, self.root.as_ref().unwrap()) {\n let left = pre.borrow().left.clone();\n if left.is_some() &amp;&amp; Rc::ptr_eq(left.as_ref().unwrap(), &amp;cur) {\n pre.borrow_mut().left = child;\n } else {\n pre.borrow_mut().right = child;\n }\n } else {\n // 削除するノードが根ノードの場合、根ノードを指定し直す\n self.root = child;\n }\n }\n // 子ノード数 = 2\n (Some(_), Some(_)) =&gt; {\n // 通りがけ順で cur の次のノードを取得する\n let mut tmp = cur.borrow().right.clone();\n while let Some(node) = tmp.clone() {\n if node.borrow().left.is_some() {\n tmp = node.borrow().left.clone();\n } else {\n break;\n }\n }\n let tmp_val = tmp.unwrap().borrow().val;\n // ノード tmp を再帰的に削除する\n self.remove(tmp_val);\n // tmp で cur を上書きする\n cur.borrow_mut().val = tmp_val;\n }\n }\n}\n</code></pre> binary_search_tree.c<pre><code>/* ノードを削除する */\n// stdio.h を読み込んでいるため、ここでは remove という名前は使えない\nvoid removeItem(BinarySearchTree *bst, int num) {\n // 木が空の場合、そのまま早期リターンする\n if (bst-&gt;root == NULL)\n return;\n TreeNode *cur = bst-&gt;root, *pre = NULL;\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != NULL) {\n // 削除対象のノードが見つかったので、ループを終了する\n if (cur-&gt;val == num)\n break;\n pre = cur;\n if (cur-&gt;val &lt; num) {\n // 削除対象のノードは root の右部分木にある\n cur = cur-&gt;right;\n } else {\n // 削除対象のノードは root の左部分木にある\n cur = cur-&gt;left;\n }\n }\n // 削除対象のノードが存在しない場合、そのまま戻る\n if (cur == NULL)\n return;\n // 削除対象のノードに子ノードがあるかどうかを判定する\n if (cur-&gt;left == NULL || cur-&gt;right == NULL) {\n /* 子ノード数 = 0 または 1 */\n // 子ノード数 = 0 / 1 の場合、child = nullptr / その子ノード\n TreeNode *child = cur-&gt;left != NULL ? cur-&gt;left : cur-&gt;right;\n // ノード cur を削除する\n if (pre-&gt;left == cur) {\n pre-&gt;left = child;\n } else {\n pre-&gt;right = child;\n }\n // メモリを解放する\n free(cur);\n } else {\n /* 子ノード数 = 2 */\n // 通りがけ順で cur の次のノードを取得する\n TreeNode *tmp = cur-&gt;right;\n while (tmp-&gt;left != NULL) {\n tmp = tmp-&gt;left;\n }\n int tmpVal = tmp-&gt;val;\n // ノード tmp を再帰的に削除する\n removeItem(bst, tmp-&gt;val);\n // tmp で cur を上書きする\n cur-&gt;val = tmpVal;\n }\n}\n</code></pre> binary_search_tree.kt<pre><code>/* ノードを削除する */\nfun remove(num: Int) {\n // 木が空の場合、そのまま早期リターンする\n if (root == null)\n return\n var cur = root\n var pre: TreeNode? = null\n // 繰り返し探索し、葉ノードを越えたら終了する\n while (cur != null) {\n // 削除対象のノードが見つかったので、ループを終了する\n if (cur._val == num)\n break\n pre = cur\n // 削除対象のノードは cur の右部分木にある\n cur = if (cur._val &lt; num)\n cur.right\n // 削除対象のノードは cur の左部分木にある\n else\n cur.left\n }\n // 削除対象のノードが存在しない場合、そのまま戻る\n if (cur == null)\n return\n // 子ノード数 = 0 または 1\n if (cur.left == null || cur.right == null) {\n // 子ノード数 = 0 / 1 の場合、child = null / その子ノード\n val child = if (cur.left != null)\n cur.left\n else\n cur.right\n // ノード cur を削除する\n if (cur != root) {\n if (pre!!.left == cur)\n pre.left = child\n else\n pre.right = child\n } else {\n // 削除するノードが根ノードの場合、根ノードを指定し直す\n root = child\n }\n // 子ノード数 = 2\n } else {\n // 通りがけ順で cur の次のノードを取得する\n var tmp = cur.right\n while (tmp!!.left != null) {\n tmp = tmp.left\n }\n // ノード tmp を再帰的に削除する\n remove(tmp._val)\n // tmp で cur を上書きする\n cur._val = tmp._val\n }\n}\n</code></pre> binary_search_tree.rb<pre><code>### ノードを削除する ###\ndef remove(num)\n # 木が空の場合、そのまま早期リターンする\n return if @root.nil?\n\n # 繰り返し探索し、葉ノードを越えたら終了する\n cur, pre = @root, nil\n while !cur.nil?\n # 削除対象のノードが見つかったので、ループを終了する\n break if cur.val == num\n\n pre = cur\n # 削除対象のノードは cur の右部分木にある\n if cur.val &lt; num\n cur = cur.right\n # 削除対象のノードは cur の左部分木にある\n else\n cur = cur.left\n end\n end\n # 削除対象のノードが存在しない場合、そのまま戻る\n return if cur.nil?\n\n # 子ノード数 = 0 または 1\n if cur.left.nil? || cur.right.nil?\n # 子ノード数 = 0 / 1 の場合、child = null / その子ノード\n child = cur.left || cur.right\n # ノード cur を削除する\n if cur != @root\n if pre.left == cur\n pre.left = child\n else\n pre.right = child\n end\n else\n # 削除するノードが根ノードの場合、根ノードを指定し直す\n @root = child\n end\n # 子ノード数 = 2\n else\n # 通りがけ順で cur の次のノードを取得する\n tmp = cur.right\n while !tmp.left.nil?\n tmp = tmp.left\n end\n # ノード tmp を再帰的に削除する\n remove(tmp.val)\n # tmp で cur を上書きする\n cur.val = tmp.val\n end\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 7 章   木","7.4   二分探索木"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#4","level":3,"title":"4.   通りがけ順では整列済みになる","text":"<p>図 7-22 のように、通りがけ順走査は「左 \\(\\rightarrow\\) 根 \\(\\rightarrow\\) 右」の順で進み、二分探索木では「左の子 \\(&lt;\\) 根 \\(&lt;\\) 右の子」の大小関係が成り立ちます。</p> <p>そのため、通りがけ順では常に次に小さいノードを優先して訪問します。ここから、二分探索木の通りがけ順走査の列は昇順になるという重要な性質が得られます。</p> <p>この性質を利用すれば、追加のソートなしに \\(O(n)\\) 時間で整列済みのデータを取得でき、非常に効率的です。</p> <p></p> <p> 図 7-22   二分探索木の通りがけ順走査の列 </p>","path":["第 7 章   木","7.4   二分探索木"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#742","level":2,"title":"7.4.2   二分探索木の効率","text":"<p>データを配列または二分探索木で格納する場合を考えます。表 7-2 では、二分探索木の各操作の時間計算量はすべて対数時間で、安定した高い性能を示します。配列のほうが効率的なのは、追加が多く、探索と削除が少ない場合だけです。</p> <p> 表 7-2   配列と探索木の効率の比較 </p> 未整列配列 二分探索木 要素の探索 \\(O(n)\\) \\(O(\\log n)\\) 要素の挿入 \\(O(1)\\) \\(O(\\log n)\\) 要素の削除 \\(O(n)\\) \\(O(\\log n)\\) <p>理想的には二分探索木は平衡であり、どのノードも \\(\\log n\\) 回の反復で見つけられます。</p> <p>ただし、挿入と削除を続けると、図 7-23 のように連結リストへ退化する可能性があります。その場合、各操作の時間計算量も \\(O(n)\\) に悪化します。</p> <p></p> <p> 図 7-23   二分探索木の退化 </p>","path":["第 7 章   木","7.4   二分探索木"],"tags":[]},{"location":"chapter_tree/binary_search_tree/#743","level":2,"title":"7.4.3   二分探索木の主な用途","text":"<ul> <li>システムの多段階のインデックスとして、効率的な探索、挿入、削除を実現します。</li> <li>一部の探索アルゴリズムの基盤となるデータ構造として使います。</li> <li>データストリームを格納し、整列状態を保ちます。</li> </ul>","path":["第 7 章   木","7.4   二分探索木"],"tags":[]},{"location":"chapter_tree/binary_tree/","level":1,"title":"7.1   二分木","text":"<p>二分木(binary tree)は、「祖先」と「子孫」の派生関係を表す非線形データ構造で、「一つを二つに分ける」という分割統治の論理を体現しています。連結リストと同様、基本単位はノードで、各ノードには値、左の子への参照、右の子への参照が含まれます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby <pre><code>class TreeNode:\n \"\"\"二分木のノードクラス\"\"\"\n def __init__(self, val: int):\n self.val: int = val # ノードの値\n self.left: TreeNode | None = None # 左の子への参照\n self.right: TreeNode | None = None # 右の子への参照\n</code></pre> <pre><code>/* 二分木のノード構造体 */\nstruct TreeNode {\n int val; // ノードの値\n TreeNode *left; // 左の子へのポインタ\n TreeNode *right; // 右の子へのポインタ\n TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}\n};\n</code></pre> <pre><code>/* 二分木のノードクラス */\nclass TreeNode {\n int val; // ノードの値\n TreeNode left; // 左の子への参照\n TreeNode right; // 右の子への参照\n TreeNode(int x) { val = x; }\n}\n</code></pre> <pre><code>/* 二分木のノードクラス */\nclass TreeNode(int? x) {\n public int? val = x; // ノードの値\n public TreeNode? left; // 左の子への参照\n public TreeNode? right; // 右の子への参照\n}\n</code></pre> <pre><code>/* 二分木のノード構造体 */\ntype TreeNode struct {\n Val int\n Left *TreeNode\n Right *TreeNode\n}\n/* コンストラクタ */\nfunc NewTreeNode(v int) *TreeNode {\n return &amp;TreeNode{\n Left: nil, // 左の子へのポインタ\n Right: nil, // 右の子へのポインタ\n Val: v, // ノードの値\n }\n}\n</code></pre> <pre><code>/* 二分木のノードクラス */\nclass TreeNode {\n var val: Int // ノードの値\n var left: TreeNode? // 左の子への参照\n var right: TreeNode? // 右の子への参照\n\n init(x: Int) {\n val = x\n }\n}\n</code></pre> <pre><code>/* 二分木のノードクラス */\nclass TreeNode {\n val; // ノードの値\n left; // 左の子へのポインタ\n right; // 右の子へのポインタ\n constructor(val, left, right) {\n this.val = val === undefined ? 0 : val;\n this.left = left === undefined ? null : left;\n this.right = right === undefined ? null : right;\n }\n}\n</code></pre> <pre><code>/* 二分木のノードクラス */\nclass TreeNode {\n val: number;\n left: TreeNode | null;\n right: TreeNode | null;\n\n constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {\n this.val = val === undefined ? 0 : val; // ノードの値\n this.left = left === undefined ? null : left; // 左の子への参照\n this.right = right === undefined ? null : right; // 右の子への参照\n }\n}\n</code></pre> <pre><code>/* 二分木のノードクラス */\nclass TreeNode {\n int val; // ノードの値\n TreeNode? left; // 左の子への参照\n TreeNode? right; // 右の子への参照\n TreeNode(this.val, [this.left, this.right]);\n}\n</code></pre> <pre><code>use std::rc::Rc;\nuse std::cell::RefCell;\n\n/* 二分木のノード構造体 */\nstruct TreeNode {\n val: i32, // ノードの値\n left: Option&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;, // 左の子への参照\n right: Option&lt;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;, // 右の子への参照\n}\n\nimpl TreeNode {\n /* コンストラクタ */\n fn new(val: i32) -&gt; Rc&lt;RefCell&lt;Self&gt;&gt; {\n Rc::new(RefCell::new(Self {\n val,\n left: None,\n right: None\n }))\n }\n}\n</code></pre> <pre><code>/* 二分木のノード構造体 */\ntypedef struct TreeNode {\n int val; // ノードの値\n int height; // ノードの高さ\n struct TreeNode *left; // 左の子へのポインタ\n struct TreeNode *right; // 右の子へのポインタ\n} TreeNode;\n\n/* コンストラクタ */\nTreeNode *newTreeNode(int val) {\n TreeNode *node;\n\n node = (TreeNode *)malloc(sizeof(TreeNode));\n node-&gt;val = val;\n node-&gt;height = 0;\n node-&gt;left = NULL;\n node-&gt;right = NULL;\n return node;\n}\n</code></pre> <pre><code>/* 二分木のノードクラス */\nclass TreeNode(val _val: Int) { // ノードの値\n var left: TreeNode? = null // 左の子への参照\n var right: TreeNode? = null // 右の子への参照\n}\n</code></pre> <pre><code>### 二分木のノードクラス ###\nclass TreeNode\n attr_accessor :val # ノードの値\n attr_accessor :left # 左の子への参照\n attr_accessor :right # 右の子への参照\n\n def initialize(val)\n @val = val\n end\nend\n</code></pre> <p>各ノードは二つの参照(ポインタ)を持ち、それぞれ左の子ノード(left-child node)と右の子ノード(right-child node)を指します。そのノードは、二つの子の親ノード(parent node)と呼ばれます。あるノードの左の子とその下のノードが作る木を左部分木(left subtree)と呼び、同様に右部分木(right subtree)も定義できます。</p> <p>二分木では、葉以外のすべてのノードが、子ノードと空でない部分木を持ちます。図 7-1 で「ノード 2」を親とすると、左右の子は「ノード 4」と「ノード 5」です。左部分木はノード 4 とその下のノードが作る木、右部分木はノード 5 とその下のノードが作る木です。</p> <p></p> <p> 図 7-1   親ノード、子ノード、部分木 </p>","path":["第 7 章   木","7.1   二分木"],"tags":[]},{"location":"chapter_tree/binary_tree/#711","level":2,"title":"7.1.1   二分木の主な用語","text":"<p>主な用語を図 7-2 に示します。</p> <ul> <li>根ノード(root node):最上層にある、親を持たないノードです。</li> <li>葉ノード(leaf node):子を持たず、二つのポインタがどちらも <code>None</code> を指すノードです。</li> <li>辺(edge):二つのノードを結ぶ線で、ノードの参照(ポインタ)です。</li> <li>ノードのレベル(level):上から下へ増え、根のレベルは 1 です。</li> <li>ノードの次数(degree):子ノードの数です。二分木では 0、1、2 のいずれかです。</li> <li>二分木の高さ(height):根から最も遠い葉までに通る辺の数です。</li> <li>ノードの深さ(depth):根からそのノードまでに通る辺の数です。</li> <li>ノードの高さ(height):そのノードから最も遠い葉と、そのノードとの間にある辺の数です。</li> </ul> <p></p> <p> 図 7-2   二分木の主な用語 </p> <p>ヒント</p> <p>通常、高さと深さは通る辺の数で定義しますが、問題や教科書によっては通るノードの数で定義する場合もあります。その場合は、高さと深さの両方に 1 を加えます。</p>","path":["第 7 章   木","7.1   二分木"],"tags":[]},{"location":"chapter_tree/binary_tree/#712","level":2,"title":"7.1.2   二分木の基本操作","text":"","path":["第 7 章   木","7.1   二分木"],"tags":[]},{"location":"chapter_tree/binary_tree/#1","level":3,"title":"1.   二分木の初期化","text":"<p>連結リストと同じく、まずノードを初期化し、次に参照(ポインタ)をつなぎます。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree.py<pre><code># 二分木を初期化\n# ノードを初期化\nn1 = TreeNode(val=1)\nn2 = TreeNode(val=2)\nn3 = TreeNode(val=3)\nn4 = TreeNode(val=4)\nn5 = TreeNode(val=5)\n# ノード間の参照(ポインタ)をつなぐ\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n</code></pre> binary_tree.cpp<pre><code>/* 二分木を初期化 */\n// ノードを初期化\nTreeNode* n1 = new TreeNode(1);\nTreeNode* n2 = new TreeNode(2);\nTreeNode* n3 = new TreeNode(3);\nTreeNode* n4 = new TreeNode(4);\nTreeNode* n5 = new TreeNode(5);\n// ノード間の参照(ポインタ)をつなぐ\nn1-&gt;left = n2;\nn1-&gt;right = n3;\nn2-&gt;left = n4;\nn2-&gt;right = n5;\n</code></pre> binary_tree.java<pre><code>// ノードを初期化\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// ノード間の参照(ポインタ)をつなぐ\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n</code></pre> binary_tree.cs<pre><code>/* 二分木を初期化 */\n// ノードを初期化\nTreeNode n1 = new(1);\nTreeNode n2 = new(2);\nTreeNode n3 = new(3);\nTreeNode n4 = new(4);\nTreeNode n5 = new(5);\n// ノード間の参照(ポインタ)をつなぐ\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n</code></pre> binary_tree.go<pre><code>/* 二分木を初期化 */\n// ノードを初期化\nn1 := NewTreeNode(1)\nn2 := NewTreeNode(2)\nn3 := NewTreeNode(3)\nn4 := NewTreeNode(4)\nn5 := NewTreeNode(5)\n// ノード間の参照(ポインタ)をつなぐ\nn1.Left = n2\nn1.Right = n3\nn2.Left = n4\nn2.Right = n5\n</code></pre> binary_tree.swift<pre><code>// ノードを初期化\nlet n1 = TreeNode(x: 1)\nlet n2 = TreeNode(x: 2)\nlet n3 = TreeNode(x: 3)\nlet n4 = TreeNode(x: 4)\nlet n5 = TreeNode(x: 5)\n// ノード間の参照(ポインタ)をつなぐ\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n</code></pre> binary_tree.js<pre><code>/* 二分木を初期化 */\n// ノードを初期化\nlet n1 = new TreeNode(1),\n n2 = new TreeNode(2),\n n3 = new TreeNode(3),\n n4 = new TreeNode(4),\n n5 = new TreeNode(5);\n// ノード間の参照(ポインタ)をつなぐ\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n</code></pre> binary_tree.ts<pre><code>/* 二分木を初期化 */\n// ノードを初期化\nlet n1 = new TreeNode(1),\n n2 = new TreeNode(2),\n n3 = new TreeNode(3),\n n4 = new TreeNode(4),\n n5 = new TreeNode(5);\n// ノード間の参照(ポインタ)をつなぐ\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n</code></pre> binary_tree.dart<pre><code>/* 二分木を初期化 */\n// ノードを初期化\nTreeNode n1 = new TreeNode(1);\nTreeNode n2 = new TreeNode(2);\nTreeNode n3 = new TreeNode(3);\nTreeNode n4 = new TreeNode(4);\nTreeNode n5 = new TreeNode(5);\n// ノード間の参照(ポインタ)をつなぐ\nn1.left = n2;\nn1.right = n3;\nn2.left = n4;\nn2.right = n5;\n</code></pre> binary_tree.rs<pre><code>// ノードを初期化\nlet n1 = TreeNode::new(1);\nlet n2 = TreeNode::new(2);\nlet n3 = TreeNode::new(3);\nlet n4 = TreeNode::new(4);\nlet n5 = TreeNode::new(5);\n// ノード間の参照(ポインタ)をつなぐ\nn1.borrow_mut().left = Some(n2.clone());\nn1.borrow_mut().right = Some(n3);\nn2.borrow_mut().left = Some(n4);\nn2.borrow_mut().right = Some(n5);\n</code></pre> binary_tree.c<pre><code>/* 二分木を初期化 */\n// ノードを初期化\nTreeNode *n1 = newTreeNode(1);\nTreeNode *n2 = newTreeNode(2);\nTreeNode *n3 = newTreeNode(3);\nTreeNode *n4 = newTreeNode(4);\nTreeNode *n5 = newTreeNode(5);\n// ノード間の参照(ポインタ)をつなぐ\nn1-&gt;left = n2;\nn1-&gt;right = n3;\nn2-&gt;left = n4;\nn2-&gt;right = n5;\n</code></pre> binary_tree.kt<pre><code>// ノードを初期化\nval n1 = TreeNode(1)\nval n2 = TreeNode(2)\nval n3 = TreeNode(3)\nval n4 = TreeNode(4)\nval n5 = TreeNode(5)\n// ノード間の参照(ポインタ)をつなぐ\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n</code></pre> binary_tree.rb<pre><code># 二分木を初期化\n# ノードを初期化\nn1 = TreeNode.new(1)\nn2 = TreeNode.new(2)\nn3 = TreeNode.new(3)\nn4 = TreeNode.new(4)\nn5 = TreeNode.new(5)\n# ノード間の参照(ポインタ)をつなぐ\nn1.left = n2\nn1.right = n3\nn2.left = n4\nn2.right = n5\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 7 章   木","7.1   二分木"],"tags":[]},{"location":"chapter_tree/binary_tree/#2","level":3,"title":"2.   ノードの挿入と削除","text":"<p>連結リストと同様に、ポインタを変更してノードを挿入・削除できます。図 7-3 に例を示します。</p> <p></p> <p> 図 7-3   二分木でのノードの挿入と削除 </p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree.py<pre><code># ノードの挿入と削除\np = TreeNode(0)\n# n1 -&gt; n2 の間にノード P を挿入\nn1.left = p\np.left = n2\n# ノード P を削除\nn1.left = n2\n</code></pre> binary_tree.cpp<pre><code>/* ノードの挿入と削除 */\nTreeNode* P = new TreeNode(0);\n// n1 -&gt; n2 の間にノード P を挿入\nn1-&gt;left = P;\nP-&gt;left = n2;\n// ノード P を削除\nn1-&gt;left = n2;\n// メモリを解放\ndelete P;\n</code></pre> binary_tree.java<pre><code>TreeNode P = new TreeNode(0);\n// n1 -&gt; n2 の間にノード P を挿入\nn1.left = P;\nP.left = n2;\n// ノード P を削除\nn1.left = n2;\n</code></pre> binary_tree.cs<pre><code>/* ノードの挿入と削除 */\nTreeNode P = new(0);\n// n1 -&gt; n2 の間にノード P を挿入\nn1.left = P;\nP.left = n2;\n// ノード P を削除\nn1.left = n2;\n</code></pre> binary_tree.go<pre><code>/* ノードの挿入と削除 */\n// n1 -&gt; n2 の間にノード P を挿入\np := NewTreeNode(0)\nn1.Left = p\np.Left = n2\n// ノード P を削除\nn1.Left = n2\n</code></pre> binary_tree.swift<pre><code>let P = TreeNode(x: 0)\n// n1 -&gt; n2 の間にノード P を挿入\nn1.left = P\nP.left = n2\n// ノード P を削除\nn1.left = n2\n</code></pre> binary_tree.js<pre><code>/* ノードの挿入と削除 */\nlet P = new TreeNode(0);\n// n1 -&gt; n2 の間にノード P を挿入\nn1.left = P;\nP.left = n2;\n// ノード P を削除\nn1.left = n2;\n</code></pre> binary_tree.ts<pre><code>/* ノードの挿入と削除 */\nconst P = new TreeNode(0);\n// n1 -&gt; n2 の間にノード P を挿入\nn1.left = P;\nP.left = n2;\n// ノード P を削除\nn1.left = n2;\n</code></pre> binary_tree.dart<pre><code>/* ノードの挿入と削除 */\nTreeNode P = new TreeNode(0);\n// n1 -&gt; n2 の間にノード P を挿入\nn1.left = P;\nP.left = n2;\n// ノード P を削除\nn1.left = n2;\n</code></pre> binary_tree.rs<pre><code>let p = TreeNode::new(0);\n// n1 -&gt; n2 の間にノード P を挿入\nn1.borrow_mut().left = Some(p.clone());\np.borrow_mut().left = Some(n2.clone());\n// ノード p を削除\nn1.borrow_mut().left = Some(n2);\n</code></pre> binary_tree.c<pre><code>/* ノードの挿入と削除 */\nTreeNode *P = newTreeNode(0);\n// n1 -&gt; n2 の間にノード P を挿入\nn1-&gt;left = P;\nP-&gt;left = n2;\n// ノード P を削除\nn1-&gt;left = n2;\n// メモリを解放\nfree(P);\n</code></pre> binary_tree.kt<pre><code>val P = TreeNode(0)\n// n1 -&gt; n2 の間にノード P を挿入\nn1.left = P\nP.left = n2\n// ノード P を削除\nn1.left = n2\n</code></pre> binary_tree.rb<pre><code># ノードの挿入と削除\n_p = TreeNode.new(0)\n# n1 -&gt; n2 の間にノード _p を挿入\nn1.left = _p\n_p.left = n2\n# ノードを削除\nn1.left = n2\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>ヒント</p> <p>挿入は元の論理構造を変える可能性があり、削除は通常、そのノードとすべての部分木の削除を意味します。そのため、実際に意味のある操作を実現するには、複数の操作を組み合わせて挿入や削除を行うのが一般的です。</p>","path":["第 7 章   木","7.1   二分木"],"tags":[]},{"location":"chapter_tree/binary_tree/#713","level":2,"title":"7.1.3   主な二分木の種類","text":"","path":["第 7 章   木","7.1   二分木"],"tags":[]},{"location":"chapter_tree/binary_tree/#1_1","level":3,"title":"1.   パーフェクト二分木","text":"<p>図 7-4 のパーフェクト二分木(perfect binary tree)は、すべてのレベルが完全に埋まっています。葉の次数は \\(0\\)、ほかのすべてのノードの次数は \\(2\\) です。高さが \\(h\\) なら、総ノード数は \\(2^{h+1} - 1\\) という典型的な指数関係になり、自然界の細胞分裂に似た様子を示します。</p> <p>ヒント</p> <p>中国語圏では、パーフェクト二分木は「满二叉树」ともよく呼ばれます。</p> <p></p> <p> 図 7-4   パーフェクト二分木 </p>","path":["第 7 章   木","7.1   二分木"],"tags":[]},{"location":"chapter_tree/binary_tree/#2_1","level":3,"title":"2.   完全二分木","text":"<p>図 7-5 の完全二分木(complete binary tree)では、最下層だけが完全に埋まっていなくてもよく、その層のノードは左から右へ連続して埋まっている必要があります。パーフェクト二分木も、完全二分木の一種です。</p> <p></p> <p> 図 7-5   完全二分木 </p>","path":["第 7 章   木","7.1   二分木"],"tags":[]},{"location":"chapter_tree/binary_tree/#3","level":3,"title":"3.   全二分木","text":"<p>図 7-6 の全二分木(full binary tree)では、葉以外のすべてのノードが二つの子を持ちます。</p> <p></p> <p> 図 7-6   全二分木 </p>","path":["第 7 章   木","7.1   二分木"],"tags":[]},{"location":"chapter_tree/binary_tree/#4","level":3,"title":"4.   平衡二分木","text":"<p>図 7-7 の平衡二分木(balanced binary tree)では、どのノードでも左右の部分木の高さの差の絶対値が 1 以下です。</p> <p></p> <p> 図 7-7   平衡二分木 </p>","path":["第 7 章   木","7.1   二分木"],"tags":[]},{"location":"chapter_tree/binary_tree/#714","level":2,"title":"7.1.4   二分木の退化","text":"<p>図 7-8 は理想的な構造と退化した構造です。各層が埋まるとパーフェクト二分木になり、すべてのノードが一方に偏ると連結リストに退化します。</p> <ul> <li>パーフェクト二分木は理想的な場合で、分割統治の利点を十分に発揮できます。</li> <li>連結リストはもう一方の極端な場合で、各操作が線形になり、時間計算量は \\(O(n)\\) に悪化します。</li> </ul> <p></p> <p> 図 7-8   二分木の最良と最悪の構造 </p> <p>表 7-1 のように、最良と最悪の構造では、葉の数、総ノード数、高さなどが最大値または最小値になります。</p> <p> 表 7-1   二分木の最良と最悪の構造 </p> パーフェクト二分木 連結リスト 第 \\(i\\) レベルのノード数 \\(2^{i-1}\\) \\(1\\) 高さ \\(h\\) の木の葉ノード数 \\(2^h\\) \\(1\\) 高さ \\(h\\) の木の総ノード数 \\(2^{h+1} - 1\\) \\(h + 1\\) 総ノード数 \\(n\\) の木の高さ \\(\\log_2 (n+1) - 1\\) \\(n - 1\\)","path":["第 7 章   木","7.1   二分木"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/","level":1,"title":"7.2   二分木の走査","text":"<p>物理的な構造として、木は連結リストに基づくデータ構造なので、ポインタでノードを一つずつ訪問します。ただし、木は非線形なので、連結リストの走査より複雑で、探索アルゴリズムを使う必要があります。</p> <p>主な方法には、レベル順、行きがけ順、通りがけ順、帰りがけ順などがあります。</p>","path":["第 7 章   木","7.2   二分木の走査"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#721","level":2,"title":"7.2.1   レベル順走査","text":"<p>図 7-9 のレベル順走査(level-order traversal)は、上から下へ層ごとに進み、各層では左から右へノードを訪問します。</p> <p>これは本質的に幅優先走査(breadth-first traversal)であり、幅優先探索(breadth-first search, BFS)とも呼びます。一周ずつ外へ広がるように、層ごとに走査する方法です。</p> <p></p> <p> 図 7-9   二分木のレベル順走査 </p>","path":["第 7 章   木","7.2   二分木の走査"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#1","level":3,"title":"1.   コードの実装","text":"<p>幅優先走査は通常、キューで実装します。キューの「先入れ先出し」と、幅優先走査の「層ごとに進む」という規則は、同じ考え方に基づいています。実装は次のとおりです。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree_bfs.py<pre><code>def level_order(root: TreeNode | None) -&gt; list[int]:\n \"\"\"レベル順走査\"\"\"\n # キューを初期化し、根ノードを追加する\n queue: deque[TreeNode] = deque()\n queue.append(root)\n # 走査順序を保存するリストを初期化する\n res = []\n while queue:\n node: TreeNode = queue.popleft() # キューからデキューする\n res.append(node.val) # ノードの値を保存する\n if node.left is not None:\n queue.append(node.left) # 左の子ノードをエンキューする\n if node.right is not None:\n queue.append(node.right) # 右の子ノードをエンキューする\n return res\n</code></pre> binary_tree_bfs.cpp<pre><code>/* レベル順走査 */\nvector&lt;int&gt; levelOrder(TreeNode *root) {\n // キューを初期化し、根ノードを追加する\n queue&lt;TreeNode *&gt; queue;\n queue.push(root);\n // 走査順序を保存するリストを初期化する\n vector&lt;int&gt; vec;\n while (!queue.empty()) {\n TreeNode *node = queue.front();\n queue.pop(); // キューからデキューする\n vec.push_back(node-&gt;val); // ノードの値を保存する\n if (node-&gt;left != nullptr)\n queue.push(node-&gt;left); // 左の子ノードをエンキューする\n if (node-&gt;right != nullptr)\n queue.push(node-&gt;right); // 右の子ノードをエンキューする\n }\n return vec;\n}\n</code></pre> binary_tree_bfs.java<pre><code>/* レベル順走査 */\nList&lt;Integer&gt; levelOrder(TreeNode root) {\n // キューを初期化し、根ノードを追加する\n Queue&lt;TreeNode&gt; queue = new LinkedList&lt;&gt;();\n queue.add(root);\n // 走査順序を保存するリストを初期化する\n List&lt;Integer&gt; list = new ArrayList&lt;&gt;();\n while (!queue.isEmpty()) {\n TreeNode node = queue.poll(); // キューからデキューする\n list.add(node.val); // ノードの値を保存する\n if (node.left != null)\n queue.offer(node.left); // 左の子ノードをエンキューする\n if (node.right != null)\n queue.offer(node.right); // 右の子ノードをエンキューする\n }\n return list;\n}\n</code></pre> binary_tree_bfs.cs<pre><code>/* レベル順走査 */\nList&lt;int&gt; LevelOrder(TreeNode root) {\n // キューを初期化し、根ノードを追加する\n Queue&lt;TreeNode&gt; queue = new();\n queue.Enqueue(root);\n // 走査順序を保存するリストを初期化する\n List&lt;int&gt; list = [];\n while (queue.Count != 0) {\n TreeNode node = queue.Dequeue(); // キューからデキューする\n list.Add(node.val!.Value); // ノードの値を保存する\n if (node.left != null)\n queue.Enqueue(node.left); // 左の子ノードをエンキューする\n if (node.right != null)\n queue.Enqueue(node.right); // 右の子ノードをエンキューする\n }\n return list;\n}\n</code></pre> binary_tree_bfs.go<pre><code>/* レベル順走査 */\nfunc levelOrder(root *TreeNode) []any {\n // キューを初期化し、根ノードを追加する\n queue := list.New()\n queue.PushBack(root)\n // 走査順序を保存するスライスを初期化する\n nums := make([]any, 0)\n for queue.Len() &gt; 0 {\n // キューからデキューする\n node := queue.Remove(queue.Front()).(*TreeNode)\n // ノードの値を保存する\n nums = append(nums, node.Val)\n if node.Left != nil {\n // 左の子ノードをエンキューする\n queue.PushBack(node.Left)\n }\n if node.Right != nil {\n // 右の子ノードをエンキューする\n queue.PushBack(node.Right)\n }\n }\n return nums\n}\n</code></pre> binary_tree_bfs.swift<pre><code>/* レベル順走査 */\nfunc levelOrder(root: TreeNode) -&gt; [Int] {\n // キューを初期化し、根ノードを追加する\n var queue: [TreeNode] = [root]\n // 走査順序を保存するリストを初期化する\n var list: [Int] = []\n while !queue.isEmpty {\n let node = queue.removeFirst() // キューからデキューする\n list.append(node.val) // ノードの値を保存する\n if let left = node.left {\n queue.append(left) // 左の子ノードをエンキューする\n }\n if let right = node.right {\n queue.append(right) // 右の子ノードをエンキューする\n }\n }\n return list\n}\n</code></pre> binary_tree_bfs.js<pre><code>/* レベル順走査 */\nfunction levelOrder(root) {\n // キューを初期化し、根ノードを追加する\n const queue = [root];\n // 走査順序を保存するリストを初期化する\n const list = [];\n while (queue.length) {\n let node = queue.shift(); // キューからデキューする\n list.push(node.val); // ノードの値を保存する\n if (node.left) queue.push(node.left); // 左の子ノードをエンキューする\n if (node.right) queue.push(node.right); // 右の子ノードをエンキューする\n }\n return list;\n}\n</code></pre> binary_tree_bfs.ts<pre><code>/* レベル順走査 */\nfunction levelOrder(root: TreeNode | null): number[] {\n // キューを初期化し、根ノードを追加する\n const queue = [root];\n // 走査順序を保存するリストを初期化する\n const list: number[] = [];\n while (queue.length) {\n let node = queue.shift() as TreeNode; // キューからデキューする\n list.push(node.val); // ノードの値を保存する\n if (node.left) {\n queue.push(node.left); // 左の子ノードをエンキューする\n }\n if (node.right) {\n queue.push(node.right); // 右の子ノードをエンキューする\n }\n }\n return list;\n}\n</code></pre> binary_tree_bfs.dart<pre><code>/* レベル順走査 */\nList&lt;int&gt; levelOrder(TreeNode? root) {\n // キューを初期化し、根ノードを追加する\n Queue&lt;TreeNode?&gt; queue = Queue();\n queue.add(root);\n // 走査順序を保存するリストを初期化する\n List&lt;int&gt; res = [];\n while (queue.isNotEmpty) {\n TreeNode? node = queue.removeFirst(); // キューからデキューする\n res.add(node!.val); // ノードの値を保存する\n if (node.left != null) queue.add(node.left); // 左の子ノードをエンキューする\n if (node.right != null) queue.add(node.right); // 右の子ノードをエンキューする\n }\n return res;\n}\n</code></pre> binary_tree_bfs.rs<pre><code>/* レベル順走査 */\nfn level_order(root: &amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;) -&gt; Vec&lt;i32&gt; {\n // キューを初期化し、根ノードを追加する\n let mut que = VecDeque::new();\n que.push_back(root.clone());\n // 走査順序を保存するリストを初期化する\n let mut vec = Vec::new();\n\n while let Some(node) = que.pop_front() {\n // キューからデキューする\n vec.push(node.borrow().val); // ノードの値を保存する\n if let Some(left) = node.borrow().left.as_ref() {\n que.push_back(left.clone()); // 左の子ノードをエンキューする\n }\n if let Some(right) = node.borrow().right.as_ref() {\n que.push_back(right.clone()); // 右の子ノードをエンキューする\n };\n }\n vec\n}\n</code></pre> binary_tree_bfs.c<pre><code>/* レベル順走査 */\nint *levelOrder(TreeNode *root, int *size) {\n /* 補助キュー */\n int front, rear;\n int index, *arr;\n TreeNode *node;\n TreeNode **queue;\n\n /* 補助キュー */\n queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE);\n // キューのポインタ\n front = 0, rear = 0;\n // 根ノードを追加する\n queue[rear++] = root;\n // 走査順序を保存するリストを初期化する\n /* 補助配列 */\n arr = (int *)malloc(sizeof(int) * MAX_SIZE);\n // 配列のポインタ\n index = 0;\n while (front &lt; rear) {\n // キューからデキューする\n node = queue[front++];\n // ノードの値を保存する\n arr[index++] = node-&gt;val;\n if (node-&gt;left != NULL) {\n // 左の子ノードをエンキューする\n queue[rear++] = node-&gt;left;\n }\n if (node-&gt;right != NULL) {\n // 右の子ノードをエンキューする\n queue[rear++] = node-&gt;right;\n }\n }\n // 配列の長さの値を更新する\n *size = index;\n arr = realloc(arr, sizeof(int) * (*size));\n\n // 補助配列の領域を解放する\n free(queue);\n return arr;\n}\n</code></pre> binary_tree_bfs.kt<pre><code>/* レベル順走査 */\nfun levelOrder(root: TreeNode?): MutableList&lt;Int&gt; {\n // キューを初期化し、根ノードを追加する\n val queue = LinkedList&lt;TreeNode?&gt;()\n queue.add(root)\n // 走査順序を保存するリストを初期化する\n val list = mutableListOf&lt;Int&gt;()\n while (queue.isNotEmpty()) {\n val node = queue.poll() // キューからデキューする\n list.add(node?._val!!) // ノードの値を保存する\n if (node.left != null)\n queue.offer(node.left) // 左の子ノードをエンキューする\n if (node.right != null)\n queue.offer(node.right) // 右の子ノードをエンキューする\n }\n return list\n}\n</code></pre> binary_tree_bfs.rb<pre><code>### レベル順走査 ###\ndef level_order(root)\n # キューを初期化し、根ノードを追加する\n queue = [root]\n # 走査順序を保存するリストを初期化する\n res = []\n while !queue.empty?\n node = queue.shift # キューからデキューする\n res &lt;&lt; node.val # ノードの値を保存する\n queue &lt;&lt; node.left unless node.left.nil? # 左の子ノードをエンキューする\n queue &lt;&lt; node.right unless node.right.nil? # 右の子ノードをエンキューする\n end\n res\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p>","path":["第 7 章   木","7.2   二分木の走査"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#2","level":3,"title":"2.   計算量の解析","text":"<ul> <li>時間計算量は \\(O(n)\\):全ノードを一回ずつ訪問するため、\\(O(n)\\) 時間です。\\(n\\) はノード数です。</li> <li>空間計算量は \\(O(n)\\):最悪の場合、つまりパーフェクト二分木では、最下層の走査直前に最大 \\((n + 1) / 2\\) 個のノードが同時にキューに入り、\\(O(n)\\) の領域を使います。</li> </ul>","path":["第 7 章   木","7.2   二分木の走査"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#722","level":2,"title":"7.2.2   行きがけ順・通りがけ順・帰りがけ順走査","text":"<p>これに対し、行きがけ順、通りがけ順、帰りがけ順は、いずれも深さ優先走査(depth-first traversal)です。深さ優先探索(depth-first search, DFS)とも呼び、行き止まりまで進んでから戻り、探索を続ける方法です。</p> <p>図 7-10 は二分木の深さ優先走査の仕組みです。木全体の外周を一周歩くようなものです。各ノードで三つの位置を通り、それぞれが行きがけ順、通りがけ順、帰りがけ順に対応します。</p> <p></p> <p> 図 7-10   二分探索木の行きがけ順・通りがけ順・帰りがけ順走査 </p>","path":["第 7 章   木","7.2   二分木の走査"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#1_1","level":3,"title":"1.   コードの実装","text":"<p>深さ優先探索は通常、再帰で実装します。</p> PythonC++JavaC#GoSwiftJSTSDartRustCKotlinRuby binary_tree_dfs.py<pre><code>def pre_order(root: TreeNode | None):\n \"\"\"行きがけ順走査\"\"\"\n if root is None:\n return\n # 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n res.append(root.val)\n pre_order(root=root.left)\n pre_order(root=root.right)\n\ndef in_order(root: TreeNode | None):\n \"\"\"通りがけ順走査\"\"\"\n if root is None:\n return\n # 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n in_order(root=root.left)\n res.append(root.val)\n in_order(root=root.right)\n\ndef post_order(root: TreeNode | None):\n \"\"\"帰りがけ順走査\"\"\"\n if root is None:\n return\n # 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n post_order(root=root.left)\n post_order(root=root.right)\n res.append(root.val)\n</code></pre> binary_tree_dfs.cpp<pre><code>/* 行きがけ順走査 */\nvoid preOrder(TreeNode *root) {\n if (root == nullptr)\n return;\n // 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n vec.push_back(root-&gt;val);\n preOrder(root-&gt;left);\n preOrder(root-&gt;right);\n}\n\n/* 通りがけ順走査 */\nvoid inOrder(TreeNode *root) {\n if (root == nullptr)\n return;\n // 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n inOrder(root-&gt;left);\n vec.push_back(root-&gt;val);\n inOrder(root-&gt;right);\n}\n\n/* 帰りがけ順走査 */\nvoid postOrder(TreeNode *root) {\n if (root == nullptr)\n return;\n // 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n postOrder(root-&gt;left);\n postOrder(root-&gt;right);\n vec.push_back(root-&gt;val);\n}\n</code></pre> binary_tree_dfs.java<pre><code>/* 行きがけ順走査 */\nvoid preOrder(TreeNode root) {\n if (root == null)\n return;\n // 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n list.add(root.val);\n preOrder(root.left);\n preOrder(root.right);\n}\n\n/* 通りがけ順走査 */\nvoid inOrder(TreeNode root) {\n if (root == null)\n return;\n // 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n inOrder(root.left);\n list.add(root.val);\n inOrder(root.right);\n}\n\n/* 帰りがけ順走査 */\nvoid postOrder(TreeNode root) {\n if (root == null)\n return;\n // 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n postOrder(root.left);\n postOrder(root.right);\n list.add(root.val);\n}\n</code></pre> binary_tree_dfs.cs<pre><code>/* 行きがけ順走査 */\nvoid PreOrder(TreeNode? root) {\n if (root == null) return;\n // 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n list.Add(root.val!.Value);\n PreOrder(root.left);\n PreOrder(root.right);\n}\n\n/* 通りがけ順走査 */\nvoid InOrder(TreeNode? root) {\n if (root == null) return;\n // 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n InOrder(root.left);\n list.Add(root.val!.Value);\n InOrder(root.right);\n}\n\n/* 帰りがけ順走査 */\nvoid PostOrder(TreeNode? root) {\n if (root == null) return;\n // 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n PostOrder(root.left);\n PostOrder(root.right);\n list.Add(root.val!.Value);\n}\n</code></pre> binary_tree_dfs.go<pre><code>/* 行きがけ順走査 */\nfunc preOrder(node *TreeNode) {\n if node == nil {\n return\n }\n // 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n nums = append(nums, node.Val)\n preOrder(node.Left)\n preOrder(node.Right)\n}\n\n/* 通りがけ順走査 */\nfunc inOrder(node *TreeNode) {\n if node == nil {\n return\n }\n // 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n inOrder(node.Left)\n nums = append(nums, node.Val)\n inOrder(node.Right)\n}\n\n/* 帰りがけ順走査 */\nfunc postOrder(node *TreeNode) {\n if node == nil {\n return\n }\n // 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n postOrder(node.Left)\n postOrder(node.Right)\n nums = append(nums, node.Val)\n}\n</code></pre> binary_tree_dfs.swift<pre><code>/* 行きがけ順走査 */\nfunc preOrder(root: TreeNode?) {\n guard let root = root else {\n return\n }\n // 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n list.append(root.val)\n preOrder(root: root.left)\n preOrder(root: root.right)\n}\n\n/* 通りがけ順走査 */\nfunc inOrder(root: TreeNode?) {\n guard let root = root else {\n return\n }\n // 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n inOrder(root: root.left)\n list.append(root.val)\n inOrder(root: root.right)\n}\n\n/* 帰りがけ順走査 */\nfunc postOrder(root: TreeNode?) {\n guard let root = root else {\n return\n }\n // 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n postOrder(root: root.left)\n postOrder(root: root.right)\n list.append(root.val)\n}\n</code></pre> binary_tree_dfs.js<pre><code>/* 行きがけ順走査 */\nfunction preOrder(root) {\n if (root === null) return;\n // 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n list.push(root.val);\n preOrder(root.left);\n preOrder(root.right);\n}\n\n/* 通りがけ順走査 */\nfunction inOrder(root) {\n if (root === null) return;\n // 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n inOrder(root.left);\n list.push(root.val);\n inOrder(root.right);\n}\n\n/* 帰りがけ順走査 */\nfunction postOrder(root) {\n if (root === null) return;\n // 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n postOrder(root.left);\n postOrder(root.right);\n list.push(root.val);\n}\n</code></pre> binary_tree_dfs.ts<pre><code>/* 行きがけ順走査 */\nfunction preOrder(root: TreeNode | null): void {\n if (root === null) {\n return;\n }\n // 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n list.push(root.val);\n preOrder(root.left);\n preOrder(root.right);\n}\n\n/* 通りがけ順走査 */\nfunction inOrder(root: TreeNode | null): void {\n if (root === null) {\n return;\n }\n // 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n inOrder(root.left);\n list.push(root.val);\n inOrder(root.right);\n}\n\n/* 帰りがけ順走査 */\nfunction postOrder(root: TreeNode | null): void {\n if (root === null) {\n return;\n }\n // 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n postOrder(root.left);\n postOrder(root.right);\n list.push(root.val);\n}\n</code></pre> binary_tree_dfs.dart<pre><code>/* 行きがけ順走査 */\nvoid preOrder(TreeNode? node) {\n if (node == null) return;\n // 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n list.add(node.val);\n preOrder(node.left);\n preOrder(node.right);\n}\n\n/* 通りがけ順走査 */\nvoid inOrder(TreeNode? node) {\n if (node == null) return;\n // 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n inOrder(node.left);\n list.add(node.val);\n inOrder(node.right);\n}\n\n/* 帰りがけ順走査 */\nvoid postOrder(TreeNode? node) {\n if (node == null) return;\n // 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n postOrder(node.left);\n postOrder(node.right);\n list.add(node.val);\n}\n</code></pre> binary_tree_dfs.rs<pre><code>/* 行きがけ順走査 */\nfn pre_order(root: Option&lt;&amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;) -&gt; Vec&lt;i32&gt; {\n let mut result = vec![];\n\n fn dfs(root: Option&lt;&amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;, res: &amp;mut Vec&lt;i32&gt;) {\n if let Some(node) = root {\n // 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n let node = node.borrow();\n res.push(node.val);\n dfs(node.left.as_ref(), res);\n dfs(node.right.as_ref(), res);\n }\n }\n dfs(root, &amp;mut result);\n\n result\n}\n\n/* 通りがけ順走査 */\nfn in_order(root: Option&lt;&amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;) -&gt; Vec&lt;i32&gt; {\n let mut result = vec![];\n\n fn dfs(root: Option&lt;&amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;, res: &amp;mut Vec&lt;i32&gt;) {\n if let Some(node) = root {\n // 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n let node = node.borrow();\n dfs(node.left.as_ref(), res);\n res.push(node.val);\n dfs(node.right.as_ref(), res);\n }\n }\n dfs(root, &amp;mut result);\n\n result\n}\n\n/* 帰りがけ順走査 */\nfn post_order(root: Option&lt;&amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;) -&gt; Vec&lt;i32&gt; {\n let mut result = vec![];\n\n fn dfs(root: Option&lt;&amp;Rc&lt;RefCell&lt;TreeNode&gt;&gt;&gt;, res: &amp;mut Vec&lt;i32&gt;) {\n if let Some(node) = root {\n // 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n let node = node.borrow();\n dfs(node.left.as_ref(), res);\n dfs(node.right.as_ref(), res);\n res.push(node.val);\n }\n }\n\n dfs(root, &amp;mut result);\n\n result\n}\n</code></pre> binary_tree_dfs.c<pre><code>/* 行きがけ順走査 */\nvoid preOrder(TreeNode *root, int *size) {\n if (root == NULL)\n return;\n // 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n arr[(*size)++] = root-&gt;val;\n preOrder(root-&gt;left, size);\n preOrder(root-&gt;right, size);\n}\n\n/* 通りがけ順走査 */\nvoid inOrder(TreeNode *root, int *size) {\n if (root == NULL)\n return;\n // 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n inOrder(root-&gt;left, size);\n arr[(*size)++] = root-&gt;val;\n inOrder(root-&gt;right, size);\n}\n\n/* 帰りがけ順走査 */\nvoid postOrder(TreeNode *root, int *size) {\n if (root == NULL)\n return;\n // 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n postOrder(root-&gt;left, size);\n postOrder(root-&gt;right, size);\n arr[(*size)++] = root-&gt;val;\n}\n</code></pre> binary_tree_dfs.kt<pre><code>/* 行きがけ順走査 */\nfun preOrder(root: TreeNode?) {\n if (root == null) return\n // 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n list.add(root._val)\n preOrder(root.left)\n preOrder(root.right)\n}\n\n/* 通りがけ順走査 */\nfun inOrder(root: TreeNode?) {\n if (root == null) return\n // 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n inOrder(root.left)\n list.add(root._val)\n inOrder(root.right)\n}\n\n/* 帰りがけ順走査 */\nfun postOrder(root: TreeNode?) {\n if (root == null) return\n // 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n postOrder(root.left)\n postOrder(root.right)\n list.add(root._val)\n}\n</code></pre> binary_tree_dfs.rb<pre><code>### 行きがけ順走査 ###\ndef pre_order(root)\n return if root.nil?\n\n # 訪問順序:根ノード -&gt; 左部分木 -&gt; 右部分木\n $res &lt;&lt; root.val\n pre_order(root.left)\n pre_order(root.right)\nend\n\n### 通りがけ順走査 ###\ndef in_order(root)\n return if root.nil?\n\n # 訪問順序:左部分木 -&gt; 根ノード -&gt; 右部分木\n in_order(root.left)\n $res &lt;&lt; root.val\n in_order(root.right)\nend\n\n### 帰りがけ順走査 ###\ndef post_order(root)\n return if root.nil?\n\n # 訪問順序:左部分木 -&gt; 右部分木 -&gt; 根ノード\n post_order(root.left)\n post_order(root.right)\n $res &lt;&lt; root.val\nend\n</code></pre> 実行の可視化 <p> 全画面で表示 &gt;</p> <p>ヒント</p> <p>反復でも実装できます。興味があれば調べてみてください。</p> <p>図 7-11 は行きがけ順走査の再帰過程です。「呼び出し」と「戻り」という、逆方向の二つの部分に分けられます。</p> <ol> <li>「呼び出し」では新しいメソッドの実行を始め、次のノードを訪問します。</li> <li>「戻り」は関数から戻ることで、現在のノードの訪問が終わったことを表します。</li> </ol> &lt;1&gt;&lt;2&gt;&lt;3&gt;&lt;4&gt;&lt;5&gt;&lt;6&gt;&lt;7&gt;&lt;8&gt;&lt;9&gt;&lt;10&gt;&lt;11&gt; <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p> 図 7-11   行きがけ順走査の再帰過程 </p>","path":["第 7 章   木","7.2   二分木の走査"],"tags":[]},{"location":"chapter_tree/binary_tree_traversal/#2_1","level":3,"title":"2.   計算量の解析","text":"<ul> <li>時間計算量は \\(O(n)\\):全ノードを一回ずつ訪問するため、\\(O(n)\\) 時間です。</li> <li>空間計算量は \\(O(n)\\):最悪の場合、木が連結リストに退化すると再帰の深さが \\(n\\) となり、スタックフレームに \\(O(n)\\) の領域を使います。</li> </ul>","path":["第 7 章   木","7.2   二分木の走査"],"tags":[]},{"location":"chapter_tree/exercises/","level":1,"title":"7.7   練習問題","text":"","path":["第 7 章   木","7.7   練習問題"],"tags":[]},{"location":"chapter_tree/exercises/#771","level":2,"title":"7.7.1   知識の確認","text":"","path":["第 7 章   木","7.7   練習問題"],"tags":[]},{"location":"chapter_tree/exercises/#1","level":3,"title":"1.   完全二分木・全二分木・パーフェクト二分木","text":"<p>次の二つの配列は、二分木をレベル順に表しています。<code>None</code> は空の位置を表します。</p> <ul> <li>木 A:<code>[1, 2, 3, 4, 5, 6]</code></li> <li>木 B:<code>[1, 2, 3, None, None, 6, 7]</code></li> </ul> <ol> <li>どちらが完全二分木ですか。</li> <li>どちらが全二分木、つまり葉以外のすべてのノードに二つの子がある木ですか。</li> <li>この二つの中にパーフェクト二分木はありますか。それぞれ理由を説明してください。</li> </ol> 解答例 <ol> <li> <p>木 A は完全二分木です。埋まっていないのは最下層だけで、ノードは左から右へ連続して並んでいます。 木 B は完全二分木ではありません。最下層の左側に空きがあるのに、右側にはノードがあるためです。</p> </li> <li> <p>木 B は全二分木です。ノード 1 と 3 はそれぞれ二つの子を持ち、ほかはすべて葉です。 木 A は全二分木ではありません。ノード 3 にあるのは左の子 6 だけだからです。</p> </li> <li> <p>どちらも最下層が完全には埋まっていないため、パーフェクト二分木ではありません。</p> </li> </ol>","path":["第 7 章   木","7.7   練習問題"],"tags":[]},{"location":"chapter_tree/exercises/#2","level":3,"title":"2.   同じ木を走査する三つの順序","text":"<p>配列 <code>[1, 2, 3, 4, 5, 6, 7]</code> を、レベル順に完全二分木へ格納します。</p> <ol> <li>この木を描いてください。</li> <li>行きがけ順、通りがけ順、帰りがけ順の走査列を書いてください。</li> <li>通りがけ順の列で、根ノード 1 の左側と右側の列は、それぞれ木のどの部分に対応しますか。</li> </ol> 解答例 <ol> <li> <p>木は次のようになります。</p> <pre><code> 1\n / \\\n 2 3\n / \\ / \\\n 4 5 6 7\n</code></pre> </li> <li> <p>行きがけ順は <code>1, 2, 4, 5, 3, 6, 7</code>、 通りがけ順は <code>4, 2, 5, 1, 6, 3, 7</code>、 帰りがけ順は <code>4, 5, 2, 6, 7, 3, 1</code> です。</p> </li> <li> <p>根ノード 1 の左側の <code>4, 2, 5</code> は、左部分木の通りがけ順の列です。 右側の <code>6, 3, 7</code> は、右部分木の通りがけ順の列です。</p> </li> </ol>","path":["第 7 章   木","7.7   練習問題"],"tags":[]},{"location":"chapter_tree/exercises/#3","level":3,"title":"3.   二つの二分探索木を比べる","text":"<p>次の二つの列を、それぞれ空の二分探索木へ左から右へ順に挿入します。</p> <ul> <li>列 A:<code>[4, 2, 6, 1, 3, 5, 7]</code></li> <li>列 B:<code>[1, 2, 3, 4, 5, 6, 7]</code></li> </ul> <ol> <li>7 を探すときに通るノードを、それぞれ書いてください。</li> <li>高さを、根から最も遠い葉までに通る辺の数とすると、それぞれの木の高さはいくつですか。</li> <li>前の二問から、7 を探す効率は二つの木で同じだと思いますか。木の形と探索経路に基づいて理由を説明してください。</li> </ol> 解答例 <ol> <li> <p>列 A で作った木の探索経路は <code>4 → 6 → 7</code>、 列 B で作った木の探索経路は <code>1 → 2 → 3 → 4 → 5 → 6 → 7</code> です。</p> </li> <li> <p>一つ目はすべての層が埋まっており、高さは 2 です。二つ目は右の子しかなく、高さは 6 です。</p> </li> <li> <p>7 を探す効率は異なります。挿入順によって二分探索木の形と高さが変わるためです。7 を探すとき、一つ目の木で通るのは 3 個のノードだけですが、 二つ目では 7 個のノードを順に通る必要があります。木が高いほど、最悪の場合に経路に沿って比較するノード数が増えます。</p> </li> </ol>","path":["第 7 章   木","7.7   練習問題"],"tags":[]},{"location":"chapter_tree/exercises/#772","level":2,"title":"7.7.2   プログラミング演習","text":"","path":["第 7 章   木","7.7   練習問題"],"tags":[]},{"location":"chapter_tree/exercises/#1_1","level":3,"title":"1.   二分木の最大の深さ","text":"<p>二分木の根ノード <code>root</code> が与えられます。各ノードは整数値と、左右の子への参照を持ちます。</p> <p>根から最も遠い葉までに通るノード数を、二分木の最大の深さと呼びます。この木の最大の深さを返してください。空の木の最大の深さは 0 とします。 再帰を使って解いてください。</p> 解くためのヒント <ol> <li>この問題ではノード数で深さを数えます。根が一つだけなら、最大の深さは 1 です</li> <li>再帰関数は、現在のノードを根とする部分木の最大の深さを返すようにします</li> <li>空のノードでは 0、それ以外では max(depth(left), depth(right)) + 1 を返します</li> </ol> <p>LeetCode</p>","path":["第 7 章   木","7.7   練習問題"],"tags":[]},{"location":"chapter_tree/exercises/#2_1","level":3,"title":"2.   二分木を層ごとに走査する","text":"<p>二分木の根ノード <code>root</code> が与えられます。キューを使って上から下へ層ごとに全ノードを訪問し、同じ層では左から右の順に訪問してください。</p> <p>二次元配列を返してください。最初の部分配列には根の層のノード値、二番目には次の層のノード値、というように格納します。 木が空なら、空配列を返してください。</p> 解くためのヒント <ol> <li>レベル順走査では、先に入ったノードを先に訪問するため、キューを使います</li> <li>各反復の開始時にキューにあるノードは、ちょうど同じ層に属しています</li> <li>まずキューの長さを記録し、その個数だけノードを取り出して、それぞれの子を追加します</li> </ol> <p>LeetCode</p>","path":["第 7 章   木","7.7   練習問題"],"tags":[]},{"location":"chapter_tree/exercises/#3-k","level":3,"title":"3.   二分探索木の k 番目に小さい要素","text":"<p>二分探索木に <code>n</code> 個のノードがあり、各ノードの値はすべて異なります。 全ノードの値を昇順に並べ、その位置に 1 から番号を付けます。</p> <p>根ノード <code>root</code> と、<code>1 &lt;= k &lt;= n</code> を満たす整数 <code>k</code> が与えられます。<code>k</code> 番目のノードの値を返してください。 先に全ノードの値を集めず、通りがけ順の走査中に直接答えを見つけてください。</p> 解くためのヒント <ol> <li>二分探索木の通りがけ順走査では、ノードの値を小さい順に訪問します</li> <li>通りがけ順では左部分木、現在のノード、右部分木の順に処理します。現在のノードを訪問したら、個数を 1 増やします</li> <li>個数が初めて k に達したときのノード値が答えです。それ以降は走査する必要がありません</li> </ol> <p>LeetCode</p>","path":["第 7 章   木","7.7   練習問題"],"tags":[]},{"location":"chapter_tree/summary/","level":1,"title":"7.6   まとめ","text":"","path":["第 7 章   木","7.6   まとめ"],"tags":[]},{"location":"chapter_tree/summary/#1","level":3,"title":"1.   要点の振り返り","text":"<ul> <li>二分木は「一つを二つに分ける」という分割統治の論理を表す非線形データ構造です。各ノードには値と、左右の子をそれぞれ指す二つのポインタがあります。</li> <li>あるノードの左(右)の子とその下のノードが作る木を、そのノードの左(右)部分木と呼びます。</li> <li>関連する用語には、根ノード、葉ノード、レベル、次数、辺、高さ、深さなどがあります。</li> <li>初期化、ノードの挿入・削除の方法は、連結リストと似ています。</li> <li>主な種類には、パーフェクト二分木、完全二分木、全二分木、平衡二分木があります。パーフェクト二分木は最も理想的な状態で、連結リストは退化した最悪の状態です。</li> <li>二分木は配列でも表せます。ノードの値と空の位置をレベル順に並べ、親子のインデックスの対応でポインタを実現します。</li> <li>レベル順走査は幅優先探索の一種で、一周ずつ外へ広がるように層ごとに進み、通常はキューで実装します。</li> <li>行きがけ順、通りがけ順、帰りがけ順は深さ優先探索で、行き止まりまで進んでから戻って続ける方法です。通常は再帰で実装します。</li> <li>二分探索木は要素を効率よく探せるデータ構造で、探索、挿入、削除の時間計算量はすべて \\(O(\\log n)\\) です。連結リストに退化すると、いずれも \\(O(n)\\) に悪化します。</li> <li>AVL 木は平衡二分探索木とも呼ばれ、回転によって、挿入と削除を続けても平衡を保ちます。</li> <li>AVL 木の回転には、右回転、左回転、右回転してから左回転、左回転してから右回転があります。挿入や削除の後、下から上へ回転して平衡を取り戻します。</li> </ul>","path":["第 7 章   木","7.6   まとめ"],"tags":[]},{"location":"chapter_tree/summary/#2-q-a","level":3,"title":"2.   Q &amp; A","text":"<p>Q:ノードが一つだけの二分木では、木の高さも根の深さも \\(0\\) ですか?</p> <p>はい。通常は、通る辺の数で高さと深さを定義するためです。</p> <p>Q:二分木の挿入・削除は複数の操作を組み合わせるとありますが、どういう意味ですか? リソースの子ノードのリソースを解放することと考えてよいですか?</p> <p>二分探索木を例にすると、削除には三つの場合があり、それぞれで複数のノード操作が必要です。</p> <p>Q:二分木の DFS には、なぜ行きがけ順、通りがけ順、帰りがけ順があり、それぞれ何に使いますか?</p> <p>配列を順方向や逆方向に走査するのと同様、これらは特定の順序で結果を得るための三つの走査方法です。例えば二分探索木では、<code>左の子の値 &lt; 根の値 &lt; 右の子の値</code> が成り立つため、「左 \\(\\rightarrow\\) 根 \\(\\rightarrow\\) 右」の順で走査すれば、整列済みのノード列を得られます。</p> <p>Q:右回転は不均衡な <code>node</code>、<code>child</code>、<code>grand_child</code> の関係を処理しますが、<code>node</code> と親のつながりは更新しなくてよいのですか? 切れてしまいませんか?</p> <p>再帰の視点から考える必要があります。<code>right_rotate(root)</code> は部分木の根を受け取り、最後に <code>return child</code> で回転後の根を返します。部分木の根と親の接続は、この関数から戻った後に行うので、右回転の処理範囲には含まれません。</p> <p>Q:C++ で関数を <code>private</code> と <code>public</code> に分ける基準は何ですか? <code>height()</code> は <code>public</code>、<code>updateHeight()</code> は <code>private</code> なのはなぜですか?</p> <p>主に利用範囲で決めます。クラス内部だけで使うなら <code>private</code> にします。例えば <code>updateHeight()</code> は挿入や削除の一段階なので、利用者が単独で呼ぶ意味はありません。一方、<code>height()</code> は <code>vector.size()</code> のように高さを取得するものなので、使えるように <code>public</code> にします。</p> <p>Q:入力データから二分探索木をどう構築しますか? 根の選び方は重要ですか?</p> <p>はい。構築方法は、二分探索木のコードの <code>build_tree()</code> にあります。通常、入力をソートして中点の要素を根とし、左右の部分木を再帰的に構築します。こうすることで、平衡性をできるだけ高く保てます。</p> <p>Q:Java の文字列比較には、必ず <code>equals()</code> を使いますか?</p> <p>Java では、基本データ型の <code>==</code> は二つの変数の値が等しいかを比較します。参照型では、二つの方法の仕組みが異なります。</p> <ul> <li><code>==</code>:二つの変数が同じオブジェクト、つまり同じメモリ位置を指すかを比較します。</li> <li><code>equals()</code>:二つのオブジェクトの値が等しいかを比較します。</li> </ul> <p>値を比較するなら <code>equals()</code> を使うべきです。ただし、<code>String a = \"hi\"; String b = \"hi\";</code> で初期化した文字列は文字列定数プールに格納され、同じオブジェクトを指すため、<code>a == b</code> でも内容を比較できます。</p> <p>Q:幅優先走査で最下層に到達する直前、キューのノード数は \\(2^h\\) ですか?</p> <p>はい。例えば高さ \\(h = 2\\) のパーフェクト二分木なら、総ノード数は \\(n = 7\\) で、最下層のノード数は \\(4 = 2^h = (n + 1) / 2\\) です。</p>","path":["第 7 章   木","7.6   まとめ"],"tags":[]}]}