AVL木と赤黒木をPythonで実装して比較する

はじめに 動かしながらゼロから学ぶLinuxカーネルの教科書 第2版 上記の技術書を読んでいてLinuxスケジューラの話が出てきた。CFSもEEVDFも赤黒木を使っている。赤黒木とは何かを調べていくうちにAVL木との比較が面白かったので、両方Pythonで実装してベンチマークを取った。 実装コードはClaude (Anthropic) により生成。リポジトリは以下。 https://github.com/wasuken/avl-rb-tree 自己平衡二分探索木とは まず前提として、ただの二分探索木には問題がある。 挿入順: 1, 2, 3, 4, 5 1 \ 2 \ 3 \ 4 ← 一直線になる 挿入順によっては木が一直線になり、検索がO(n)に劣化する。これを解決するのが自己平衡二分探索木で、挿入・削除のたびに自動でバランスを取り直す。AVL木と赤黒木はどちらもこのカテゴリに属する。 AVL木 1962年にソ連の数学者Adelson-VelskyとLandis(AVLの名前の由来)が考案した世界初の自己平衡二分探索木。 バランスの管理方法 各ノードに高さ(height)を持たせ、左右の高さの差(バランス係数)が常に1以内になるよう管理する。 def _balance_factor(self, node): return self._height(node.left) - self._height(node.right) 差が2以上になったら回転で修正する。 回転 回転は「親子関係を1段入れ替えるだけ」の操作。二分探索木の順序を壊さずに形だけ変える。 rotate_right(y): y x / \ / \ x C → A y / \ / \ A t2 t2 C t2 は回転で行き場を失う孫ノード。二分探索木の順序的に x < t2 < y が保証されているので、yの左に付け替えるだけでよい。 バランス崩れのパターンは4つ(LL, RR, LR, RL)で、それぞれ1〜2回の回転で解消できる。 挿入・削除 挿入・削除のたびに _rebalance が呼ばれ、高さの更新とバランスチェックが走る。 ...

March 1, 2026 · 2 min

Proxmox LXCコンテナでJupyterLab環境構築 - 試行錯誤とトラブルシューティング

はじめに Proxmox上にJupyterLabのLXC環境を構築しました。当初はGeminiに任せて試行錯誤しましたが、最終的にベストプラクティスに辿り着いたので、その過程と解決策をまとめます。 構築の基本方針 当初は「Root + グローバル環境」で構築しようとしましたが、最終的に**「専用ユーザー + 仮想環境(venv)」**による安全でクリーンな構成に落ち着きました。 最終構成 OS: Ubuntu 24.04 LTS (LXC Container) ユーザー: jupyter (非Root運用) Jupyter: JupyterLab (v4.x) 環境: /opt/jupyter/venv (OSと分離した仮想環境) 環境構築手順 1. OSの準備 Ubuntu 24.04の最小構成に必要なパッケージをインストールします。 apt update && apt upgrade -y apt install -y python3-full build-essential python3-fullが重要です。これがないと後述するPEP 668の問題に直面します。 2. 専用ユーザーとディレクトリの作成 # 専用ユーザー作成 useradd -m -s /bin/bash jupyter # Jupyter本体用のディレクトリ準備 mkdir -p /opt/jupyter chown jupyter:jupyter /opt/jupyter 3. 仮想環境の構築 jupyterユーザーとして、OSの制限を受けない独立した環境を作ります。 su - jupyter python3 -m venv /opt/jupyter/venv source /opt/jupyter/venv/bin/activate # JupyterLabとカーネルのインストール pip install jupyterlab ipykernel pandas 4. systemdによるデーモン化 /etc/systemd/system/jupyter.serviceを作成します。 ...

January 26, 2026 · 2 min

データが暴く物価高騰の真実 - エネルギー価格と為替の相関分析で見えた意外な結論

はじめに - 「円安=物価高」という通説への挑戦 「円安だから物価が上がる」――ニュースで繰り返されるこのフレーズ。本当にそうなのか?統計総局の消費者物価指数(CPI)と為替レートのデータを使って、この仮説を検証してみた。 データ準備 使用データ 消費者物価指数:統計総局『tmi2020a.csv』(2020年基準) 為替レート:みずほ銀行『quote.csv』(日次データを月次平均化) 期間:2023年1月〜2026年1月(3年間) 前処理 import pandas as pd import matplotlib.pyplot as plt from scipy.stats import linregress # CPI読み込み(ヘッダー5行スキップ) cpi_df = pd.read_csv('./tmi/tmi2020a.csv') cpi_clean = cpi_df.iloc[5:].copy().reset_index(drop=True) cpi_clean['エネルギー'] = pd.to_numeric(cpi_clean['エネルギー'], errors='coerce') # 為替読み込み(日次→月次平均) fx_df = pd.read_csv('./doru/quote.csv', encoding='utf-8') fx_clean = fx_df.iloc[2:].copy() fx_clean['日付'] = pd.to_datetime(fx_clean.iloc[:, 0], format='%Y/%m/%d') fx_clean['USD'] = pd.to_numeric(fx_clean.iloc[:, 1], errors='coerce') fx_clean['年月'] = fx_clean['日付'].dt.strftime('%Y%m') monthly_fx = fx_clean.groupby('年月')['USD'].mean().reset_index() monthly_fx.columns = ['年月', 'ドル円'] # データ結合 recent = cpi_clean[cpi_clean['類・品目'] >= '202301'].copy() data = recent.merge(monthly_fx, left_on='類・品目', right_on='年月', how='left') まず全体像を把握する グラフから見える3つの真実 1. エネルギー価格の激しい変動 オレンジ線を見ると、2023年初頭の135から2023年秋には104まで急落(-23%)。その後も上下を繰り返し、最終的に122で着地。地政学リスクがそのまま価格に反映されている。 2. 食料価格の不可逆的上昇 ピンク線は2023年から2025年にかけてほぼ一直線に上昇(109→127、+16%)。一度上がった食品価格は下がらない構造的問題が見える。 3. 総合指数の「マイルド感」 青線は安定的に上昇(104→112、+7%)。しかし国民が実感する物価高は、日常的に買う食料品の16%上昇の方。統計と実感の乖離がここに現れている。 ...

