アルゴリズム レッスン4
データ構造
配列、連結リスト、スタック、キュー、木、グラフの実装を学ぼう
連結リスト(Linked List)
各要素(ノード)がデータと次のノードへの参照を持つデータ構造です。 配列と違い、先頭への挿入・削除が O(1) で行えます。 ただし、インデックスアクセスは O(n) です。
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
class LinkedList {
constructor() {
this.head = null;
this.size = 0;
}
// 先頭に追加 O(1)
prepend(value) {
const node = new Node(value);
node.next = this.head;
this.head = node;
this.size++;
}
// 末尾に追加 O(n)
append(value) {
const node = new Node(value);
if (!this.head) {
this.head = node;
} else {
let current = this.head;
while (current.next) current = current.next;
current.next = node;
}
this.size++;
}
// 全要素を表示
print() {
const values = [];
let current = this.head;
while (current) {
values.push(current.value);
current = current.next;
}
console.log(values.join(" -> "));
}
}
const list = new LinkedList();
list.append(1);
list.append(2);
list.append(3);
list.prepend(0);
list.print(); // 0 -> 1 -> 2 -> 3スタック(Stack)
LIFO(Last In, First Out)の原則に従うデータ構造です。 最後に追加した要素が最初に取り出されます。 ブラウザの「戻る」ボタン、関数の呼び出し履歴、括弧の対応チェックなどに使われます。
class Stack {
constructor() {
this.items = [];
}
push(item) {
this.items.push(item); // O(1)
}
pop() {
return this.items.pop(); // O(1)
}
peek() {
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
get size() {
return this.items.length;
}
}
// 括弧の対応チェック
function isValidParentheses(s) {
const stack = new Stack();
const pairs = { ")": "(", "]": "[", "}": "{" };
for (const char of s) {
if ("([{".includes(char)) {
stack.push(char);
} else {
if (stack.pop() !== pairs[char]) return false;
}
}
return stack.isEmpty();
}
console.log(isValidParentheses("({[]})")); // true
console.log(isValidParentheses("({)}")); // falseキュー(Queue)
FIFO(First In, First Out)の原則に従うデータ構造です。 最初に追加した要素が最初に取り出されます。 タスクの処理順序、BFS、プリンタのジョブ管理などに使われます。
class Queue {
constructor() {
this.items = {};
this.head = 0;
this.tail = 0;
}
enqueue(item) {
this.items[this.tail] = item;
this.tail++;
}
dequeue() {
if (this.isEmpty()) return undefined;
const item = this.items[this.head];
delete this.items[this.head];
this.head++;
return item;
}
peek() {
return this.items[this.head];
}
isEmpty() {
return this.tail - this.head === 0;
}
get size() {
return this.tail - this.head;
}
}
const q = new Queue();
q.enqueue("タスクA");
q.enqueue("タスクB");
q.enqueue("タスクC");
console.log(q.dequeue()); // "タスクA"(先に入れた方から)
console.log(q.dequeue()); // "タスクB"
console.log(q.size); // 1二分探索木(Binary Search Tree)
各ノードが最大2つの子ノードを持ち、左の子 < 親 < 右の子という規則を満たす木構造です。 探索・挿入・削除が平均 O(log n) で行えます。
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class BST {
constructor() {
this.root = null;
}
insert(value) {
const node = new TreeNode(value);
if (!this.root) { this.root = node; return; }
let current = this.root;
while (true) {
if (value < current.value) {
if (!current.left) { current.left = node; return; }
current = current.left;
} else {
if (!current.right) { current.right = node; return; }
current = current.right;
}
}
}
search(value) {
let current = this.root;
while (current) {
if (value === current.value) return true;
current = value < current.value ? current.left : current.right;
}
return false;
}
// 中間順走査(昇順で出力される)
inOrder(node = this.root, result = []) {
if (node) {
this.inOrder(node.left, result);
result.push(node.value);
this.inOrder(node.right, result);
}
return result;
}
}
const tree = new BST();
[8, 3, 10, 1, 6, 14].forEach(v => tree.insert(v));
console.log(tree.inOrder()); // [1, 3, 6, 8, 10, 14]
console.log(tree.search(6)); // true
console.log(tree.search(7)); // falseグラフ(Graph)
ノード(頂点)とエッジ(辺)で構成されるデータ構造です。 SNSの友人関係、路線図、Webページのリンクなど、もの同士の関係を表現するのに使います。
class Graph {
constructor() {
this.adjacencyList = {};
}
addVertex(vertex) {
if (!this.adjacencyList[vertex]) {
this.adjacencyList[vertex] = [];
}
}
// 無向グラフのエッジ追加
addEdge(v1, v2) {
this.adjacencyList[v1].push(v2);
this.adjacencyList[v2].push(v1);
}
print() {
for (const vertex in this.adjacencyList) {
console.log(vertex, "->", this.adjacencyList[vertex].join(", "));
}
}
}
const g = new Graph();
["東京", "大阪", "名古屋", "福岡"].forEach(v => g.addVertex(v));
g.addEdge("東京", "大阪");
g.addEdge("東京", "名古屋");
g.addEdge("大阪", "福岡");
g.addEdge("名古屋", "大阪");
g.print();
// 東京 -> 大阪, 名古屋
// 大阪 -> 東京, 福岡, 名古屋
// 名古屋 -> 東京, 大阪
// 福岡 -> 大阪まとめ
- 連結リストは先頭への挿入・削除が O(1) だがインデックスアクセスは O(n)
- スタック(LIFO)は関数呼び出し、元に戻す操作などに使われる
- キュー(FIFO)はタスク管理、BFS などに使われる
- 二分探索木は探索・挿入・削除が平均 O(log n)
- グラフは関係性の表現に強く、BFS/DFS で探索できる
- 問題に最適なデータ構造を選ぶことでアルゴリズムの効率が大幅に変わる