initial commit

This commit is contained in:
2026-03-02 20:55:36 +09:00
commit f224644b49
86 changed files with 1065250 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
# Latex
latex/aux/
latex/build/*
!latex/build/*.pdf
# Codes
__pycache__/
.venv/
codes/results/
codes/experiments/real_data/data/
# System
.DS_Store
+166
View File
@@ -0,0 +1,166 @@
python -V
Python 3.12.12
pip -V
pip 26.0.1 from /root/dijkstra/src/.venv/lib/python3.12/site-packages/pip (python 3.12)
python -c "import platform; print(platform.platform())"
Linux-6.8.12-17-pve-x86_64-with-glibc2.41
lscpu | head
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 48 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 1-3,8
Off-line CPU(s) list: 0,4-7,9-15
Vendor ID: AuthenticAMD
Model name: AMD Ryzen 7 7700 8-Core Processor
CPU family: 25
## I. Introduction
• 이론적 복잡도 vs 실제 성능 문제 제기
• 기존 연구 간략 언급
• RQ 제시
• 핵심 주장 요약
## II. Theoretical Background
이 부분이 추가되어야 한다.
### 2.1 Dijkstra 시간복잡도 구조
Binary:
T_B = c_1 E \log V
Fibonacci:
T_F = c_2 E + c_3 V \log V
### 2.2 decrease-key의 역할
• relax_success = decrease_key 발생 빈도
• 입력 분포와 그래프 밀도가 이를 결정
→ 여기서 “역전 조건의 이론적 형태”를 먼저 제시
예:
c_{dec}^{F} \cdot D < c_{dec}^{B} \cdot D \log V
이게 논문의 수학적 중심이 된다.
III. Methodology
이건 반드시 독립 섹션으로.
3.1 실제 데이터 분석 방법
• USA-road 기반 분포 추정
• mean, std 계산
• lognormal 변환 공식
3.2 가상 그래프 생성 방법
• outdegree 기반 생성
• density 정의
• std 범위 정당화
3.3 측정 지표
• relax_attempts
• relax_success
• relax_ratio
• speedup 정의
3.4 연산 단위 비용 모델
선형 모델:
T = \beta_0 + \beta_1 extract + \beta_2 relax + \beta_3 decrease
이걸 명확히 수식으로 써야 함.
IV. Empirical Analysis
이제 네가 말한 흐름을 그대로 쓰면 된다.
4.1 실제 데이터 분포 분석
• mean ≈ 3000
• std ≈ 4000
• lognormal 적합성
→ 실험 파라미터의 정당화
4.2 변수 간 관계 분석 (가상 데이터)
4.2.1 Density ↔ Relax_attempts
→ 거의 선형 관계
4.2.2 Sigma ↔ Relax_success_ratio
→ 양의 상관관계
4.2.3 Decrease_key ↔ Speedup
→ 거의 무관
4.3 런타임 다중 선형 회귀 분석
• 단위 연산 비용 추정
• Binary vs Fibonacci 비교
• R² 제시
이 부분이 핵심 증거.
V. Deriving the Crossover Condition
여기가 논문의 “기여”다.
회귀 계수를 이용해:
Binary:
T_B = a_1 extract\log V + a_2 decrease\log V + ...
Fibonacci:
T_F = b_1 extract\log V + b_2 decrease + ...
역전 조건:
T_F < T_B
정리하면:
(b_2 - a_2 \log V) D + (b_1 - a_1)\log V + ... < 0
여기서:
• 실제 계수 대입
• 부등식이 성립하는 V, D 범위 계산
이게 논문의 가장 강력한 부분.
VI. Feasibility Analysis
• Python 환경에서는 성립 불가
• 상수항이 지배
• C++ 가능성 언급
VII. Conclusion
• RQ에 대한 명확한 답
• 이론과 실제의 간극 강조
• 실무적 함의
• 한계 및 향후 연구
View File
View File
+46
View File
@@ -0,0 +1,46 @@
class DijkstraStats:
def __init__(self):
self.extract_min_calls = 0
self.relax_attempts = 0
self.relax_success = 0
# self.decrease_key_calls = 0
# self.add_calls = 0
def heap_dijkstra(nodes, adj, heap, start, end):
INF = float('inf')
dist = [INF] * nodes
visited = [False] * nodes
stats = DijkstraStats()
dist[start] = 0
for v in range(nodes):
heap.add(v, dist[v])
# stats.add_calls += 1
while True:
res = heap.extract_min()
stats.extract_min_calls += 1
if res is None:
break
cur, cur_dist = res
if cur_dist == INF:
break
if cur == end:
break
for nxt, d in adj[cur]:
stats.relax_attempts += 1
if visited[nxt]:
continue
new = cur_dist + d
if new < dist[nxt]:
stats.relax_success += 1
dist[nxt] = new
heap.decrease_key(nxt, new)
visited[cur] = True
return dist[end], stats
+32
View File
@@ -0,0 +1,32 @@
def pure_dijkstra(nodes, adj, start, end):
dist = [float('inf')] * nodes
dist[start] = 0
visited = [False] * nodes
cur = start
while True:
if cur == -1:
break
if cur == end:
break
for next, d in adj[cur]:
if not visited[next]:
new = dist[cur] + d
if new < dist[next]:
dist[next] = new
visited[cur] = True
# Finding min node
min_node = -1
min_val = float('inf')
for n in range(nodes):
if not visited[n] and dist[n] < min_val:
min_node = n
min_val = dist[n]
cur = min_node
return dist[end]
View File
+75
View File
@@ -0,0 +1,75 @@
class BinHeap:
def __init__(self, nodes):
self.heap = []
self.pos = [-1] * nodes
def sift_up(self, idx):
heap = self.heap
pos = self.pos
backup = heap[idx]
cur = idx
while cur > 0:
par = (cur - 1) // 2
if backup[1] >= heap[par][1]:
break
heap[cur] = heap[par]
pos[heap[cur][0]] = cur
cur = par
heap[cur] = backup
pos[backup[0]] = cur
def sift_down(self, idx):
heap = self.heap
pos = self.pos
n = len(heap)
backup = heap[idx]
cur = idx
while True:
left = cur * 2 + 1
if left >= n:
break
right = left + 1
tar = right if right < n and heap[right][1] < heap[left][1] else left
if backup[1] <= heap[tar][1]:
break
heap[cur] = heap[tar]
pos[heap[cur][0]] = cur
cur = tar
heap[cur] = backup
pos[backup[0]] = cur
def add(self, key, dist):
idx = len(self.heap)
self.heap.append([key, dist])
self.pos[key] = idx
self.sift_up(idx)
def extract_min(self):
heap = self.heap
pos = self.pos
if not heap:
return None
min_node, min_dist = heap[0]
pos[min_node] = -1
last = heap.pop()
if heap:
heap[0] = last
pos[last[0]] = 0
self.sift_down(0)
return (min_node, min_dist)
def decrease_key(self, key, new_dist):
idx = self.pos[key]
if idx == -1:
return None
node = self.heap[idx]
if new_dist >= node[1]:
return None
node[1] = new_dist
self.sift_up(idx)
+177
View File
@@ -0,0 +1,177 @@
import math
class Node:
__slots__ = ('key', 'dist', 'left', 'right', 'parent', 'child', 'degree', 'lost')
def __init__(self, key, dist):
self.key = key
self.dist = dist
self.left = self
self.right = self
self.parent = None
self.child = None
self.degree = 0
self.lost = False
def connect_right(self, new_right):
old_right = self.right
new_left = new_right.left
new_right.left = self
new_left.right = old_right
old_right.left = new_left
self.right = new_right
def remove(self):
self.left.right = self.right
self.right.left = self.left
self.left = self
self.right = self
def connect_child(self, child):
child.parent = self
child.lost = False
if self.child is None:
self.child = child
else:
self.child.connect_right(child)
self.degree += 1
class FiboHeap:
def __init__(self, nodes):
self.min = None
self.n = 0
self.pos = [None] * nodes
def cut(self, cur):
p = cur.parent
if p.child is cur:
if cur.right is cur:
p.child = None
else:
p.child = cur.right
cur.remove()
p.degree -= 1
cur.parent = None
cur.lost = False
self.min.connect_right(cur)
if cur.dist < self.min.dist:
self.min = cur
def cascading_cut(self, cur):
while True:
p = cur.parent
if p is None:
return
if not cur.lost:
cur.lost = True
return
self.cut(cur)
cur = p
def add(self, key, dist):
cur = Node(key, dist)
if self.min is None:
self.min = cur
else:
self.min.connect_right(cur)
if cur.dist < self.min.dist:
self.min = cur
self.n += 1
self.pos[key] = cur
def extract_min(self):
# 1. Find min node
min_node = self.min
self.n -= 1
# 2. Make min node's child into individual tree
c = min_node.child
if c is not None:
start = c
cur = c
while True:
cur.parent = None
cur.lost = False
cur = cur.right
if cur == start:
break
min_node.connect_right(start)
if min_node.right is min_node:
self.min = None
return (min_node.key, min_node.dist)
nxt = min_node.right
min_node.remove()
self.min = nxt
min_node.child = None
min_node.degree = 0
self.pos[min_node.key] = None
# 3. Make into binomial tree
roots = []
start = self.min
cur = start
while True:
roots.append(cur)
cur = cur.right
if cur == start:
break
max_deg = int(math.log2(self.n)) + 2 if self.n > 0 else 1 # 여기서 +2 하는 이유는 사실은 log2가 아니라 fibonacci 수열 기반이기 때문에 +1 더 해주는 거임.
A = [None] * (max_deg + 1)
for cur in roots:
x = cur
d = x.degree
while A[d] is not None:
y = A[d]
if y.dist < x.dist:
x, y = y, x
y.remove()
x.connect_child(y)
A[d] = None
d = x.degree
A[d] = x
# 4. Find new min
self.min = None
for cur in A:
if cur is None:
continue
if self.min is None:
self.min = cur
else:
if cur.dist < self.min.dist:
self.min = cur
return (min_node.key, min_node.dist)
def decrease_key(self, key, new_dist):
cur = self.pos[key]
if cur is None or new_dist >= cur.dist:
return
cur.dist = new_dist
p = cur.parent
if p is not None and cur.dist < p.dist:
self.cut(cur)
self.cascading_cut(p)
if cur.dist < self.min.dist:
self.min = cur
View File
@@ -0,0 +1,95 @@
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import random
def read_dimacs(file):
idx = 0
with open(f"{folder}/{file}", "r") as f:
for line in f:
line = line.strip()
if line.startswith("c"):
continue
elif line.startswith("p"):
_, _, n, e = line.split()
edges = int(e)
data = [0] * edges
elif line.startswith("a"):
_, u, v, w = line.split()
data[idx] = int(w)
idx += 1
return data
def visualize_weights(file):
data = read_dimacs(file)
sample = np.array(data)
save_folder = f"{res_folder}/{file}"
os.makedirs(save_folder, exist_ok=True)
plt.figure(figsize=(8, 5))
plt.hist(sample, bins=100)
plt.title("Edge Weight Distribution (Original Scale)")
plt.xlabel("Weight")
plt.ylabel("Frequency")
plt.tight_layout()
plt.savefig(f"{save_folder}/hist_original.png")
plt.close()
log_sample = np.log(sample[sample > 0])
plt.figure(figsize=(8, 5))
plt.hist(log_sample, bins=100)
plt.title("Log(Weight) Distribution")
plt.xlabel("log(Weight)")
plt.ylabel("Frequency")
plt.tight_layout()
plt.savefig(f"{save_folder}/hist_log.png")
plt.close()
plt.figure(figsize=(8, 5))
plt.hist(log_sample, bins=100, density=True)
plt.title("Log(Weight) Density")
plt.xlabel("log(Weight)")
plt.ylabel("Density")
plt.tight_layout()
plt.savefig(f"{save_folder}/hist_log_density.png")
plt.close()
return {
"file": file,
"edges": len(sample),
"mean": np.mean(sample),
"std": np.std(sample),
"min": np.min(sample),
"max": np.max(sample)
}
folder = "experiments/real_data/data/dimacs_data"
files = [
"USA-road-d.BAY.gr",
"USA-road-d.CAL.gr",
"USA-road-d.COL.gr",
"USA-road-d.CTR.gr",
"USA-road-d.E.gr",
"USA-road-d.FLA.gr",
"USA-road-d.LKS.gr",
"USA-road-d.NE.gr",
"USA-road-d.NW.gr",
"USA-road-d.NY.gr",
"USA-road-d.USA.gr",
"USA-road-d.W.gr"
]
res_folder = "results/real_data/derived/dimacs_graph_distribution"
os.makedirs(res_folder, exist_ok=True)
rows = []
for file in files:
row = visualize_weights(file)
rows.append(row)
df = pd.DataFrame(rows)
df.to_csv(f"{res_folder}/distribution.csv", index=False)
@@ -0,0 +1,21 @@
def dimacs_convert_graph(filename):
with open(filename, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('c'):
continue
elif line.startswith('p'):
_, _, n, e = line.split()
nodes = int(n)
edges = int(e)
adj = [[] for _ in range(nodes + 1)]
elif line.startswith('a'):
_, u, v, w = line.split()
u, v, w = int(u) - 1, int(v) - 1, int(w)
adj[u].append((v, w))
return nodes, adj
@@ -0,0 +1,102 @@
import os
import time
import pandas as pd
import numpy as np
from experiments.real_data.graph_converters.dimacs_graph_converter import dimacs_convert_graph
from core.dijkstra.heap_dijkstra import heap_dijkstra
from core.heaps.binary_heap import BinHeap
from core.heaps.fibonacci_heap import FiboHeap
def run_test(
folder,
files,
trials,
base_seed
):
rows = []
timer = time.perf_counter
rng = np.random.default_rng(base_seed)
INF = float('inf')
for file in files:
nodes, adj = dimacs_convert_graph(f'{folder}/{file}')
density = len(adj) / (nodes * (nodes - 1))
for trial in range(1, trials + 1):
start, end = rng.choice(nodes, size=2, replace=False) + 1
bin_heap = BinHeap(nodes)
st = timer()
dist, stats = heap_dijkstra(nodes, adj, bin_heap, start, end)
et = timer()
rows.append(
{
"file": file,
"nodes": nodes,
"density": density,
"trial": trial,
"start": start,
"end": end,
"time": et - st,
"algorithm": "binary",
"reached": dist != INF,
"extract_min_calls": stats.extract_min_calls,
"relax_attempts": stats.relax_attempts,
"relax_success": stats.relax_success,
}
)
fibo_heap = FiboHeap(nodes)
st = timer()
dist, stats = heap_dijkstra(nodes, adj, fibo_heap, start, end)
et = timer()
rows.append(
{
"file": file,
"nodes": nodes,
"density": density,
"trial": trial,
"start": start,
"end": end,
"time": et - st,
"algorithm": "fibonacci",
"reached": dist != INF,
"extract_min_calls": stats.extract_min_calls,
"relax_attempts": stats.relax_attempts,
"relax_success": stats.relax_success,
}
)
df = pd.DataFrame(rows)
return df
# Settings
folder = "experiments/real_data/data/dimacs_data"
files = [
"USA-road-d.BAY.gr",
"USA-road-d.CAL.gr",
"USA-road-d.COL.gr",
"USA-road-d.CTR.gr",
"USA-road-d.E.gr",
"USA-road-d.FLA.gr",
"USA-road-d.LKS.gr",
"USA-road-d.NE.gr",
"USA-road-d.NW.gr",
"USA-road-d.NY.gr",
"USA-road-d.USA.gr",
"USA-road-d.W.gr"
]
trials = 200
base_seed = 42
df = run_test(
folder=folder,
files=files,
trials=trials,
base_seed=base_seed
)
save_folder = "results/real_data/raw"
os.makedirs(save_folder, exist_ok=True)
df.to_csv(f"{save_folder}/dimacs_t{trials}_s{base_seed}.csv", index=False)
@@ -0,0 +1,50 @@
import os
import pandas as pd
import matplotlib.pyplot as plt
csv_file = "results/synthetic_data/raw/20260302_183202.csv"
save_folder = f"results/synthetic_data/derived/{os.path.splitext(os.path.basename(csv_file))[0]}"
os.makedirs(save_folder, exist_ok=True)
df = pd.read_csv(csv_file)
df = df[df["algorithm"] == "binary"].copy()
df["relax_success_ratio"] = df["relax_success"] / df["relax_attempts"]
# N vs extract_min_calls
grouped = df.groupby("nodes")["extract_min_calls"].mean()
plt.figure(figsize=(6,4))
plt.plot(grouped.index, grouped.values, marker='o')
plt.xlabel("N (nodes)")
plt.ylabel("Average extract_min_calls")
plt.title("Nodes vs Extract-Min Calls")
plt.grid(True)
plt.tight_layout()
plt.savefig(f"{save_folder}/N_vs_extract_min.png", dpi=300)
plt.close()
# Density vs relax_attempts
grouped = df.groupby("density")["relax_attempts"].mean()
plt.figure(figsize=(6,4))
plt.plot(grouped.index, grouped.values, marker='o')
plt.xlabel("Density")
plt.ylabel("Average relax_attempts")
plt.title("Density vs Relax Attempts")
plt.grid(True)
plt.tight_layout()
plt.savefig(f"{save_folder}/density_vs_relax_attempts.png", dpi=300)
plt.close()
# Sigma vs relax_success_ratio
grouped = df.groupby("sigma")["relax_success_ratio"].mean()
plt.figure(figsize=(6,4))
plt.plot(grouped.index, grouped.values, marker='o')
plt.xlabel("Sigma (lognormal)")
plt.ylabel("Relax Success Ratio")
plt.title("Sigma vs Relax Success Ratio")
plt.grid(True)
plt.tight_layout()
plt.savefig(f"{save_folder}/sigma_vs_relax_ratio.png", dpi=300)
plt.close()
@@ -0,0 +1,48 @@
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
def fit_op_cost_functional(df, algo):
sub = df[(df["algorithm"] == algo) & (df["reached"] == True)].copy()
sub["logN"] = np.log(sub["nodes"])
sub = sub.rename(columns={"relax_success":"decrease_key_calls",
"nodes":"add_calls"})
if algo == "binary":
sub["f_extract"] = sub["extract_min_calls"] * sub["logN"]
sub["f_decrease"] = sub["decrease_key_calls"] * sub["logN"]
else: # fibonacci
sub["f_extract"] = sub["extract_min_calls"] * sub["logN"]
sub["f_decrease"] = sub["decrease_key_calls"]
X = sub[["add_calls", "f_extract", "relax_attempts", "f_decrease"]].to_numpy()
y = sub["time"].to_numpy()
model = LinearRegression()
model.fit(X, y)
y_pred = model.predict(X)
r2 = r2_score(y, y_pred)
return {
"algo": algo,
"intercept": model.intercept_,
"coef_add": model.coef_[0],
"coef_extract": model.coef_[1],
"coef_relax": model.coef_[2],
"coef_decrease": model.coef_[3],
"r2": r2,
"n_samples": len(sub)
}
def op_cost_functional_analysis(df):
return pd.DataFrame([
fit_op_cost_functional(df, "binary"),
fit_op_cost_functional(df, "fibonacci")
])
df = pd.read_csv("results/synthetic_data/raw/20260302_183202.csv")
summary = op_cost_functional_analysis(df)
print(summary)
@@ -0,0 +1,38 @@
import pandas as pd
import matplotlib.pyplot as plt
import os
csv_file = "results/synthetic_data/raw/20260302_183202.csv"
save_folder = f"results/synthetic_data/derived/{os.path.splitext(os.path.basename(csv_file))[0]}"
os.makedirs(save_folder, exist_ok=True)
df = pd.read_csv(csv_file)
# df = df[df["reached"] == True].copy()
key_cols = ["nodes", "density", "std", "sigma", "trial", "seed"]
pivot_time = (
df.pivot_table(index=key_cols, columns="algorithm", values="time", aggfunc="mean")
.reset_index()
)
pivot_time["speed_ratio"] = pivot_time["binary"] / pivot_time["fibonacci"]
# print((pivot_time["speed_ratio"] > 1).sum())
# print((pivot_time["speed_ratio"] < 1).sum())
relax = (
df[df["algorithm"] == "binary"][key_cols + ["relax_success"]]
)
merged = pivot_time.merge(relax, on=key_cols, how="left")
plt.figure(figsize=(6, 4))
plt.scatter(merged["relax_success"], merged["speed_ratio"], marker=".")
plt.axhline(y=1.0, color='red', linestyle='--')
plt.xlabel("decrease_key (binary)")
plt.ylabel("speed_ratio (binary / fibonacci)")
plt.title("decrease_key vs speed_ratio")
plt.grid(True)
plt.tight_layout()
plt.savefig(f"{save_folder}/decrease_key_vs_speed_ratio.png", dpi=300)
plt.close()
@@ -0,0 +1,36 @@
import numpy as np
def generate_graph(NODES=10, DENSITY=0.1, DISTRIBUTION=('lognormal', (100, 1)), SEED=42):
rng = np.random.default_rng(SEED)
# Calculate variables
max_edges = (NODES - 1) * NODES
edges = int(round(DENSITY * max_edges))
# Make edges
sel = rng.choice(max_edges, size=edges, replace=False)
# Make distance array
dist_name, dist_params = DISTRIBUTION
if dist_name == "lognormal":
mean, sigma = dist_params
distances = rng.lognormal(mean=mean, sigma=sigma, size=edges)
elif dist_name == "uniform":
mean = dist_params[0]
distances = rng.uniform(0.0, 2.0 * mean, size=edges)
elif dist_name == "exponential":
mean = dist_params[0]
distances = rng.exponential(scale=mean, size=edges)
else:
raise ValueError("Unknown DISTRIBUTION")
# Make adj
adj = [[] for _ in range(NODES)]
for idx, dist in zip(sel, distances):
u = idx // (NODES-1)
r = idx % (NODES-1)
v = r if r < u else r + 1
adj[u].append((v, dist))
return adj
@@ -0,0 +1,44 @@
import numpy as np
def outdegree_generate_graph(NODES, DENSITY, DISTRIBUTION, SEED=42):
rng = np.random.default_rng(SEED)
max_edges = (NODES - 1) * NODES
edges = int(round(DENSITY * max_edges))
base = edges // NODES
rem = edges % NODES
adj = [[] for _ in range(NODES)]
dist_name, dist_params = DISTRIBUTION
if dist_name == "lognormal":
mean, sigma = dist_params
dist_func = lambda size: rng.lognormal(mean=mean, sigma=sigma, size=size)
elif dist_name == "uniform":
mean = dist_params[0]
dist_func = lambda size: rng.uniform(0.0, 2.0 * mean, size=size)
elif dist_name == "exponential":
mean = dist_params[0]
dist_func = lambda size: rng.exponential(scale=mean, size=size)
else:
raise ValueError("Unknown DISTRIBUTION")
for u in range(NODES):
d_u = base + (1 if u < rem else 0)
if d_u == 0:
continue
targets = rng.choice(NODES-1, size=d_u, replace=False)
targets = np.where(targets < u, targets, targets + 1)
weights = dist_func(d_u)
weights = np.rint(weights).astype(np.int64)
weights = np.maximum(weights, 1)
for v, w in zip(targets, weights):
adj[u].append((v, float(w)))
return adj
+149
View File
@@ -0,0 +1,149 @@
import time
import pandas as pd
import numpy as np
from tqdm import tqdm
import datetime
import json
import os
from experiments.synthetic_data.graph_generators.outdegree_graph_generator import outdegree_generate_graph
from core.dijkstra.heap_dijkstra import heap_dijkstra
from core.heaps.binary_heap import BinHeap
from core.heaps.fibonacci_heap import FiboHeap
def run_test(
nodes_li,
densities,
stds,
trials,
mean_dist,
start,
end,
base_seed
):
timer = time.perf_counter
file_exists = False
INF = float('inf')
total_jobs = (
len(nodes_li) *
len(densities) *
len(stds) *
trials
)
with tqdm(total=total_jobs) as pbar:
for nodes in nodes_li:
rows = []
for density in densities:
for std in stds:
sigma = np.sqrt(np.log((std / mean_dist)**2 + 1))
mu = np.log(mean_dist) - (sigma**2) / 2
distribution = ('lognormal', (mu, sigma))
for trial in range(1, trials + 1):
seed = (base_seed * 1_000_003) ^ (nodes * 9176) ^ int(density * 1e9) ^ int(sigma * 1e6) ^ trial
seed &= 0xFFFFFFFF
adj = outdegree_generate_graph(
NODES=nodes,
DENSITY=density,
DISTRIBUTION=distribution,
SEED=seed
)
bin_heap = BinHeap(nodes)
st = timer()
dist, stats = heap_dijkstra(nodes, adj, bin_heap, start, end)
et = timer()
rows.append(
{
"nodes": nodes,
"density": density,
"std": std,
"sigma": sigma,
"trial": trial,
"seed": seed,
"time": et - st,
"algorithm": "binary",
"reached": dist != INF,
"extract_min_calls": stats.extract_min_calls,
"relax_attempts": stats.relax_attempts,
"relax_success": stats.relax_success,
}
)
fibo_heap = FiboHeap(nodes)
st = timer()
dist, stats = heap_dijkstra(nodes, adj, fibo_heap, start, end)
et = timer()
rows.append(
{
"nodes": nodes,
"density": density,
"std": std,
"sigma": sigma,
"trial": trial,
"seed": seed,
"time": et - st,
"algorithm": "fibonacci",
"reached": dist != INF,
"extract_min_calls": stats.extract_min_calls,
"relax_attempts": stats.relax_attempts,
"relax_success": stats.relax_success,
}
)
pbar.update(1)
df_nodes = pd.DataFrame(rows)
df_nodes.to_csv(
csv_file,
mode='a',
header=not file_exists,
index=False
)
file_exists = True
print(f"=== SAVED nodes = {nodes} ({len(df_nodes)} rows) ===")
return
# Settings
nodes_li = [2000, 4000, 8000, 16000]
densities = [0.00015, 0.0003, 0.0006, 0.0012, 0.0024, 0.0048, 0.0096, 0.0192, 0.0384]
stds = [1000, 1500, 2000, 3000, 4000, 6000, 8000, 12000, 16000]
trials = 400
mean_dist = 3000
start = 0
end = 1
base_seed = 42
save_folder = "results/synthetic_data/raw"
os.makedirs(save_folder, exist_ok=True)
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
csv_file = f"{save_folder}/{timestamp}.csv"
config_file = f"{save_folder}/{timestamp}.json"
config = {
"nodes_li": nodes_li,
"densities": densities,
"stds": stds,
"trials": trials,
"mean_dist": mean_dist,
"start": start,
"end": end,
"base_seed": base_seed,
}
with open(config_file, "w") as f:
json.dump(config, f, indent=4)
df = run_test(
nodes_li = nodes_li,
densities = densities,
stds = stds,
trials = trials,
mean_dist = mean_dist,
start = start,
end = end,
base_seed = base_seed
)
+34
View File
@@ -0,0 +1,34 @@
import numpy as np
import matplotlib.pyplot as plt
mu = 3.5
sigmas = [0.1, 0.3]
size = 100000
rng = np.random.default_rng(42)
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
for sigma in sigmas:
samples = rng.lognormal(mean=mu, sigma=sigma, size=size)
# lognormal → normal 변환
log_samples = np.log(samples)
# Lognormal 플롯
axes[0].hist(samples, bins=200, density=True, alpha=0.5, label=f"sigma={sigma}")
axes[0].set_xlim(0, 200)
axes[0].set_title("Lognormal Distribution")
axes[0].set_xlabel("Weight")
axes[0].set_ylabel("Density")
axes[0].legend()
# Normal 플롯
axes[1].hist(log_samples, bins=200, density=True, alpha=0.5, label=f"sigma={sigma}")
axes[1].set_title("log(samples) → Normal Distribution")
axes[1].set_xlabel("log(Weight)")
axes[1].set_ylabel("Density")
axes[1].legend()
plt.suptitle("Lognormal vs Normal (μ fixed)")
plt.tight_layout()
plt.show()
+18
View File
@@ -0,0 +1,18 @@
contourpy==1.3.3
cycler==0.12.1
fonttools==4.61.1
joblib==1.5.3
kiwisolver==1.4.9
matplotlib==3.10.8
numpy==2.4.2
packaging==26.0
pandas==3.0.1
pillow==12.1.1
pyparsing==3.3.2
python-dateutil==2.9.0.post0
scikit-learn==1.8.0
scipy==1.17.1
seaborn==0.13.2
six==1.17.0
threadpoolctl==3.6.0
tqdm==4.67.3
+34
View File
@@ -0,0 +1,34 @@
```mermaid
flowchart LR
subgraph A[dijkstra]
AA[pure dijkstra]
AB[heap dijkstra]
end
subgraph B[heap]
BA[binary heap]
BB[fibonacci heap]
end
C[outdegree graph generator]
D[dimacs graph converter]
subgraph E[test]
EA[synthesized test]
EB[dimacs test]
end
subgraph F[analysis]
FA[analysis all]
FB[analysis call]
end
```
read_dimacs.py: dimacs 파일을 읽어서 분포를 시각화 해줌.
regression.py: test.py 결과물을 바탕으로 dijkstra 작업별 시간 회귀 분석함.
lognormal_visual.py: lognormal 데이터의 분포를 시각화 해줌. (그냥 공부용이고 본 연구와 관계 없음)
test_dicmac.py: dicmac 실제 그래프 바탕으로 dijkstra 적용. 동일 그래프에 대해 여러 start, end 테스트함.
analysis_all.py:
analysis_call.py: dijkstra 변인간의 상관관계 분석함. (nodes <-> extract_min_calls, density <-> relax_attempts, sigma <-> relax_success_ratio)
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

+325
View File
@@ -0,0 +1,325 @@
\documentclass{article}
\usepackage{float}
\usepackage{hyperref}
\hypersetup{
pdfborder={0 0 0}
}
\usepackage{graphicx}
\graphicspath{{images/}}
\setlength{\parindent}{0pt}
\setlength{\parskip}{0.6em}
\linespread{1.08}
\title{Outline of EE}
\author{Seungjun Lee}
\date{February 22, 2026}
\begin{document}
\maketitle
\begin{abstract}
\textbf{Title}:
When Asymptotic Complexity Fails: An Empirical and Cost-Based Study of Binary and Fibonacci Heaps in Python
\textbf{RQ}:
Why does the theoretical asymptotic advantage of Fibonacci heap in Dijkstras algorithm not translate into practical runtime improvements in Python implementations?
\textbf{Category}:
Computer Science
\end{abstract}
\newpage
\tableofcontents
\newpage
\section{Introduction}
\subsection*{Context}
\begin{itemize}
\item Dijkstras algorithm widely used for shortest path problems
\item Priority queue choice affects theoretical time complexity
\item Pure Dijkstra: $O(E+ V^2)$
\item Binary heap: $O(E \log V)$
\item Fibonacci heap: $O(E + V \log V)$
\item Theoretical advantage not clearly observed in practical implementations
\end{itemize}
\subsection*{Outline of argument}
\begin{itemize}
\item Graph structure influences decrease-key frequency
\item Decrease-key frequency determines theoretical advantage
\item Empirical runtime does not reflect the theory
\item Operation level cost explains the gap between the thoery and reality
\end{itemize}
\subsection*{Scope}
\begin{itemize}
\item Python implementation only
\item Synthetic directed graphs (using outdegree-based generation)
\item Lognormal edge weight distribution
\item Nodes: 2000--16000, density: 0.00015--0.0384, stds: 1000--16000, trials: 200
\item Single-source single-target shortest path
\end{itemize}
\subsection*{Worthiness}
\begin{itemize}
\item Evaluates limits of asymptotic complexity in practice
\item Provides empirical basis for heap selection
\item Bridges theory and implementation level cost analysis
\end{itemize}
\section{Theoretical Framework}
\begin{itemize}
\item Formal runtime models:
\begin{itemize}
\item $T_B = c_1 E \log V$
\item $T_F = c_2 E + c_3 V \log V$
\end{itemize}
\item Role of decrease-key in Dijkstra
\item Relation between relax\_success and decrease-key calls
\item Theoretical crossover condition:
\[
T_F < T_B
\]
\end{itemize}
\section{Methodology}
\subsection*{Development}
\begin{itemize}
\item Real-world road dataset used to estimate distribution of real-world data
\item Lognormal parameters derived from real data
\item Synthetic graph generation with various nodes, density, std
\item Measurement metrics:
\begin{itemize}
\item reached
\item extract\_min\_calls
\item relax\_attempts
\item relax\_success
\item relax\_success\_ratio
\item runtime
\end{itemize}
\end{itemize}
\subsection*{Evidence}
\begin{itemize}
\item Experimental configuration
\begin{verbatim}
python -V
Python 3.12.12
pip -V
pip 26.0.1
python -c "import platform; print(platform.platform())"
Linux-6.8.12-17-pve-x86_64-with-glibc2.41
lscpu | head
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 48 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 1-3,8
Off-line CPU(s) list: 0,4-7,9-15
Vendor ID: AuthenticAMD
Model name: AMD Ryzen 7 7700 8-Core Processor
CPU family: 25
\end{verbatim}
\end{itemize}
\subsection*{Analysis}
\begin{itemize}
\item Justification of parameter intervals
\item Retain stability by 200 trials
\end{itemize}
\subsection*{Connection}
\begin{itemize}
\item Method is the fundament of runtime investigation
\end{itemize}
\section{Real Data Analysis}
\subsection*{Development}
\begin{itemize}
\item Get real data from DIMACS
\item Extraction of edge weight distribution
\item Calculation of mean and std
\end{itemize}
\subsection*{Evidence}
\begin{itemize}
\item Edge weight distribition
\begin{figure}[H]
\centering
\includegraphics[width=0.7\textwidth]{hist_original.png}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\textwidth]{hist_log.png}
\end{figure}
\item Summary statistics tableofcontents
\begin{table}[H]
\centering
\begin{tabular}{ll}
\hline
Key & Value \\ \hline
Total edges & 58333344 \\
Mean & 2950.322 \\
Std & 4071.694 \\
Min & 1 \\
Max & 368855 \\ \hline
\end{tabular}
\end{table}
\end{itemize}
\subsection*{Analysis}
\begin{itemize}
\item Lognormal approximation calculation
\item Justification of synthetic distribution parameters
\end{itemize}
\subsection*{Connection}
\begin{itemize}
\item Synthetic data is based on real-world data
\end{itemize}
\section{Relationship between variables}
\subsection*{Development}
\begin{itemize}
\item Density vs relax\_attempts
\item Sigma vs relax\_success\_ratio
\item Decrease-key vs speedup
\end{itemize}
\subsection*{Evidence}
\begin{itemize}
\item Density vs relax\_attempts plot
\begin{figure}[H]
\centering
\includegraphics[width=0.7\textwidth]{density_vs_relax_attempts.png}
\end{figure}
\item Sigma vs relax\_success\_ratio platform
\begin{figure}[H]
\centering
\includegraphics[width=0.7\textwidth]{sigma_vs_relax_ratio.png}
\end{figure}
\item Decrease-key vs speedup correlation
\begin{figure}[H]
\centering
\includegraphics[width=0.7\textwidth]{speedup_corr.png}
\end{figure}
\end{itemize}
\subsection*{Analysis}
\begin{itemize}
\item Density increases relax attempts nearly linearly
\item Higher variance increases relax\_success ratio
\item Speedup weakly correlated with decrease-key frequency
\end{itemize}
\subsection*{Connection}
\begin{itemize}
\item Structural conditions necessary but insufficient for crossover
\end{itemize}
\section{Runtime}
\subsection*{Development}
\begin{itemize}
\item Multiple linear regression of runtime
\item Operation level cost estimation
\end{itemize}
\subsection*{Evidence}
\begin{itemize}
\item Regression summary table
\begin{table}[H]
\small
\centering
\begin{tabular}{llllllll}
\hline
algorithm & intercept & $coef_{add}$ & $coef_{extract}$ & $coef_{relax}$ & $coef_{decrease}$ \\ \hline
binary & 0.000492 & -3.496595e-07 & 3.259253e-07 & 9.095429e-08 & 1.702164e-07 \\
fibonacci & -0.001191 & 7.446706e-07 & 3.938917e-07 & 9.333419e-08 & 1.504052e-06 \\ \hline
\end{tabular}
\end{table}
\item $R^2$ comparison between models
\begin{table}[H]
\centering
\begin{tabular}{lll}
\hline
algorithm & $r_2$ & $n_{samples}$ \\ \hline
binary & 0.980930 & 54801 \\
fibonacci & 0.980877 & 54801 \\ \hline
\end{tabular}
\end{table}
\end{itemize}
\subsection*{Analysis}
\begin{itemize}
\item Fibonacci decrease-key cost significantly larger in Python
\item Constant factors dominate asymptotic differences
\end{itemize}
\subsection*{Connection}
\begin{itemize}
\item Explains absence of empirical speedup
\end{itemize}
\section{Deriving the Crossover Condition}
\subsection*{Development}
\begin{itemize}
\item Substitute empirical coefficients into:
\[
T_F < T_B
\]
\end{itemize}
\subsection*{Evidence}
\begin{itemize}
\item Derived inequality expression
\end{itemize}
\subsection*{Analysis}
\begin{itemize}
\item Required conditions exceed practical graph scale
\item Python constant overhead prevents crossover
\end{itemize}
\subsection*{Connection}
\begin{itemize}
\item Theoretical superiority does not always linked to practical improvement
\end{itemize}
\section{Conclusion}
\subsection*{Conclusion}
\begin{itemize}
\item Fibonacci heap does not outperform Binary heap in Python
\item Operation level costs dominate asymptotic complexity
\item Crossover condition impractical under tested environment
\end{itemize}
\subsection*{Limitation \& Evaulation}
\begin{itemize}
\item Python-only implementation
\item Synthetic graph model assumptions
\item No low-level memory/cache analysis
\end{itemize}
\subsection*{Summarize main points}
\begin{itemize}
\item Graph structure affects decrease-key frequency
\item Empirical runtime does not follow asymptotic expectation
\item Constant factors determine real-world performance
\end{itemize}
\end{document}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long