January 23, 2026 · 2 min

LeetCode 735: Asteroid Collision - スタックで衝突判定を美しく解く

問題概要 整数で表される小惑星の配列 asteroids が与えられる。各小惑星について: 絶対値:大きさ 符号:方向(正=右、負=左) 全て同じ速度で移動 衝突ルール: 小さい方が爆発 同じ大きさなら両方爆発 同じ方向に移動する小惑星は衝突しない 全ての衝突後の状態を返せ。 失敗した実装 from collections import deque class Solution: def asteroidCollision(self, asteroids: List[int]) -> List[int]: stack = [] for aster in asteroids: if len(stack) <= 0 or (aster <= 0) == (stack[-1] <= 0): stack.append(aster) else: while len(stack) > 0 and (aster <= 0) != (stack[-1] <= 0) and abs(aster) > abs(stack[-1]): stack.pop() if len(stack) <= 0 or (stack[-1] <= 0) == (aster <= 0): stack.append(aster) elif abs(aster) == abs(stack[-1]): stack.pop() return stack 問題点 同じ条件判定 if len(stack) <= 0 or (stack[-1] <= 0) == (aster <= 0) が2箇所に重複 制御フローが複雑で読みにくい (aster <= 0) != (stack[-1] <= 0) は「符号が異なる」を検出するが、衝突しないケースも含む 最適解 class Solution: def asteroidCollision(self, asteroids: List[int]) -> List[int]: stack = [] for asteroid in asteroids: while stack and asteroid < 0 < stack[-1]: # 右向き vs 左向きの衝突が発生 if abs(stack[-1]) < abs(asteroid): # 右向きが小さい → 爆発して次の右向きとも衝突判定 stack.pop() continue elif abs(stack[-1]) == abs(asteroid): # 同じ大きさ → 両方爆発 stack.pop() break else: # 衝突しなかった or 左向きが勝った stack.append(asteroid) return stack わかったこと 1. ループ条件の本質 asteroid < 0 < stack[-1] これは (asteroid < 0) and (0 < stack[-1]) と同じ。つまり: ...

January 13, 2026 · 2 min

DFS/BFSの本質:深さと幅を支配するデータ構造の選択

DFS, BFSがわかりづらかったので、いくつかの記事を見て個人的に感じた疑問や 「こういうコード例が欲しい」という要望を踏まえて生成AIに生成してもらった。 生成された内容を検証し、コードを実際に動かして確認したところ、 自分の理解が深まる良い記事になったので、このまま公開することにした。 はじめに LeetCodeでMedium問題を解いていると、必ず遭遇するのがDFS(深さ優先探索)とBFS(幅優先探索)だ。 「Dは深さ、Bは幅」というのは知っている。でも、なぜスタックとキューを使い分けるのか?その本質を理解している人は意外と少ない。 今回は、入れ子リストの例を使って、DFS/BFSの動作原理とデータ構造の関係を視覚的に解説する。 問題設定:入れ子リストをフラット化する 以下のような入れ子構造のリストがあるとする。 data = [1, [4, 5, [6, 7, 8], 2], 3] これをフラットな配列にしたい。このとき、「どの順番で要素を取り出すか」がDFS/BFSの違いだ。 ツリー構造として可視化する 入れ子リストは、実はツリー構造として表現できる。 root / | \ 1 [] 3 | /|\ \ 4 5 [] 2 | /|\ 6 7 8 この木をどう巡回するかで、DFSとBFSが決まる。 DFS(深さ優先探索):とにかく深く潜る 動作イメージ 「見つけた枝があれば、まずそこを最後まで探索する」 訪問順序: 1 → [中に入る] → 4 → 5 → [さらに中] → 6 → 7 → 8 → [戻る] → 2 → [戻る] → 3 結果:[1, 4, 5, 6, 7, 8, 2, 3] ...

January 9, 2026 · 3 min