finish all analysis
@@ -10,4 +10,6 @@ codes/results/
|
|||||||
codes/experiments/real_data/data/
|
codes/experiments/real_data/data/
|
||||||
|
|
||||||
# System
|
# System
|
||||||
|
.claude
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.gitignore
|
||||||
@@ -23,144 +23,4 @@ Model name: AMD Ryzen 7 7700 8-Core Processor
|
|||||||
CPU family: 25
|
CPU family: 25
|
||||||
|
|
||||||
|
|
||||||
|
python3 -m experiments.synthetic_data.run.run
|
||||||
## 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에 대한 명확한 답
|
|
||||||
• 이론과 실제의 간극 강조
|
|
||||||
• 실무적 함의
|
|
||||||
• 한계 및 향후 연구
|
|
||||||
@@ -6,7 +6,7 @@ class DijkstraStats:
|
|||||||
# self.decrease_key_calls = 0
|
# self.decrease_key_calls = 0
|
||||||
# self.add_calls = 0
|
# self.add_calls = 0
|
||||||
|
|
||||||
def heap_dijkstra(nodes, adj, heap, start, end):
|
def heap_dijkstra(nodes, adj, heap, start):
|
||||||
INF = float('inf')
|
INF = float('inf')
|
||||||
dist = [INF] * nodes
|
dist = [INF] * nodes
|
||||||
visited = [False] * nodes
|
visited = [False] * nodes
|
||||||
@@ -28,9 +28,6 @@ def heap_dijkstra(nodes, adj, heap, start, end):
|
|||||||
if cur_dist == INF:
|
if cur_dist == INF:
|
||||||
break
|
break
|
||||||
|
|
||||||
if cur == end:
|
|
||||||
break
|
|
||||||
|
|
||||||
for nxt, d in adj[cur]:
|
for nxt, d in adj[cur]:
|
||||||
stats.relax_attempts += 1
|
stats.relax_attempts += 1
|
||||||
if visited[nxt]:
|
if visited[nxt]:
|
||||||
@@ -43,4 +40,5 @@ def heap_dijkstra(nodes, adj, heap, start, end):
|
|||||||
|
|
||||||
visited[cur] = True
|
visited[cur] = True
|
||||||
|
|
||||||
return dist[end], stats
|
# return dist, stats
|
||||||
|
return stats
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
def pure_dijkstra(nodes, adj, start, end):
|
def pure_dijkstra(nodes, adj, start):
|
||||||
dist = [float('inf')] * nodes
|
dist = [float('inf')] * nodes
|
||||||
dist[start] = 0
|
dist[start] = 0
|
||||||
visited = [False] * nodes
|
visited = [False] * nodes
|
||||||
@@ -9,9 +9,6 @@ def pure_dijkstra(nodes, adj, start, end):
|
|||||||
if cur == -1:
|
if cur == -1:
|
||||||
break
|
break
|
||||||
|
|
||||||
if cur == end:
|
|
||||||
break
|
|
||||||
|
|
||||||
for next, d in adj[cur]:
|
for next, d in adj[cur]:
|
||||||
if not visited[next]:
|
if not visited[next]:
|
||||||
new = dist[cur] + d
|
new = dist[cur] + d
|
||||||
@@ -29,4 +26,4 @@ def pure_dijkstra(nodes, adj, start, end):
|
|||||||
min_val = dist[n]
|
min_val = dist[n]
|
||||||
cur = min_node
|
cur = min_node
|
||||||
|
|
||||||
return dist[end]
|
return dist
|
||||||
@@ -94,6 +94,8 @@ class FiboHeap:
|
|||||||
def extract_min(self):
|
def extract_min(self):
|
||||||
# 1. Find min node
|
# 1. Find min node
|
||||||
min_node = self.min
|
min_node = self.min
|
||||||
|
if min_node is None:
|
||||||
|
return None
|
||||||
self.n -= 1
|
self.n -= 1
|
||||||
|
|
||||||
# 2. Make min node's child into individual tree
|
# 2. Make min node's child into individual tree
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import os
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import random
|
import scipy.stats as stats
|
||||||
|
from scipy.stats import shapiro
|
||||||
|
|
||||||
|
|
||||||
def read_dimacs(file):
|
def read_dimacs(file):
|
||||||
@@ -14,24 +15,38 @@ def read_dimacs(file):
|
|||||||
continue
|
continue
|
||||||
elif line.startswith("p"):
|
elif line.startswith("p"):
|
||||||
_, _, n, e = line.split()
|
_, _, n, e = line.split()
|
||||||
|
nodes = int(n)
|
||||||
edges = int(e)
|
edges = int(e)
|
||||||
data = [0] * edges
|
data = [0] * edges
|
||||||
elif line.startswith("a"):
|
elif line.startswith("a"):
|
||||||
_, u, v, w = line.split()
|
_, u, v, w = line.split()
|
||||||
data[idx] = int(w)
|
data[idx] = int(w)
|
||||||
idx += 1
|
idx += 1
|
||||||
return data
|
return ((nodes, edges), data)
|
||||||
|
|
||||||
|
|
||||||
def visualize_weights(file):
|
def visualize_weights(file):
|
||||||
data = read_dimacs(file)
|
(nodes, edges), data = read_dimacs(file)
|
||||||
sample = np.array(data)
|
data = np.array(data)
|
||||||
|
log_data = np.log(data)
|
||||||
|
|
||||||
|
skew = stats.skew(data)
|
||||||
|
skew_log = stats.skew(log_data)
|
||||||
|
kurt = stats.kurtosis(data) # excess kurtosis (정규=0)
|
||||||
|
kurt_log = stats.kurtosis(log_data)
|
||||||
|
print(f'\n--- Skewness / Kurtosis ---')
|
||||||
|
print(f'Skewness (original): {skew:.4f} (정규 기준: 0)')
|
||||||
|
print(f'Skewness (log): {skew_log:.4f} (정규 기준: 0)')
|
||||||
|
print(f'Kurtosis (original): {kurt:.4f} (정규 기준: 0, excess)')
|
||||||
|
print(f'Kurtosis (log): {kurt_log:.4f} (정규 기준: 0, excess)')
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
save_folder = f"{res_folder}/{file}"
|
save_folder = f"{res_folder}/{file}"
|
||||||
os.makedirs(save_folder, exist_ok=True)
|
os.makedirs(save_folder, exist_ok=True)
|
||||||
|
|
||||||
plt.figure(figsize=(8, 5))
|
plt.figure(figsize=(8, 5))
|
||||||
plt.hist(sample, bins=100)
|
plt.hist(data, bins=100)
|
||||||
plt.title("Edge Weight Distribution (Original Scale)")
|
plt.title("Edge Weight Distribution (Original Scale)")
|
||||||
plt.xlabel("Weight")
|
plt.xlabel("Weight")
|
||||||
plt.ylabel("Frequency")
|
plt.ylabel("Frequency")
|
||||||
@@ -39,10 +54,16 @@ def visualize_weights(file):
|
|||||||
plt.savefig(f"{save_folder}/hist_original.png")
|
plt.savefig(f"{save_folder}/hist_original.png")
|
||||||
plt.close()
|
plt.close()
|
||||||
|
|
||||||
log_sample = np.log(sample[sample > 0])
|
plt.figure(figsize=(8, 5))
|
||||||
|
stats.probplot(data, dist="norm", plot=plt)
|
||||||
|
plt.title("Q-Q Plot (Original Scale)")
|
||||||
|
plt.xlabel("Theoretical Quantiles (Normal)")
|
||||||
|
plt.ylabel("Sample Quantiles (Weight)")
|
||||||
|
plt.savefig(f"{save_folder}/Q-Q_original.png")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
plt.figure(figsize=(8, 5))
|
plt.figure(figsize=(8, 5))
|
||||||
plt.hist(log_sample, bins=100)
|
plt.hist(log_data, bins=100)
|
||||||
plt.title("Log(Weight) Distribution")
|
plt.title("Log(Weight) Distribution")
|
||||||
plt.xlabel("log(Weight)")
|
plt.xlabel("log(Weight)")
|
||||||
plt.ylabel("Frequency")
|
plt.ylabel("Frequency")
|
||||||
@@ -51,37 +72,50 @@ def visualize_weights(file):
|
|||||||
plt.close()
|
plt.close()
|
||||||
|
|
||||||
plt.figure(figsize=(8, 5))
|
plt.figure(figsize=(8, 5))
|
||||||
plt.hist(log_sample, bins=100, density=True)
|
stats.probplot(log_data, dist="norm", plot=plt)
|
||||||
plt.title("Log(Weight) Density")
|
plt.title("Q-Q Plot (Log Weight)")
|
||||||
plt.xlabel("log(Weight)")
|
plt.xlabel("Theoretical Quantiles (Normal)")
|
||||||
plt.ylabel("Density")
|
plt.ylabel("Sample Quantiles (Weight)")
|
||||||
plt.tight_layout()
|
plt.savefig(f"{save_folder}/Q-Q_log.png")
|
||||||
plt.savefig(f"{save_folder}/hist_log_density.png")
|
|
||||||
plt.close()
|
plt.close()
|
||||||
|
|
||||||
|
# plt.figure(figsize=(8, 5))
|
||||||
|
# plt.hist(log_data, 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()
|
||||||
|
|
||||||
|
print(f"finish {file}")
|
||||||
return {
|
return {
|
||||||
"file": file,
|
"file": file,
|
||||||
"edges": len(sample),
|
"nodes": nodes,
|
||||||
"mean": np.mean(sample),
|
"edges": edges,
|
||||||
"std": np.std(sample),
|
"density": edges / (nodes * (nodes - 1)),
|
||||||
"min": np.min(sample),
|
"mean": np.mean(data),
|
||||||
"max": np.max(sample)
|
"std": np.std(data),
|
||||||
|
"min": np.min(data),
|
||||||
|
"max": np.max(data),
|
||||||
|
"skew": stats.skew(data),
|
||||||
|
"skew_log": stats.skew(log_data),
|
||||||
}
|
}
|
||||||
|
|
||||||
folder = "experiments/real_data/data/dimacs_data"
|
folder = "experiments/real_data/data/dimacs_data"
|
||||||
files = [
|
files = [
|
||||||
"USA-road-d.BAY.gr",
|
# "USA-road-d.BAY.gr",
|
||||||
"USA-road-d.CAL.gr",
|
# "USA-road-d.CAL.gr",
|
||||||
"USA-road-d.COL.gr",
|
# "USA-road-d.COL.gr",
|
||||||
"USA-road-d.CTR.gr",
|
# "USA-road-d.CTR.gr",
|
||||||
"USA-road-d.E.gr",
|
# "USA-road-d.E.gr",
|
||||||
"USA-road-d.FLA.gr",
|
# "USA-road-d.FLA.gr",
|
||||||
"USA-road-d.LKS.gr",
|
# "USA-road-d.LKS.gr",
|
||||||
"USA-road-d.NE.gr",
|
# "USA-road-d.NE.gr",
|
||||||
"USA-road-d.NW.gr",
|
# "USA-road-d.NW.gr",
|
||||||
"USA-road-d.NY.gr",
|
# "USA-road-d.NY.gr",
|
||||||
"USA-road-d.USA.gr",
|
"USA-road-d.USA.gr",
|
||||||
"USA-road-d.W.gr"
|
# "USA-road-d.W.gr"
|
||||||
]
|
]
|
||||||
|
|
||||||
res_folder = "results/real_data/derived/dimacs_graph_distribution"
|
res_folder = "results/real_data/derived/dimacs_graph_distribution"
|
||||||
|
|||||||
@@ -0,0 +1,371 @@
|
|||||||
|
import os
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import pandas as pd
|
||||||
|
from sklearn.linear_model import LinearRegression
|
||||||
|
from sklearn.metrics import r2_score
|
||||||
|
from scipy.optimize import curve_fit
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
# Initial Setup
|
||||||
|
csv_file = "results/synthetic_data/raw/20260418_095837.csv"
|
||||||
|
save_folder = f"results/synthetic_data/derived/{os.path.splitext(os.path.basename(csv_file))[0]}/call_number_analysis"
|
||||||
|
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"]
|
||||||
|
df["E"] = df["nodes"] * (df["nodes"] - 1) * df["density"]
|
||||||
|
df["decrease_key"] = df["relax_success"]
|
||||||
|
df["avg_degree"] = (df["nodes"] - 1) * df["density"]
|
||||||
|
|
||||||
|
|
||||||
|
# 1. E vs relax_attempts
|
||||||
|
def E_vs_relax_attempts(df):
|
||||||
|
X = df[["E"]].to_numpy()
|
||||||
|
y = df["relax_attempts"].to_numpy()
|
||||||
|
|
||||||
|
reg = LinearRegression()
|
||||||
|
reg.fit(X, y)
|
||||||
|
y_hat = reg.predict(X)
|
||||||
|
r2 = r2_score(y, y_hat)
|
||||||
|
equation = f"y = {reg.coef_[0]:.4g}x + {reg.intercept_:.4g}\n$R^2$ = {r2:.4f}"
|
||||||
|
|
||||||
|
plt.figure(figsize=(6, 4))
|
||||||
|
plt.scatter(X, y, marker="o")
|
||||||
|
plt.plot(X, y_hat, color="red", label=equation)
|
||||||
|
plt.xlabel("E (edges)")
|
||||||
|
plt.ylabel("Average relax_attempts")
|
||||||
|
plt.title("Edges vs Relax Attempts")
|
||||||
|
plt.legend(loc="upper right", fontsize=8)
|
||||||
|
plt.grid(True)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(f"{save_folder}/E_vs_relax_attempts.png", dpi=300)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
print(f"Saved E vs relax_attempts plots to {save_folder}")
|
||||||
|
|
||||||
|
# E_vs_relax_attempts(df)
|
||||||
|
|
||||||
|
|
||||||
|
# sigma vs relax_success_ratio
|
||||||
|
def sigma_vs_relax_success_ratio(df):
|
||||||
|
rows = []
|
||||||
|
sigma_save_folder = os.path.join(save_folder, "sigma_vs_relax_ratio_controlled")
|
||||||
|
os.makedirs(sigma_save_folder, exist_ok=True)
|
||||||
|
cnt = 1
|
||||||
|
|
||||||
|
for pair, group in df.groupby(["nodes", "density"]):
|
||||||
|
nodes, density = pair
|
||||||
|
grouped = group.groupby("sigma")["relax_success_ratio"].mean()
|
||||||
|
|
||||||
|
sigma_vals = grouped.index.to_numpy()
|
||||||
|
ratio_vals = grouped.to_numpy()
|
||||||
|
|
||||||
|
valid = np.isfinite(ratio_vals) # When attempts = 0 -> ratio becomes infinite.
|
||||||
|
sigma_vals = sigma_vals[valid]
|
||||||
|
ratio_vals = ratio_vals[valid]
|
||||||
|
|
||||||
|
if len(sigma_vals) < 2: # When too many invalid values -> Can't do regression.
|
||||||
|
plt.close()
|
||||||
|
cnt += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
log_sigma = np.log(sigma_vals).reshape(-1, 1)
|
||||||
|
reg = LinearRegression()
|
||||||
|
reg.fit(log_sigma, ratio_vals)
|
||||||
|
r2 = r2_score(ratio_vals, reg.predict(log_sigma))
|
||||||
|
|
||||||
|
sigma_line = np.linspace(sigma_vals.min(), sigma_vals.max(), 200)
|
||||||
|
ratio_line = reg.predict(np.log(sigma_line).reshape(-1, 1))
|
||||||
|
equation = f"ratio = {reg.coef_[0]:.4g}$\\cdot \\ln{{\\sigma}}$ + {reg.intercept_:.4g}\n$R^2$ = {r2:.4f}"
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(6, 4))
|
||||||
|
ax.scatter(sigma_vals, ratio_vals, marker="o")
|
||||||
|
ax.plot(sigma_line, ratio_line, color="red", label=equation)
|
||||||
|
ax.set_xlabel("Sigma")
|
||||||
|
ax.set_ylabel("Relax Success Ratio")
|
||||||
|
ax.set_title(
|
||||||
|
f"Sigma vs Relax Success Ratio\n" f"nodes={nodes}, density={density}"
|
||||||
|
)
|
||||||
|
plt.legend(loc="upper left", fontsize=8)
|
||||||
|
ax.grid(True)
|
||||||
|
fig.tight_layout()
|
||||||
|
fname = (
|
||||||
|
f"{cnt}. nodes{nodes}_density{density:.20f}".rstrip("0").rstrip(".")
|
||||||
|
+ ".png"
|
||||||
|
)
|
||||||
|
fig.savefig(os.path.join(sigma_save_folder, fname), dpi=300)
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
rows.append(
|
||||||
|
{
|
||||||
|
"nodes": nodes,
|
||||||
|
"density": density,
|
||||||
|
"avg_degree": (nodes - 1) * density,
|
||||||
|
"coef": reg.coef_[0],
|
||||||
|
"intercept": reg.intercept_,
|
||||||
|
"r2": r2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cnt += 1
|
||||||
|
|
||||||
|
res = pd.DataFrame(rows)
|
||||||
|
res.to_csv(os.path.join(sigma_save_folder, "r2.csv"))
|
||||||
|
print(f"Saved controlled sigma vs relax_success_ratio plots to {sigma_save_folder}")
|
||||||
|
|
||||||
|
# sigma_vs_relax_success_ratio(df)
|
||||||
|
|
||||||
|
|
||||||
|
# avg_degree vs relax_success_ratio
|
||||||
|
def avg_degree_vs_relax_success_ratio(df):
|
||||||
|
avgdeg_scatter_folder = os.path.join(save_folder, "avg_deg_vs_ratio_controlled")
|
||||||
|
os.makedirs(avgdeg_scatter_folder, exist_ok=True)
|
||||||
|
cnt = 1
|
||||||
|
|
||||||
|
for pair, group in df.groupby(["nodes", "sigma"]):
|
||||||
|
nodes, sigma = pair
|
||||||
|
grouped = group.groupby("avg_degree")["relax_success_ratio"].mean().reset_index()
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(6, 4))
|
||||||
|
ax.plot(grouped["avg_degree"], grouped["relax_success_ratio"], marker="o")
|
||||||
|
ax.set_xlabel("Avg Degree = (N-1) × density")
|
||||||
|
ax.set_ylabel("Relax Success Ratio")
|
||||||
|
ax.set_title(
|
||||||
|
f"Avg Degree vs Relax Success Ratio\n"
|
||||||
|
f"nodes={nodes}, sigma={sigma:.4f}"
|
||||||
|
)
|
||||||
|
ax.grid(True)
|
||||||
|
fig.tight_layout()
|
||||||
|
fname = f"{cnt}. nodes{nodes}_sigma{sigma:.6f}.png"
|
||||||
|
fig.savefig(os.path.join(avgdeg_scatter_folder, fname), dpi=300)
|
||||||
|
plt.close(fig)
|
||||||
|
cnt += 1
|
||||||
|
|
||||||
|
print(f"Saved avg_degree vs ratio plots to {avgdeg_scatter_folder}")
|
||||||
|
|
||||||
|
# avg_degree_vs_relax_success_ratio(df)
|
||||||
|
|
||||||
|
|
||||||
|
# log_avg_degree vs log_ratio
|
||||||
|
def log_avg_degree_vs_log_ratio():
|
||||||
|
CEILING = 1
|
||||||
|
avgdeg_loglog_folder = os.path.join(save_folder, "avgdeg_vs_ratio_loglog")
|
||||||
|
os.makedirs(avgdeg_loglog_folder, exist_ok=True)
|
||||||
|
cnt = 0.99
|
||||||
|
|
||||||
|
for pair, group in df.groupby(["nodes", "sigma"]):
|
||||||
|
nodes, sigma = pair
|
||||||
|
grouped = group.groupby("avg_degree")["relax_success_ratio"].mean().reset_index()
|
||||||
|
grouped = grouped[grouped["relax_success_ratio"] > 0]
|
||||||
|
|
||||||
|
deg_all = grouped["avg_degree"].to_numpy(dtype=float)
|
||||||
|
ratio_all = grouped["relax_success_ratio"].to_numpy(dtype=float)
|
||||||
|
|
||||||
|
ceiling_mask = ratio_all >= CEILING
|
||||||
|
decline_mask = ~ceiling_mask
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(6, 4))
|
||||||
|
|
||||||
|
if ceiling_mask.any():
|
||||||
|
ax.scatter(
|
||||||
|
deg_all[ceiling_mask],
|
||||||
|
ratio_all[ceiling_mask],
|
||||||
|
color="gray",
|
||||||
|
label="ratio ≥ 0.99 (ceiling)",
|
||||||
|
)
|
||||||
|
|
||||||
|
if decline_mask.sum() >= 2:
|
||||||
|
x_dec = deg_all[decline_mask]
|
||||||
|
y_dec = ratio_all[decline_mask]
|
||||||
|
log_x = np.log2(x_dec)
|
||||||
|
log_y = np.log2(y_dec)
|
||||||
|
|
||||||
|
reg = LinearRegression()
|
||||||
|
reg.fit(log_x.reshape(-1, 1), log_y)
|
||||||
|
r2 = r2_score(log_y, reg.predict(log_x.reshape(-1, 1)))
|
||||||
|
slope = reg.coef_[0]
|
||||||
|
intercept = reg.intercept_
|
||||||
|
|
||||||
|
log_x_line = np.linspace(log_x.min(), log_x.max(), 200)
|
||||||
|
x_line = 2 ** log_x_line
|
||||||
|
y_line = 2 ** (slope * log_x_line + intercept)
|
||||||
|
|
||||||
|
ax.scatter(
|
||||||
|
x_dec, y_dec, color="blue", label="declining points"
|
||||||
|
)
|
||||||
|
ax.plot(
|
||||||
|
x_line,
|
||||||
|
y_line,
|
||||||
|
color="blue",
|
||||||
|
linestyle="--",
|
||||||
|
label=f"fit (slope={slope:.3f}, R²={r2:.3f})",
|
||||||
|
)
|
||||||
|
elif decline_mask.any():
|
||||||
|
ax.scatter(
|
||||||
|
deg_all[decline_mask],
|
||||||
|
ratio_all[decline_mask],
|
||||||
|
color="blue",
|
||||||
|
label="declining points",
|
||||||
|
)
|
||||||
|
|
||||||
|
ax.set_xscale("log")
|
||||||
|
ax.set_yscale("log")
|
||||||
|
ax.set_xlabel("Avg Degree (log scale)")
|
||||||
|
ax.set_ylabel("Relax Success Ratio (log scale)")
|
||||||
|
ax.set_title(
|
||||||
|
f"Avg Degree vs Relax Success Ratio (log-log)\n"
|
||||||
|
f"nodes={nodes}, sigma={sigma:.4f}"
|
||||||
|
)
|
||||||
|
ax.legend(fontsize=8)
|
||||||
|
ax.grid(True)
|
||||||
|
fig.tight_layout()
|
||||||
|
fname = f"{cnt}. nodes{nodes}_sigma{sigma:.6f}.png"
|
||||||
|
fig.savefig(os.path.join(avgdeg_loglog_folder, fname), dpi=300)
|
||||||
|
plt.close(fig)
|
||||||
|
cnt += 1
|
||||||
|
|
||||||
|
print(f"Saved avg_degree vs ratio log-log plots to {avgdeg_loglog_folder}")
|
||||||
|
|
||||||
|
# log_avg_degree_vs_log_ratio()
|
||||||
|
|
||||||
|
|
||||||
|
def regime_distribution():
|
||||||
|
r2_csv = os.path.join(save_folder, "sigma_vs_relax_ratio_controlled", "r2.csv")
|
||||||
|
res = pd.read_csv(r2_csv)
|
||||||
|
|
||||||
|
avg_deg = res["avg_degree"].to_numpy() # (nodes-1)*density
|
||||||
|
r2 = res["r2"].to_numpy()
|
||||||
|
coef = res["coef"].to_numpy()
|
||||||
|
intercept = res["intercept"].to_numpy()
|
||||||
|
|
||||||
|
# ── Phase classification ──────────────────────────────────────────────────
|
||||||
|
tree_mask = (intercept == 1) & (np.abs(coef) == 0)
|
||||||
|
giant_mask = (~tree_mask) & (r2 >= 0.9)
|
||||||
|
trans_mask = (~tree_mask) & (~giant_mask)
|
||||||
|
|
||||||
|
labels = np.empty(len(res), dtype=object)
|
||||||
|
labels[tree_mask] = "Tree"
|
||||||
|
labels[giant_mask] = "Giant"
|
||||||
|
labels[trans_mask] = "Transition"
|
||||||
|
|
||||||
|
print("\n=== Phase counts ===")
|
||||||
|
for phase in ["Tree", "Transition", "Giant"]:
|
||||||
|
print(f" {phase}: {(labels == phase).sum()}")
|
||||||
|
|
||||||
|
regime_save_folder = os.path.join(save_folder, "regime_distribution")
|
||||||
|
os.makedirs(regime_save_folder, exist_ok=True)
|
||||||
|
|
||||||
|
colors = {"Tree": "gray", "Transition": "darkorange", "Giant": "steelblue"}
|
||||||
|
bins = np.logspace(
|
||||||
|
np.log10(avg_deg[avg_deg > 0].min()),
|
||||||
|
np.log10(avg_deg.max()),
|
||||||
|
40
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Histogram: avg_degree distribution per phase ──────────────────────────
|
||||||
|
fig, ax = plt.subplots(figsize=(8, 5))
|
||||||
|
for phase, color in colors.items():
|
||||||
|
vals = avg_deg[labels == phase]
|
||||||
|
ax.hist(vals, bins=bins, alpha=0.6, color=color, label=phase)
|
||||||
|
ax.axvline(1.0, color="red", linestyle="--", linewidth=1.5, label="avg_degree = 1")
|
||||||
|
ax.set_xscale("log")
|
||||||
|
ax.set_xlabel("Avg Degree = (N-1) × density (log scale)")
|
||||||
|
ax.set_ylabel("Count")
|
||||||
|
ax.set_title("Phase Distribution by Avg Degree")
|
||||||
|
ax.legend(fontsize=9)
|
||||||
|
ax.grid(True, which="both", alpha=0.4)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(os.path.join(regime_save_folder, "phase_histogram.png"), dpi=300)
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
# ── Scatter: avg_degree vs R², colored by phase ───────────────────────────
|
||||||
|
fig, ax = plt.subplots(figsize=(8, 5))
|
||||||
|
for phase, color in colors.items():
|
||||||
|
mask = labels == phase
|
||||||
|
ax.scatter(avg_deg[mask], r2[mask], s=15, alpha=0.6, color=color, label=phase)
|
||||||
|
ax.axvline(1.0, color="red", linestyle="--", linewidth=1.5, label="avg_degree = 1")
|
||||||
|
ax.set_xscale("log")
|
||||||
|
ax.set_xlabel("Avg Degree (log scale)")
|
||||||
|
ax.set_ylabel("R²")
|
||||||
|
ax.set_title("R² vs Avg Degree, colored by phase")
|
||||||
|
ax.legend(fontsize=9)
|
||||||
|
ax.grid(True, which="both", alpha=0.4)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(os.path.join(regime_save_folder, "r2_by_phase.png"), dpi=300)
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
print(f"Saved regime distribution plots to {regime_save_folder}")
|
||||||
|
|
||||||
|
# regime_distribution()
|
||||||
|
|
||||||
|
|
||||||
|
# Nonlinear regression: r = (a·ln(sigma) + b) · avg_deg^c
|
||||||
|
def nonlinear_regression(df):
|
||||||
|
# Filter: giant component regime only
|
||||||
|
sub = df[(df["avg_degree"] >= 1) & (df["relax_success_ratio"] < 0.99)].copy()
|
||||||
|
sub = sub[sub["relax_success_ratio"] > 0].dropna(subset=["relax_success_ratio", "avg_degree", "sigma"])
|
||||||
|
|
||||||
|
avg_deg = sub["avg_degree"].to_numpy()
|
||||||
|
sigma = sub["sigma"].to_numpy()
|
||||||
|
r = sub["relax_success_ratio"].to_numpy()
|
||||||
|
|
||||||
|
print(f"Fitting on {len(sub)} data points")
|
||||||
|
|
||||||
|
def model(X, a, b, c):
|
||||||
|
avg_deg_, sigma_ = X
|
||||||
|
return (a * np.log(sigma_) + b) * avg_deg_ ** c
|
||||||
|
|
||||||
|
# Initial guess
|
||||||
|
p0 = [0.05, 0.5, -0.7]
|
||||||
|
|
||||||
|
popt, pcov = curve_fit(model, (avg_deg, sigma), r, p0=p0, maxfev=10000)
|
||||||
|
a, b, c = popt
|
||||||
|
perr = np.sqrt(np.diag(pcov))
|
||||||
|
|
||||||
|
r_pred = model((avg_deg, sigma), a, b, c)
|
||||||
|
ss_res = np.sum((r - r_pred) ** 2)
|
||||||
|
ss_tot = np.sum((r - r.mean()) ** 2)
|
||||||
|
r2 = 1 - ss_res / ss_tot
|
||||||
|
|
||||||
|
print("\n=== Nonlinear regression: r = (a·ln(σ) + b) · avg_deg^c ===")
|
||||||
|
print(f" a = {a:.6f} ± {perr[0]:.6f}")
|
||||||
|
print(f" b = {b:.6f} ± {perr[1]:.6f}")
|
||||||
|
print(f" c = {c:.6f} ± {perr[2]:.6f}")
|
||||||
|
print(f" R² = {r2:.4f}")
|
||||||
|
|
||||||
|
# ── Predicted vs Actual ───────────────────────────────────────────────────
|
||||||
|
nlr_save_folder = os.path.join(save_folder, "nonlinear_regression")
|
||||||
|
os.makedirs(nlr_save_folder, exist_ok=True)
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(6, 5))
|
||||||
|
ax.scatter(r, r_pred, s=5, alpha=0.3, color="steelblue")
|
||||||
|
mn, mx = min(r.min(), r_pred.min()), max(r.max(), r_pred.max())
|
||||||
|
ax.plot([mn, mx], [mn, mx], color="red", linewidth=1.5, linestyle="--")
|
||||||
|
ax.set_xlabel("Actual ratio")
|
||||||
|
ax.set_ylabel("Predicted ratio")
|
||||||
|
ax.set_title(f"Nonlinear fit: r = (a·ln(σ)+b)·avg_deg^c\nR²={r2:.4f}")
|
||||||
|
ax.grid(True, alpha=0.4)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(os.path.join(nlr_save_folder, "predicted_vs_actual.png"), dpi=300)
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
# ── Residuals by nodes ────────────────────────────────────────────────────
|
||||||
|
residuals = r - r_pred
|
||||||
|
fig, ax = plt.subplots(figsize=(7, 5))
|
||||||
|
for n in sorted(sub["nodes"].unique()):
|
||||||
|
mask = sub["nodes"].to_numpy() == n
|
||||||
|
ax.scatter(r_pred[mask], residuals[mask], s=5, alpha=0.4, label=f"N={n}")
|
||||||
|
ax.axhline(0, color="red", linewidth=1.2, linestyle="--")
|
||||||
|
ax.set_xlabel("Predicted ratio")
|
||||||
|
ax.set_ylabel("Residual")
|
||||||
|
ax.set_title("Residuals by N")
|
||||||
|
ax.legend(fontsize=6, ncol=3, markerscale=2)
|
||||||
|
ax.grid(True, alpha=0.4)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(os.path.join(nlr_save_folder, "residuals_by_N.png"), dpi=300)
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
print(f"Saved nonlinear regression plots to {nlr_save_folder}")
|
||||||
|
|
||||||
|
nonlinear_regression(df)
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import os
|
|
||||||
import pandas as pd
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
csv_file = "results/synthetic_data/raw/20260303_083239.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,190 @@
|
|||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import os
|
||||||
|
from sklearn.linear_model import LinearRegression
|
||||||
|
from sklearn.metrics import r2_score
|
||||||
|
|
||||||
|
# Runtime = Add + Extract-min + Relax-attempts + Relax-success
|
||||||
|
# = VlogV * k1 + VlogV * k2 + E * k3 + \alpha * logV * k4 (Binary)
|
||||||
|
# = V * k1 + VlogV * k2 + E * k3 + \alpha * k4 (Fibonacci)
|
||||||
|
# (\alpha = relax_success)
|
||||||
|
#
|
||||||
|
# V increases -> Add, Extract-min, Relax-attempts, Relax-success all increases.
|
||||||
|
# Therfore, critical multicollinearlity occurs.
|
||||||
|
#
|
||||||
|
# To solve, run regression per each V.
|
||||||
|
# Then V becomes constant.
|
||||||
|
#
|
||||||
|
# Runtime(Binary) = VlogV * k1 + VlogV * k2 + E * k3 + \alpha * logV * k4 (Binary)
|
||||||
|
# = intercept + E * k3 + \alpha * k4
|
||||||
|
# Runtime(Fibonacci) = V * k1 + VlogV * k2 + E * k3 + \alpha * k4 (Fibonacci)
|
||||||
|
# = intercept + E * k3 + \alpha * k4
|
||||||
|
#
|
||||||
|
# Final equation
|
||||||
|
# Runtime of specific V = intercept + E * k3 + \alpha * k4
|
||||||
|
#
|
||||||
|
# intercept absorbs V*(k1+k2) which is constant within each N.
|
||||||
|
# k3: cost per relax_attempt (should be equal across heaps)
|
||||||
|
# k4: cost per decrease-key (binary: should scale with log2N; fibonacci: should be constant)
|
||||||
|
|
||||||
|
csv_file = "results/synthetic_data/raw/20260418_095837.csv"
|
||||||
|
save_folder = f"results/synthetic_data/derived/{os.path.splitext(os.path.basename(csv_file))[0]}/processing_time_analysis"
|
||||||
|
os.makedirs(save_folder, exist_ok=True)
|
||||||
|
|
||||||
|
df = pd.read_csv(csv_file)
|
||||||
|
nodes = sorted(df["nodes"].unique())
|
||||||
|
algos = ["binary", "fibonacci"]
|
||||||
|
|
||||||
|
records = []
|
||||||
|
|
||||||
|
for algo in algos:
|
||||||
|
sub = df[df["algorithm"] == algo]
|
||||||
|
for n in nodes:
|
||||||
|
g = sub[sub["nodes"] == n]
|
||||||
|
|
||||||
|
X = g[["relax_attempts", "relax_success"]].to_numpy()
|
||||||
|
y = g["time"].to_numpy()
|
||||||
|
|
||||||
|
model = LinearRegression().fit(X, y)
|
||||||
|
r2 = r2_score(y, model.predict(X))
|
||||||
|
|
||||||
|
records.append({
|
||||||
|
"algorithm": algo,
|
||||||
|
"N": n,
|
||||||
|
"log2N": round(np.log2(n), 4),
|
||||||
|
"intercept": model.intercept_,
|
||||||
|
"k3 (E)": model.coef_[0],
|
||||||
|
"k4 (alpha)":model.coef_[1],
|
||||||
|
"r2": r2,
|
||||||
|
"n": len(g),
|
||||||
|
})
|
||||||
|
|
||||||
|
results = pd.DataFrame(records)
|
||||||
|
|
||||||
|
for algo in algos:
|
||||||
|
print(f"=== {algo} ===")
|
||||||
|
r = results[results["algorithm"] == algo][["N","log2N","intercept","k3 (E)","k4 (alpha)","r2","n"]]
|
||||||
|
print(r.to_string(index=False))
|
||||||
|
print()
|
||||||
|
|
||||||
|
grouped = results.set_index(["N", "algorithm"])
|
||||||
|
|
||||||
|
def get_param(param):
|
||||||
|
return (
|
||||||
|
grouped.loc[(nodes, "binary"), param].values,
|
||||||
|
grouped.loc[(nodes, "fibonacci"), param].values
|
||||||
|
)
|
||||||
|
|
||||||
|
k3_bin, k3_fib = get_param("k3 (E)")
|
||||||
|
k4_bin, k4_fib = get_param("k4 (alpha)")
|
||||||
|
int_bin, int_fib = get_param("intercept")
|
||||||
|
|
||||||
|
# binary: k4 ~ log₂N 회귀
|
||||||
|
log_n = np.log2(np.array(nodes)).reshape(-1, 1)
|
||||||
|
bin_reg = LinearRegression().fit(log_n, k4_bin)
|
||||||
|
r2_bin = r2_score(k4_bin, bin_reg.predict(log_n))
|
||||||
|
|
||||||
|
n_line = np.linspace(min(nodes), max(nodes), 300)
|
||||||
|
log_n_line = np.log2(n_line).reshape(-1, 1)
|
||||||
|
|
||||||
|
print(f"\nbinary log₂N regression: coef={bin_reg.coef_[0]:.4e}, intercept={bin_reg.intercept_:.4e}, R²={r2_bin:.4f}")
|
||||||
|
|
||||||
|
# Plot
|
||||||
|
fig, ax = plt.subplots(figsize=(7, 5))
|
||||||
|
ax.plot(nodes, k4_bin, marker="o", color="steelblue", label="binary k4")
|
||||||
|
ax.plot(nodes, k4_fib, marker="s", color="darkorange", label="fibonacci k4")
|
||||||
|
ax.plot(n_line, bin_reg.predict(log_n_line), color="steelblue", linestyle="--", linewidth=1.5,
|
||||||
|
label=f"binary fit (∝ log₂N) R²={r2_bin:.3f}")
|
||||||
|
ax.set_xlabel("N (nodes)")
|
||||||
|
ax.set_ylabel("k4 — decrease-key cost per call [s]")
|
||||||
|
ax.set_title("k4 (decrease-key unit cost) vs N")
|
||||||
|
ax.legend(fontsize=8)
|
||||||
|
ax.grid(True, alpha=0.4)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(f"{save_folder}/k4_vs_N.png", dpi=300)
|
||||||
|
plt.close(fig)
|
||||||
|
print(f"Saved: {save_folder}/k4_vs_N.png")
|
||||||
|
|
||||||
|
|
||||||
|
# k3
|
||||||
|
fig, ax = plt.subplots(figsize=(7, 5))
|
||||||
|
ax.plot(nodes, k3_bin, marker="o", color="steelblue", label="binary k4")
|
||||||
|
ax.plot(nodes, k3_fib, marker="s", color="darkorange", label="fibonacci k4")
|
||||||
|
ax.set_xlabel("N (nodes)")
|
||||||
|
# ax.set_ylabel("k3 — decrease-key cost per call [s]")
|
||||||
|
# ax.set_title("k3 (decrease-key unit cost) vs N")
|
||||||
|
ax.legend(fontsize=8)
|
||||||
|
ax.grid(True, alpha=0.4)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(f"{save_folder}/k3_vs_N.png", dpi=300)
|
||||||
|
plt.close(fig)
|
||||||
|
print(f"Saved: {save_folder}/k3_vs_N.png")
|
||||||
|
|
||||||
|
|
||||||
|
# Intercept
|
||||||
|
fig, ax = plt.subplots(figsize=(7, 5))
|
||||||
|
ax.plot(nodes, int_bin, marker="o", color="steelblue", label="binary k4")
|
||||||
|
ax.plot(nodes, int_fib, marker="s", color="darkorange", label="fibonacci k4")
|
||||||
|
ax.set_xlabel("N (nodes)")
|
||||||
|
# ax.set_ylabel("k4 — decrease-key cost per call [s]")
|
||||||
|
# ax.set_title("k4 (decrease-key unit cost) vs N")
|
||||||
|
ax.legend(fontsize=8)
|
||||||
|
ax.grid(True, alpha=0.4)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(f"{save_folder}/int_vs_N.png", dpi=300)
|
||||||
|
plt.close(fig)
|
||||||
|
print(f"Saved: {save_folder}/int_vs_N.png")
|
||||||
|
|
||||||
|
|
||||||
|
# ── VIF Check ──────────────────────────────────────────────────────────────────
|
||||||
|
# relax_success = ratio × relax_attempts → 두 변수가 선형 상관될 수 있음
|
||||||
|
# VIF = 1 / (1 - R²), where R² is from regressing X1 on X2 (2-predictor case)
|
||||||
|
# VIF > 10: severe multicollinearity, coefficients become unstable
|
||||||
|
|
||||||
|
vif_records = []
|
||||||
|
|
||||||
|
for algo in algos:
|
||||||
|
sub = df[df["algorithm"] == algo]
|
||||||
|
for n in nodes:
|
||||||
|
g = sub[sub["nodes"] == n]
|
||||||
|
X = g[["relax_attempts", "relax_success"]].to_numpy()
|
||||||
|
|
||||||
|
# Regress relax_attempts on relax_success
|
||||||
|
reg_vif = LinearRegression().fit(X[:, 1].reshape(-1, 1), X[:, 0])
|
||||||
|
r2_vif = r2_score(X[:, 0], reg_vif.predict(X[:, 1].reshape(-1, 1)))
|
||||||
|
vif = 1 / (1 - r2_vif) if r2_vif < 1.0 else float("inf")
|
||||||
|
|
||||||
|
# Pearson correlation (simpler diagnostic)
|
||||||
|
corr = np.corrcoef(X[:, 0], X[:, 1])[0, 1]
|
||||||
|
|
||||||
|
vif_records.append({
|
||||||
|
"algorithm": algo,
|
||||||
|
"N": n,
|
||||||
|
"corr(E, alpha)": round(corr, 6),
|
||||||
|
"R2_vif": round(r2_vif, 6),
|
||||||
|
"VIF": round(vif, 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
vif_df = pd.DataFrame(vif_records)
|
||||||
|
|
||||||
|
for algo in algos:
|
||||||
|
print(f"\n=== VIF — {algo} ===")
|
||||||
|
v = vif_df[vif_df["algorithm"] == algo][["N", "corr(E, alpha)", "R2_vif", "VIF"]]
|
||||||
|
print(v.to_string(index=False))
|
||||||
|
|
||||||
|
# Plot VIF vs N
|
||||||
|
fig, ax = plt.subplots(figsize=(7, 4))
|
||||||
|
for algo, color, marker in [("binary", "steelblue", "o"), ("fibonacci", "darkorange", "s")]:
|
||||||
|
v = vif_df[vif_df["algorithm"] == algo]
|
||||||
|
ax.plot(v["N"], v["VIF"], marker=marker, color=color, label=algo)
|
||||||
|
ax.axhline(10, color="red", linestyle="--", linewidth=1.2, label="VIF = 10 (threshold)")
|
||||||
|
ax.set_xlabel("N (nodes)")
|
||||||
|
ax.set_ylabel("VIF")
|
||||||
|
ax.set_title("VIF (relax_attempts vs relax_success) per N")
|
||||||
|
ax.legend(fontsize=8)
|
||||||
|
ax.grid(True, alpha=0.4)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(f"{save_folder}/vif_vs_N.png", dpi=300)
|
||||||
|
plt.close(fig)
|
||||||
|
print(f"\nSaved: {save_folder}/vif_vs_N.png")
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
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/20260303_083239.csv")
|
|
||||||
summary = op_cost_functional_analysis(df)
|
|
||||||
print(summary)
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import pandas as pd
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import os
|
|
||||||
|
|
||||||
csv_file = "results/synthetic_data/raw/20260303_083239.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,2 @@
|
|||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(6, 4))
|
||||||
@@ -18,7 +18,6 @@ def run_test(
|
|||||||
trials,
|
trials,
|
||||||
mean_dist,
|
mean_dist,
|
||||||
start,
|
start,
|
||||||
end,
|
|
||||||
base_seed
|
base_seed
|
||||||
):
|
):
|
||||||
timer = time.perf_counter
|
timer = time.perf_counter
|
||||||
@@ -55,7 +54,7 @@ def run_test(
|
|||||||
|
|
||||||
bin_heap = BinHeap(nodes)
|
bin_heap = BinHeap(nodes)
|
||||||
st = timer()
|
st = timer()
|
||||||
dist, stats = heap_dijkstra(nodes, adj, bin_heap, start, end)
|
stats = heap_dijkstra(nodes, adj, bin_heap, start)
|
||||||
et = timer()
|
et = timer()
|
||||||
rows.append(
|
rows.append(
|
||||||
{
|
{
|
||||||
@@ -67,7 +66,6 @@ def run_test(
|
|||||||
"seed": seed,
|
"seed": seed,
|
||||||
"time": et - st,
|
"time": et - st,
|
||||||
"algorithm": "binary",
|
"algorithm": "binary",
|
||||||
"reached": dist != INF,
|
|
||||||
"extract_min_calls": stats.extract_min_calls,
|
"extract_min_calls": stats.extract_min_calls,
|
||||||
"relax_attempts": stats.relax_attempts,
|
"relax_attempts": stats.relax_attempts,
|
||||||
"relax_success": stats.relax_success,
|
"relax_success": stats.relax_success,
|
||||||
@@ -76,7 +74,7 @@ def run_test(
|
|||||||
|
|
||||||
fibo_heap = FiboHeap(nodes)
|
fibo_heap = FiboHeap(nodes)
|
||||||
st = timer()
|
st = timer()
|
||||||
dist, stats = heap_dijkstra(nodes, adj, fibo_heap, start, end)
|
stats = heap_dijkstra(nodes, adj, fibo_heap, start)
|
||||||
et = timer()
|
et = timer()
|
||||||
rows.append(
|
rows.append(
|
||||||
{
|
{
|
||||||
@@ -88,7 +86,6 @@ def run_test(
|
|||||||
"seed": seed,
|
"seed": seed,
|
||||||
"time": et - st,
|
"time": et - st,
|
||||||
"algorithm": "fibonacci",
|
"algorithm": "fibonacci",
|
||||||
"reached": dist != INF,
|
|
||||||
"extract_min_calls": stats.extract_min_calls,
|
"extract_min_calls": stats.extract_min_calls,
|
||||||
"relax_attempts": stats.relax_attempts,
|
"relax_attempts": stats.relax_attempts,
|
||||||
"relax_success": stats.relax_success,
|
"relax_success": stats.relax_success,
|
||||||
@@ -110,12 +107,11 @@ def run_test(
|
|||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
nodes_li = [2000, 4000, 8000, 16000]
|
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]
|
densities = [0.0000001, 0.0000003, 0.000001, 0.000003, 0.00001, 0.00003, 0.0001, 0.0003, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3]
|
||||||
stds = [1000, 1500, 2000, 3000, 4000, 6000, 8000, 12000, 16000]
|
stds = [1000, 1500, 2000, 3000, 4000, 6000, 8000, 12000, 16000]
|
||||||
trials = 400
|
trials = 10
|
||||||
mean_dist = 3000
|
mean_dist = 3000
|
||||||
start = 0
|
start = 0
|
||||||
end = 1
|
|
||||||
base_seed = 42
|
base_seed = 42
|
||||||
|
|
||||||
save_folder = "results/synthetic_data/raw"
|
save_folder = "results/synthetic_data/raw"
|
||||||
@@ -131,7 +127,6 @@ config = {
|
|||||||
"trials": trials,
|
"trials": trials,
|
||||||
"mean_dist": mean_dist,
|
"mean_dist": mean_dist,
|
||||||
"start": start,
|
"start": start,
|
||||||
"end": end,
|
|
||||||
"base_seed": base_seed,
|
"base_seed": base_seed,
|
||||||
}
|
}
|
||||||
with open(config_file, "w") as f:
|
with open(config_file, "w") as f:
|
||||||
@@ -144,6 +139,5 @@ df = run_test(
|
|||||||
trials = trials,
|
trials = trials,
|
||||||
mean_dist = mean_dist,
|
mean_dist = mean_dist,
|
||||||
start = start,
|
start = start,
|
||||||
end = end,
|
|
||||||
base_seed = base_seed
|
base_seed = base_seed
|
||||||
)
|
)
|
||||||
|
After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 118 KiB |
@@ -8,6 +8,8 @@
|
|||||||
\usepackage{graphicx}
|
\usepackage{graphicx}
|
||||||
\graphicspath{{images/}}
|
\graphicspath{{images/}}
|
||||||
|
|
||||||
|
\usepackage{amsmath}
|
||||||
|
|
||||||
\usepackage[style=apa,backend=biber]{biblatex} % 출처
|
\usepackage[style=apa,backend=biber]{biblatex} % 출처
|
||||||
\addbibresource{references.bib}
|
\addbibresource{references.bib}
|
||||||
|
|
||||||
@@ -23,12 +25,6 @@
|
|||||||
|
|
||||||
\maketitle
|
\maketitle
|
||||||
|
|
||||||
\begin{abstract}
|
|
||||||
뒷부분 작성을 덜하여 최신 수정 버전은 아래 링크에 업데이트하고 있습니다.
|
|
||||||
|
|
||||||
https://gitea.seung6lee.com/seung6lee/IB-CS-EE/src/branch/main/latex/build/main.pdf
|
|
||||||
\end{abstract}
|
|
||||||
|
|
||||||
\newpage
|
\newpage
|
||||||
|
|
||||||
\tableofcontents
|
\tableofcontents
|
||||||
@@ -36,15 +32,15 @@
|
|||||||
\newpage
|
\newpage
|
||||||
|
|
||||||
\section{Introduction}
|
\section{Introduction}
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item Dijkstra 알고리즘 소개
|
% \item Dijkstra 알고리즘 소개
|
||||||
\item Priority queue 소개
|
% \item Priority queue 소개
|
||||||
\item Binary heap 소개 (간단한 설명과 시간 복잡도)
|
% \item Binary heap 소개 (간단한 설명과 시간 복잡도)
|
||||||
\item Fibonacci heap 소개 (간단한 설명과 시간 복잡도)
|
% \item Fibonacci heap 소개 (간단한 설명과 시간 복잡도)
|
||||||
\item 현실과 이론의 괴리 설명
|
% \item 현실과 이론의 괴리 설명
|
||||||
\item 연구 질문 명시
|
% \item 연구 질문 명시
|
||||||
\item Why does the theoretical asymptotic advantage of Fibonacci heap in Dijkstra's algorithm not translate into practical runtime improvements in Python implementations?
|
% \item Why does the theoretical asymptotic advantage of Fibonacci heap in Dijkstra's algorithm not translate into practical runtime improvements in Python implementations?
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
|
|
||||||
Dijkstra's algorithm is widely used for finding the shortest path in graphs.
|
Dijkstra's algorithm is widely used for finding the shortest path in graphs.
|
||||||
Its performance can be improved by implementing a priority queue.
|
Its performance can be improved by implementing a priority queue.
|
||||||
@@ -55,57 +51,101 @@ Asymptotic time complexity is widely used to evaluate algorithm performance.
|
|||||||
Investigating the gap between theoretical complexity and practical performance is therefore important.
|
Investigating the gap between theoretical complexity and practical performance is therefore important.
|
||||||
Therefore, this study investigates why does the theoretical asymptotic advantage of Fibonacci heap in Dijkstra's algorithm not translate into practical runtime improvements in Python implementations.
|
Therefore, this study investigates why does the theoretical asymptotic advantage of Fibonacci heap in Dijkstra's algorithm not translate into practical runtime improvements in Python implementations.
|
||||||
|
|
||||||
\newpage
|
% \newpage
|
||||||
|
|
||||||
\section{Theoretical Background}
|
\section{Theoretical Background}
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item Dijkstra 알고리즘 원리
|
% \item Dijkstra 알고리즘 원리
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item 특정 노드로 갈 수 있는 최단거리 계속 수정
|
% \item 특정 노드로 갈 수 있는 최단거리 계속 수정
|
||||||
\item 현재 기준 가장 이동거리 짧은 노드 (여기서 priority queue 연결)
|
% \item 현재 기준 가장 이동거리 짧은 노드 (여기서 priority queue 연결)
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
|
|
||||||
\item Priority queue
|
% \item Priority queue
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item 이게 뭔지 설명. add, extract\_min, decrease\_key 3개 설명
|
% \item 이게 뭔지 설명. add, extract\_min, decrease\_key 3개 설명
|
||||||
\item Binary tree
|
% \item Binary tree
|
||||||
\item Fibonacci tree
|
% \item Fibonacci tree
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
|
|
||||||
\item 시간 복잡도
|
% \item 시간 복잡도
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item 어떤 부분에서 차이가 나는지
|
% \item 어떤 부분에서 차이가 나는지
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
|
|
||||||
\subsection{Dijkstra's Algorithm}
|
\subsection{Dijkstra's Algorithm}
|
||||||
|
|
||||||
Dijkstra's algorithm computes the shortest path from a source node to all other nodes in a graph with non-negative edge weights.
|
Dijkstra's algorithm solves the \emph{single-source shortest-path} problem on a finite graph whose edges are assigned nonnegative real weights.
|
||||||
At each step, it selects the unvisited vertex with the smallest distance and relaxes the edges adjacent to that vertex.
|
Given a designated source vertex, the algorithm computes, for every vertex reachable from the source, the minimum total weight of any path from the source to that vertex.
|
||||||
This process is repeated until all vertices have been processed.
|
The pseudocode below records a standard sequential formulation in which each iteration selects the next vertex by scanning the entire unvisited set.
|
||||||
|
|
||||||
To efficiently select the next vertex with the smallest tentative distance, Dijkstra's algorithm typically uses a priority queue.
|
\begin{verbatim}
|
||||||
Without a priority queue, the algorithm must scan all vertices to find the smallest distance, which takes $O(V)$ time for each iteration.
|
dist[v] = ∞ for all vertices v
|
||||||
Since this operation is performed once for each vertex, the total cost of selecting the next vertex becomes $O(V^2)$.
|
unvisited = set of all vertices
|
||||||
Additionally, each edge is relaxed once during the algorithm, which requires $O(E)$ time.
|
|
||||||
Therefore, the total time complexity becomes $O(V^2 + E)$.
|
dist[source] = 0
|
||||||
Using a priority queue can significantly reduce this cost.
|
|
||||||
|
while unvisited is not empty:
|
||||||
|
# Main loop executes once per vertex → total O(V)
|
||||||
|
|
||||||
|
cur = unvisited vertex with minimum dist[cur]
|
||||||
|
# Select vertex with smallest tentative distance
|
||||||
|
# Linear scan over unvisited → O(V) per iteration
|
||||||
|
|
||||||
|
if dist[cur] = ∞:
|
||||||
|
break
|
||||||
|
remove cur from unvisited
|
||||||
|
|
||||||
|
for each neighbor nxt of cur:
|
||||||
|
# Iterate over all adjacent edges
|
||||||
|
# Total number of iterations across algorithm = O(E)
|
||||||
|
# Each iteration is counted as a relaxation attempt
|
||||||
|
|
||||||
|
cand = dist[cur] + weight(cur, nxt)
|
||||||
|
if cand < dist[nxt]:
|
||||||
|
dist[nxt] = cand
|
||||||
|
# Relaxation success
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
endwhile
|
||||||
|
|
||||||
|
return dist
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
The algorithm iterates until all vertices have been processed or there are no more connected nodes.
|
||||||
|
In each iteration, the algorithm finds the unvisited node with the shortest distance and updates the distance of its neighbors.
|
||||||
|
Then, for each neighboring node, compare the existing distance with the distance going through the current node.
|
||||||
|
If going through the current node is shorter, change the distance.
|
||||||
|
By doing this, the algorithm can find the shortest path from the source to all other vertices.
|
||||||
|
|
||||||
|
\subsection{Time Complexity}
|
||||||
|
|
||||||
|
The total time complexity of this algorithm can be calculated by summing the cost of each operation.
|
||||||
|
First, the main loop repeats once for every vertex, resulting in $V$ iterations.
|
||||||
|
In each interation, it tooks $O(V)$ to find the minimum-distance unvisited vertex.
|
||||||
|
Therefore, the total cost of minimum selection is $O(V^2)$.
|
||||||
|
Additionally, during each iteration, the algorithm iterates all neighboring vertices of the current vertex.
|
||||||
|
As each edge is chosen once, neighbor examinations took total $O(E)$ times.
|
||||||
|
Combining these two, the overall time complexity becomes $O(V^2 + E)$.
|
||||||
|
|
||||||
\subsection{Priority Queues}
|
\subsection{Priority Queues}
|
||||||
|
|
||||||
|
The pure dijkstra must scan all vertices to find the smallest distance, which takes $O(V)$ time for each iteration.
|
||||||
|
To efficiently handle this process, a priority queue is typically adopted.
|
||||||
|
|
||||||
A priority queue is a data structure that supports efficient extraction of the element with smallest key.
|
A priority queue is a data structure that supports efficient extraction of the element with smallest key.
|
||||||
This is used in Dijkstra's algorithm to select the vertex with smallest tentative distance.
|
This is used in Dijkstra's algorithm to select the vertex with smallest tentative distance.
|
||||||
|
|
||||||
The main operations required by Dijkstra's algorithm are insert, decrease-key, and extract-min.
|
The main operations required by Dijkstra's algorithm are insert, decrease-key, and extract-min.
|
||||||
As different queues have distinct time complexities in each operation,
|
As different queues have distinct time complexities in each operation, the choice of priority queue implementation determines the overall time complexity of the algorithm.
|
||||||
the choice of priority queue implementation determines the overall time complexity of the algorithm.
|
|
||||||
|
|
||||||
\subsection{Binary heap}
|
\subsection{Binary heap}
|
||||||
|
|
||||||
A binary heap is a heap data structure that is implemented in a complete binary tree satisfying either the min-heap or max-heap property.
|
A binary heap is a heap data structure that is implemented in a complete binary tree satisfying either the min-heap or max-heap property.
|
||||||
|
|
||||||
In Dijkstra's algorithm, it is used as a priority queue that stores vertices with their current tentative distances and supports the operations insert, decrease-key, and extract-min.
|
In Dijkstra's algorithm, it is used as a priority queue that stores vertices with their current tentative distances and supports the operations insert, decrease-key, and extract-min.
|
||||||
As a complete binary tree has height of $\log_{2}{n}$, restoring the heap property after an insertion or key modification requires moving a node up or down the tree by at most $\log_{2}{n}$ times.
|
As a complete binary tree has height of $\log_{2}{V}$, restoring the heap property after an insertion or key modification requires moving a node up or down the tree by at most $\log_{2}{V}$ times.
|
||||||
Therefore, the insert, decrease-key, and extract-min operations each run in $O(\log{n})$ time.
|
Therefore, the insert, decrease-key, and extract-min operations each run in $O(\log{V})$ time.
|
||||||
|
|
||||||
\subsection{Fibonacci Heap}
|
\subsection{Fibonacci Heap}
|
||||||
|
|
||||||
@@ -126,21 +166,60 @@ Although consolidation require significant work, amortized analysis shows that e
|
|||||||
|
|
||||||
As a result, insert and decrease-key operations take $O(1)$ amortized time, while extract-min takes $O(\log n)$ amortized time.
|
As a result, insert and decrease-key operations take $O(1)$ amortized time, while extract-min takes $O(\log n)$ amortized time.
|
||||||
|
|
||||||
|
\begin{verbatim}
|
||||||
|
dist[v] = ∞ for all vertices v
|
||||||
|
dist[source] = 0
|
||||||
|
|
||||||
|
queue = priority queue containing all vertices
|
||||||
|
decrease-key in queue source to 0
|
||||||
|
|
||||||
|
while queue is not empty: # O(V)
|
||||||
|
cur = extract-min from queue # Extract-min O(a)
|
||||||
|
|
||||||
|
for each neighbor nxt of cur: # Relaxation attempt O(E_cur) <- Sum(O(E_i)) = O(E)
|
||||||
|
cand = dist[cur] + weight(cur, nxt)
|
||||||
|
if cand < dist[nxt]:
|
||||||
|
dist[nxt] = cand
|
||||||
|
decrease-key in queue nxt to cand # Relaxation success O(b)
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
endwhile
|
||||||
|
|
||||||
|
return dist
|
||||||
|
|
||||||
|
The entire process is similar to the pure dijkstra.
|
||||||
|
However, it uses priority queue to select current vertex.
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
\subsection{Time Complexity}
|
\subsection{Time Complexity}
|
||||||
|
|
||||||
Originally Dijkstra's algorithm needs $O(V)$ time to select next vertex in each iteration.
|
The time complexity of Dijkstra’s algorithm with a priority queue can be expressed in a general form by separating the cost of key operations.
|
||||||
By using a priority queue, this process can be significantly more efficient.
|
|
||||||
|
|
||||||
Using a binary heap reduces this cost because the extract-min operation takes $O(\log V)$ time.
|
As in the pure version, the main loop iterates once for each vertex, resulting in \(V\) iterations. In each iteration, the algorithm performs an extract-min operation to select the current vertex. Let the cost of this operation be \(O(A)\), which depends on the choice of priority queue. Therefore, the total cost of minimum selection is \(O(AV)\).
|
||||||
Yet, cost of each decrease-key operation increases from $O(1)$ to $O(\log{V})$.
|
|
||||||
The extract-min operation occurs at most $V$ times, while the decrease-key operation occurs up to $E$ times.
|
|
||||||
Therefore, the overall time complexity becomes $O((V + E) \log{V})$.
|
|
||||||
Since number of edges are greater than number of vertices in most graphs,
|
|
||||||
|
|
||||||
Using a fibonacci heap can therotically further reduce the cost.
|
In addition, the algorithm performs relaxation on neighboring vertices. Across the entire execution, each edge is examined once, resulting in \(E\) relaxation attempts. When a shorter path is found, a decrease-key operation is performed. Let the cost of this operation be \(O(B)\). Thus, the total cost of neighbor processing becomes \(O(BE)\).
|
||||||
The decrease-key operation requires $O(\log{V})$ time in a binary heap.
|
|
||||||
A fibonacci heap reduces this cost to $O(1)$ amortized time.
|
Combining these components, the overall time complexity can be expressed as:
|
||||||
Therefore entire time complexity becomes $O(V \log{V} + E)$ amortized.
|
|
||||||
|
\[
|
||||||
|
O(AV + BE)
|
||||||
|
\]
|
||||||
|
|
||||||
|
This formulation allows the effect of different priority queue implementations to be analyzed by substituting their respective operation costs.
|
||||||
|
|
||||||
|
For a binary heap, both extract-min and decrease-key operations require \(O(\log V)\) time.
|
||||||
|
Substituting \(A = \log V\) and \(B = \log V\), the total complexity becomes:
|
||||||
|
|
||||||
|
\[
|
||||||
|
O(V \log V + E \log V)
|
||||||
|
\]
|
||||||
|
|
||||||
|
For a Fibonacci heap, the extract-min operation takes \(O(\log V)\) amortized time, while the decrease-key operation requires only \(O(1)\) amortized time.
|
||||||
|
Substituting \(A = \log V\) and \(B = 1\), the total complexity becomes:
|
||||||
|
|
||||||
|
\[
|
||||||
|
O(V \log V + E)
|
||||||
|
\]
|
||||||
|
|
||||||
\begin{table}[H]
|
\begin{table}[H]
|
||||||
\begin{tabular}{llll}
|
\begin{tabular}{llll}
|
||||||
@@ -153,52 +232,57 @@ Therefore entire time complexity becomes $O(V \log{V} + E)$ amortized.
|
|||||||
\label{tab:heaps_time_complexity}
|
\label{tab:heaps_time_complexity}
|
||||||
\end{table}
|
\end{table}
|
||||||
|
|
||||||
The difference in time complexity between the two heaps mainly arises from the cost of the decrease-key operation.
|
Therfore, as shown in Table~\ref{tab:heaps_time_complexity}, applying binary heaps and fibonacci heaps result in total time complexity of $O(VlogV + ElogV)$ and $O(VlogV + E) amortized$.
|
||||||
As shown in Table \ref{tab:heaps_time_complexity}, the Fibonacci heap has a significantly lower cost for the decrease-key operation.
|
Eventhough is reduced to , but $E$ becomes $E\log{V}$.
|
||||||
This implies that the Fibonacci heap becomes much more efficient as the number of decrease-key operations increases.
|
The reason that dijkstra with queue is faster is that most of graph data in reality are sparse data.
|
||||||
|
Sparse is opposite of dense.
|
||||||
|
Density of graph is calculated as $E/V(V-1)$.
|
||||||
|
|
||||||
However, despite the theoretical advantage of Fibonacci heaps, Dijkstra's algorithm implemented with a binary heap often demonstrates better runtime performance in practical implementations.
|
There are also time complexity difference between different queue types.
|
||||||
Several empirical studies, including \autocite{idowu_2025}, report that binary heaps tend to outperform Fibonacci heaps in real-world applications.
|
dijkstra with binary heap has time complexity of $O(VlogV + ElogV)$ and one with fibonacci heap has time complexity of $O(VlogV + E) amortized$.
|
||||||
This discrepancy between theoretical complexity and practical performance motivates a closer investigation of the factors affecting the runtime behaviour of these priority queue implementations.
|
Looking without the concept of amortized, this difference comes from decrease-key operation.
|
||||||
The following sections investigate this issue through empirical experiments and runtime analysis.
|
|
||||||
|
|
||||||
\newpage
|
However, despite this theoretical advantage, Dijkstra's algorithm implemented with a binary heap often demonstrates better runtime performance in practice.
|
||||||
|
Several empirical studies report that binary heaps outperform Fibonacci heaps in real-world implementations.
|
||||||
|
This discrepancy between theoretical complexity and practical performance motivates further investigation into the factors affecting runtime behaviour.
|
||||||
|
|
||||||
|
% \newpage
|
||||||
\section{Methodology}
|
\section{Methodology}
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item Experimental Environment
|
% \item Experimental Environment
|
||||||
\item 데이터
|
% \item 데이터
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item Dimacs에서 추출
|
% \item Dimacs에서 추출
|
||||||
\item 데이터 개수가 적음 $\rightarrow$ 실제 데이터의 형태만 파악하고 이를 바탕으로 가상 데이터 생성
|
% \item 데이터 개수가 적음 $\rightarrow$ 실제 데이터의 형태만 파악하고 이를 바탕으로 가상 데이터 생성
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
|
|
||||||
\item 그래프 생성
|
% \item 그래프 생성
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item outdegree 방식
|
% \item outdegree 방식
|
||||||
\item 방향 그래프
|
% \item 방향 그래프
|
||||||
\item 평균, 분포, 밀도
|
% \item 평균, 분포, 밀도
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
|
|
||||||
\item 알고리즘 적용
|
% \item 알고리즘 적용
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item dijkstra w/ binary heap
|
% \item dijkstra w/ binary heap
|
||||||
\item dijkstra w/ fibonacci heap
|
% \item dijkstra w/ fibonacci heap
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
|
|
||||||
\item 측정 변수
|
% \item 측정 변수
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item runtime
|
% \item runtime
|
||||||
\item extract\_min\_calls
|
% \item extract\_min\_calls
|
||||||
\item relax\_success (decrease\_key\_call)
|
% \item relax\_success (decrease\_key\_call)
|
||||||
\item relax\_attempts
|
% \item relax\_attempts
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
|
|
||||||
\item 분석
|
% \item 분석
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item correlation
|
% \item correlation
|
||||||
\item regression
|
% \item regression
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
|
|
||||||
\subsection{Experimental Environment}
|
\subsection{Experimental Environment}
|
||||||
|
|
||||||
@@ -209,81 +293,330 @@ Only the priority queue implementation differed between the two versions of Dijk
|
|||||||
|
|
||||||
Standard Python libraries were used for the experiments, and runtime measurements were obtained using Python's built-in timing functions.
|
Standard Python libraries were used for the experiments, and runtime measurements were obtained using Python's built-in timing functions.
|
||||||
|
|
||||||
\subsection{Graph Data}
|
\subsection{Thesis}
|
||||||
|
|
||||||
The key difference between binary heaps and Fibonacci heaps lies in the cost of the decrease-key operation.
|
decrease-key operation takes place when the algorithm finds faster way to go to neibour nodes.
|
||||||
Therefore, evaluating the performance of the two priority queues requires graphs where the number of decrease-key operations varies significantly.
|
Therfore number executed can be explained as relaxation attempt success.
|
||||||
|
This value is affected by two variables; relax-attempts and relax-success-ratio.
|
||||||
|
|
||||||
However, real-world graph datasets rarely allow precise control over the number of decrease-key operations.
|
Relaxation attempts is determined by edge numbers.
|
||||||
As a result, synthetic graph data was generated for the experiments.
|
And edge number can be expressed with nodes number and density.
|
||||||
|
|
||||||
Two major factors influence the frequency of decrease-key operations during the execution of Dijkstra's algorithm.
|
Relaxation success ratio may be affected by lots of variables, but distribution is significant factor.
|
||||||
First, graph density affects the number of relaxation attempts because each edge may trigger a relaxation operation. Graphs with higher density therefore produce more relax-attempts.
|
After finding the distribution of road data, by changing its factor, find the differnce.
|
||||||
Second, the variance of edge weights influences the probability that a relaxation succeeds. Higher variance in edge weights increases the likelihood that newly discovered paths produce shorter distances, resulting in more decrease-key operations.
|
|
||||||
|
|
||||||
% Dimacs 내용 넣기
|
Therefore, the goal is to create various environments by identifying the two variables of variance and density and the correlation between them, and then to analyze whether the time taken for each operation is similar and how different the relax success is.
|
||||||
To construct realistic synthetic graphs, structural properties of real distance graphs were first analyzed, including the distribution, mean, and variance of edge weights. Based on these observations, graphs were generated by controlling both edge density and weight variance.
|
|
||||||
|
|
||||||
The generation process first creates a set of edge weights following a specified distribution with given mean and variance. These weights are then assigned to randomly selected edges between vertices while maintaining the desired graph density.
|
\subsection{Real Data}\label{subsec:real_data}
|
||||||
|
|
||||||
|
The real data to analyze is selected to the DIMACS USA dataset.
|
||||||
|
It was selected from the 9th DIMACS Implementation Challenge: Shortest Paths (2005-2006), organized by Rutgers University's Center for Discrete Mathematics and Theoretical Computer Science, with editors from Microsoft Research and AT\&T Labs Research.
|
||||||
|
The USA graph contains 23,947,347 vertices (road intersections) and 58,333,344 directed edges (road segments), derived from the U.S. Census Bureau's official TIGER/Line database. TIGER was developed in collaboration with the U.S. Geological Survey and became the first nationwide digital map of roads in the United States.
|
||||||
|
Due to its scale, real-world structure, and status as the field's de facto standard benchmark, the dataset has facilitated substantial follow-up work and better experimental standards across the shortest path research community. It has since been adopted in hundreds of peer-reviewed studies as the common basis for algorithm comparison.
|
||||||
|
|
||||||
|
Based on the (Aradhana Singh, 2025), most of road data follow log-normal distribution.
|
||||||
|
To investigate whether the edge weight distribution of the DIMACS USA road network follows a log-normal distribution, we visualized the distribution using a histogram and a Q-Q plot against the normal distribution. We additionally applied a log transformation to the edge weights and repeated the same visualizations on the transformed data. To quantitatively assess normality, we computed skewness and excess kurtosis for both the original and log-transformed distributions, using 0 as the theoretical reference value for each metric under a normal distribution.
|
||||||
|
|
||||||
|
Then, mean and vairance are calulated.
|
||||||
|
|
||||||
|
\begin{equation*}
|
||||||
|
\mu = \overline{\text{edge}}
|
||||||
|
\end{equation*}
|
||||||
|
|
||||||
|
variance was calculated using this equation.
|
||||||
|
\begin{equation*}
|
||||||
|
\text{variance} = \frac{\sum_i{(\text{edge}_i - \mu)^2}}{\text{edge}}
|
||||||
|
\end{equation*}
|
||||||
|
|
||||||
|
graph density was calculated using this equation.
|
||||||
|
\begin{equation*}
|
||||||
|
\text{density} = \frac{\text{edge}}{\text{node}\cdot(\text{node}-1)}
|
||||||
|
\end{equation*}
|
||||||
|
|
||||||
|
\subsection{Synthetic Data}
|
||||||
|
|
||||||
|
To investigate the relationship between graph structure and algorithmic behavior, it is necessary to control key variables such as the number of nodes, graph density, and edge weight distribution.
|
||||||
|
However, real-world road network data does not allow independent control of these variables, as they are inherently fixed and interdependent.
|
||||||
|
Therefore, synthetic graph data was generated based on statistical properties extracted from the real dataset.
|
||||||
|
|
||||||
|
The synthetic graphs were designed to preserve the essential characteristics of real-world road networks while enabling systematic variation of individual parameters.
|
||||||
|
In particular, the mean edge weight observed in the real dataset (approximately 2950) was rounded to 3000 for simplicity, as this difference is negligible relative to the overall scale.
|
||||||
|
The standard deviation of edge weights was varied across a range from 1000 to 16000, centered around the observed real-world value (4071), in order to examine the effect of weight dispersion on relaxation behavior.
|
||||||
|
|
||||||
|
To model the edge weight distribution, a lognormal parameterization was adopted. Empirical analysis of the real dataset showed a highly right-skewed distribution of edge weights, and the lognormal distribution provides a reasonable approximation for such positively skewed data.
|
||||||
|
Using this parameterization, edge weights were generated by specifying the mean and standard deviation, allowing controlled variation in variance while maintaining realistic distributional properties.
|
||||||
|
|
||||||
|
Graph topology was generated using an out-degree-based approach. Each vertex was assigned a number of outgoing edges determined by the target density, ensuring that the overall number of edges satisfied $E \approx d \cdot V(V-1)$, where $d$ is the density.
|
||||||
|
This method was chosen because it allows direct control over graph density while maintaining consistent local connectivity across vertices. Compared to purely random edge sampling, the out-degree approach provides a more stable and interpretable structure for analyzing algorithm behavior.
|
||||||
|
|
||||||
|
The number of nodes was varied exponentially as $[2000, 4000, 8000, 16000]$ to evaluate scalability while maintaining computational feasibility.
|
||||||
|
Similarly, density values were selected using logarithmic spacing across multiple orders of magnitude, ranging from $10^{-7}$ to $3 \times 10^{-2}$.
|
||||||
|
This logarithmic sampling was used to efficiently capture behavioral changes across both extremely sparse and moderately dense graphs, while providing sufficient intermediate resolution to identify transitional effects.
|
||||||
|
|
||||||
|
Although real-world road networks exhibit extremely low density (approximately ), such sparse graphs produce limited variation in relaxation behavior and decrease-key frequency.
|
||||||
|
Therefore, density was intentionally expanded beyond real-world values to explore a broader range of algorithmic conditions. This allows the analysis to identify how graph connectivity influences the frequency of key operations such as relaxation and decrease-key, which are central to the theoretical performance differences between heap implementations.
|
||||||
|
|
||||||
|
Overall, this synthetic data generation approach enables controlled experimentation across a wide range of graph conditions, making it possible to isolate and analyze the impact of structural and statistical variables on Dijkstra's algorithm performance.
|
||||||
|
|
||||||
\subsection{Algorithm Implementation}
|
\subsection{Algorithm Implementation}
|
||||||
|
|
||||||
Using the synthesized graph data, Dijkstra's algorithm was executed with different priority queue implementations.
|
To ensure a fair and controlled comparison between different priority queue implementations, Dijkstra's algorithm, binary heap, and Fibonacci heap were implemented from scratch in Python.
|
||||||
|
|
||||||
Existing Python implementations of Dijkstra's algorithm and heap libraries were not used because their internal optimizations and implementation details could affect runtime performance and make a fair comparison difficult.
|
Existing library implementations were not used, as they may differ in internal optimizations, data structures, and implementation details.
|
||||||
Therefore, Dijkstra's algorithm, as well as both binary heap and Fibonacci heap data structures, were implemented directly in Python.
|
Such differences could introduce uncontrolled variables into the experiment, making it difficult to isolate the effect of the underlying data structure on performance.
|
||||||
|
By implementing all components within a unified environment and following standard algorithmic definitions, the comparison focuses solely on the theoretical characteristics of each data structure.
|
||||||
|
|
||||||
To ensure a fair comparison, both implementations shared the same Dijkstra framework and differed only in the priority queue structure.
|
All implementations were written using only Python's built-in features without external optimization libraries.
|
||||||
All algorithmic principles followed the original descriptions in the foundational papers.
|
This ensures consistency across implementations and minimizes the influence of language-specific optimizations or hidden performance enhancements.
|
||||||
Only standard Python libraries were used in the implementation.
|
|
||||||
|
|
||||||
All source codes are provided in the Appendix.
|
In addition to measuring overall runtime, several internal operation metrics were recorded to analyze the behavior of the algorithm in detail.
|
||||||
|
These metrics include:
|
||||||
|
|
||||||
For each set of graph parameters, synthetic graphs were generated with varying densities and weight distributions.
|
|
||||||
On each generated graph, Dijkstra's algorithm was executed using both heap implementations under identical conditions.
|
|
||||||
Each experiment was repeated multiple times to reduce measurement noise.
|
|
||||||
|
|
||||||
During execution, several metrics were recorded, including the number of extract-min operations, the number of decrease-key operations, and the total runtime.
|
|
||||||
|
|
||||||
\newpage
|
|
||||||
\section{Experimental results}
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item real data analysis result
|
\item \textbf{Runtime}: Total execution time of the algorithm.
|
||||||
\item graph synthesize variable settings
|
\item \textbf{Extract-min calls}: The number of times the minimum element is removed from the priority queue.
|
||||||
\item graph structure experiment (Ex. sigma Vs. relax\_success\_ratio)
|
\item \textbf{Relaxation attempts}: The number of edge relaxations attempted during execution.
|
||||||
\item runtime comparison
|
\item \textbf{Relaxation successes}: The number of times a shorter path is found, resulting in a distance update.
|
||||||
|
\item \textbf{Reached nodes}: The number of nodes that were reached from the source node during execution.
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
|
These measurements allow for a more detailed analysis beyond overall runtime, enabling the investigation of how graph structure influences the frequency of key operations such as extract-min and decrease-key.
|
||||||
|
In particular, relaxation successes correspond directly to decrease-key operations in the priority queue, providing a bridge between theoretical complexity and observed runtime behavior.
|
||||||
|
|
||||||
|
\subsection{Analysis}
|
||||||
|
|
||||||
|
Plot relationship between $E = V(V - 1) \cdot Density$ and relax attempts.
|
||||||
|
Then Find curve that fit the most.
|
||||||
|
Do same thing for variance and relax success ratio.
|
||||||
|
|
||||||
|
Calculate each operation cost using multivariate linear regression.
|
||||||
|
For each type of queue, set add,extract-main,relaxation-attempts, and decrease-key as dependent variables and runtime is independent variable.
|
||||||
|
Using data collected, find the coefficient of each dependent variables.
|
||||||
|
|
||||||
|
% \newpage
|
||||||
|
\section{Experimental results}
|
||||||
|
% \begin{itemize}
|
||||||
|
% \item real data analysis result
|
||||||
|
% \item graph synthesize variable settings
|
||||||
|
% \item graph structure experiment (Ex. sigma Vs. relax\_success\_ratio)
|
||||||
|
% \item runtime comparison
|
||||||
|
% \end{itemize}
|
||||||
|
|
||||||
\subsection{Real Data Properties}
|
\subsection{Real Data Properties}
|
||||||
|
|
||||||
Dimacs
|
This subsection reports exploratory analysis of edge weights in the DIMACS USA instance described in Section~\ref{subsec:real_data}; the conclusions inform the lognormal edge-weight specification used for synthetic graph generation.
|
||||||
|
|
||||||
|
Figure~\ref{fig:hist_original} displays the marginal distribution on the original scale, and Figure~\ref{fig:q-q_original} compares sample quantiles to those of a reference normal distribution with matching mean and variance.
|
||||||
|
The histogram is strongly right-skewed with a pronounced upper tail.
|
||||||
|
The Q-Q plot shows systematic upward curvature relative to the diagonal, indicating heavier right-tail behavior than a Gaussian model and motivating a monotone transformation of the strictly positive weights.
|
||||||
|
|
||||||
\newpage
|
\begin{figure}[H]
|
||||||
\section{Analysis}
|
\centering
|
||||||
\begin{itemize}
|
\includegraphics[width=0.7\textwidth]{hist_original.png}
|
||||||
\item operation cost multiple linear regression
|
\caption{Histogram of DIMACS USA edge weights on the original scale.}
|
||||||
\item crossover condition
|
\label{fig:hist_original}
|
||||||
\item Is it possible in real-world?
|
\end{figure}
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
\newpage
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.7\textwidth]{Q-Q_original.png}
|
||||||
|
\caption{Normal Q-Q plot of DIMACS USA edge weights on the original scale.}
|
||||||
|
\label{fig:q-q_original}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Figure~\ref{fig:hist_log} and Figure~\ref{fig:q-q_log} repeat the diagnostics after applying a logarithmic transformation.
|
||||||
|
The histogram becomes approximately symmetric and unimodal, and the Q-Q plot follows the reference line closely except for minor deviations in the extremes, which is consistent with approximate normality of the transformed weights and hence with a lognormal model on the original scale.
|
||||||
|
|
||||||
|
The Q-Q plot shows strong linearity across the central and upper quantiles, confirming that the log-transformed weights closely follow a normal distribution.
|
||||||
|
The left tail, however, deviates noticeably — sample quantiles cluster near zero rather than tracking the theoretical line.
|
||||||
|
This reflects a boundary effect common in lognormal distributions with large sigma, where probability mass concentrates near zero on the original scale, causing a collapse of low-end values upon log transformation.
|
||||||
|
|
||||||
|
Importantly, this deviation is confined to a small fraction of lower-end observations.
|
||||||
|
The linear trend holds across the bulk of the data, and the alignment with the reference line confirms that the distributional body satisfies the lognormal assumption.
|
||||||
|
The left-tail departure is therefore an artifact of the data generation process, not evidence against lognormality.
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.7\textwidth]{hist_log.png}
|
||||||
|
\caption{Histogram of DIMACS USA edge weights after a logarithmic transformation.}
|
||||||
|
\label{fig:hist_log}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.7\textwidth]{Q-Q_log.png}
|
||||||
|
\caption{Normal Q-Q plot of DIMACS USA edge weights after a logarithmic transformation.}
|
||||||
|
\label{fig:q-q_log}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{table}[H]
|
||||||
|
\centering
|
||||||
|
\begin{tabular}{lcc}
|
||||||
|
\hline
|
||||||
|
Statistic & Original & Log-transformed \\ \hline
|
||||||
|
Skewness & 4.1154 & 0.0281 \\
|
||||||
|
Excess kurtosis & 39.0225 & 0.2218 \\ \hline
|
||||||
|
\end{tabular}
|
||||||
|
\caption{Sample skewness and excess kurtosis of DIMACS USA edge weights before and after a logarithmic transformation. For a normal distribution, both quantities equal zero.}
|
||||||
|
\label{tab:skew_kurtosis_real}
|
||||||
|
\end{table}
|
||||||
|
|
||||||
|
Table~\ref{tab:skew_kurtosis_real} quantifies the change in shape: skewness and excess kurtosis both move sharply toward the normal benchmarks of zero on the transformed scale, corroborating the graphical evidence.
|
||||||
|
|
||||||
|
The empirical sample mean and sample standard deviation of the edge weights are approximately $2950$ and $4071$, respectively.
|
||||||
|
For ease of exposition and to set round nominal parameters in the synthetic experiments that follow, these estimates are represented by $3000$ and $4000$.
|
||||||
|
|
||||||
|
\subsection{Experimental Implementation}
|
||||||
|
|
||||||
|
\begin{verbatim}
|
||||||
|
nodes,density,std,sigma,trial,seed,time,algorithm,reached,extract_min_calls,relax_attempts,relax_success
|
||||||
|
2000,1e-07,1000,0.32459284597450133,1,60566251,0.0006364700384438038,binary,False,2,0,0
|
||||||
|
2000,1e-07,1000,0.32459284597450133,1,60566251,0.0012478521093726158,fibonacci,False,2,0,0
|
||||||
|
2000,1e-07,1000,0.32459284597450133,2,60566248,0.0004217512905597687,binary,False,2,0,0
|
||||||
|
2000,1e-07,1000,0.32459284597450133,2,60566248,0.0010348870418965816,fibonacci,False,2,0,0
|
||||||
|
2000,1e-07,1000,0.32459284597450133,3,60566249,0.00044644903391599655,binary,False,2,0,0
|
||||||
|
2000,1e-07,1000,0.32459284597450133,3,60566249,0.0013218028470873833,fibonacci,False,2,0,0
|
||||||
|
2000,1e-07,1000,0.32459284597450133,4,60566254,0.0004335441626608372,binary,False,2,0,0
|
||||||
|
2000,1e-07,1000,0.32459284597450133,4,60566254,0.001101895235478878,fibonacci,False,2,0,0
|
||||||
|
2000,1e-07,1000,0.32459284597450133,5,60566255,0.0004320708103477955,binary,False,2,0,0
|
||||||
|
2000,1e-07,1000,0.32459284597450133,5,60566255,0.0010821172036230564,fibonacci,False,2,0,0
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
\subsection{Analysis}
|
||||||
|
|
||||||
|
\begin{verbatim}
|
||||||
|
Todo
|
||||||
|
|
||||||
|
1. 상관관계
|
||||||
|
- E <-> Relax attempts
|
||||||
|
- Density, Sigma <-> Relax success ratio
|
||||||
|
- V, Density, Sigma <-> Relax success(Decrease key)
|
||||||
|
- Decrease key <-> runtime ratio
|
||||||
|
|
||||||
|
2. 분포
|
||||||
|
- decrease key up -> runtime ratio > 1 up
|
||||||
|
-> regression result
|
||||||
|
|
||||||
|
3. calculation
|
||||||
|
- 현실 데이터 기반으로 V, Density, Sigma -> Decrease key -> runtime ratio 도출.
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
Edges and relaxation attempts show almost perfect linear relationship.
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.7\textwidth]{E_vs_relax_attempts.png}
|
||||||
|
\caption{Relationship between edges and relaxation attempts}
|
||||||
|
\label{fig:e_vs_relax_attempts}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.7\textwidth]{sigma_vs_relax_ratio.png}
|
||||||
|
\caption{Relationship between variance and relaxation success ratio}
|
||||||
|
\label{fig:sigma_vs_relax_ratio}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
variance and relaxation success ratio shows positve relationship.
|
||||||
|
Specifically, it looks like log or Exponential Saturation function.
|
||||||
|
|
||||||
|
Therefore, it is clear that decrease-key operations occurs more often when density or variance is higher.
|
||||||
|
|
||||||
|
However, unlike this results, in every case binary heap was faster than finonacci heap.
|
||||||
|
Therfore, regression for each operation is considered together.
|
||||||
|
|
||||||
|
Table~\ref{tab:regression_heap_ops} reports the fitted regression, and all operation coefficients except decrease-key are of comparable magnitude across heaps, as expected.
|
||||||
|
However, shockingly, there was high difference in coef decrease key in opposite direction.
|
||||||
|
Fibonacci heap tooks much more time to operate decrease-key.
|
||||||
|
There was almost 10 times difference to finish single decrease-key operation.
|
||||||
|
|
||||||
|
\begin{table}[H]
|
||||||
|
\centering
|
||||||
|
\small
|
||||||
|
\begin{tabular}{lrrrrrrr}
|
||||||
|
\hline
|
||||||
|
Heap & Intercept & Add & Extr.$\cdot\log N$ & Relax & Decrease & $R^2$ & $n$ \\ \hline
|
||||||
|
Binary & $0.000086$ & $-2.449305\times 10^{-7}$ & $2.984790\times 10^{-7}$ & $9.549140\times 10^{-8}$ & $1.762933\times 10^{-7}$ & $0.978281$ & $66972$ \\
|
||||||
|
Fibonacci & $-0.001343$ & $6.499882\times 10^{-7}$ & $3.999607\times 10^{-7}$ & $9.479877\times 10^{-8}$ & $1.579612\times 10^{-6}$ & $0.977307$ & $66972$ \\ \hline
|
||||||
|
\end{tabular}
|
||||||
|
\caption{Multivariate linear regression of wall-clock runtime (seconds) on four predictors: add-call count; extract-min calls multiplied by $\log N$ (natural logarithm of the number of nodes, as in the implementation); relaxation-attempt count; and a decrease-key term that multiplies decrease-key calls by $\log N$ for the binary heap but uses raw decrease-key calls for the Fibonacci heap. Coefficients are seconds per unit of the corresponding predictor; $n$ is the number of observations.}
|
||||||
|
\label{tab:regression_heap_ops}
|
||||||
|
\end{table}
|
||||||
|
|
||||||
|
This cause make binary heap perform better in the practice.
|
||||||
|
The reason this happens is based on how it developed.
|
||||||
|
In python binary heap is made with array. So it has small memory overhead and modifying is fast.
|
||||||
|
However, in fibonacci heap, each node is saved as individual object. This causes huge memory overhead and tooks long to modify.
|
||||||
|
|
||||||
|
Therfore there are no situation that fibonaci heap is faster.
|
||||||
|
|
||||||
|
% \newpage
|
||||||
\section{Discussion}
|
\section{Discussion}
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item asymptotic complexity 한계
|
% \item asymptotic complexity 한계
|
||||||
\item constant factor 중요성
|
% \item constant factor 중요성
|
||||||
\item algorithm engineering 관점
|
% \item algorithm engineering 관점
|
||||||
\item Python implementation 영향
|
% \item Python implementation 영향
|
||||||
\item 다른 언어에서는 달라질 가능성
|
% \item 다른 언어에서는 달라질 가능성
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
|
|
||||||
\newpage
|
The theoretical advantage of the Fibonacci heap in Dijkstra's algorithm arises from its lower asymptotic complexity for the decrease-key operation.
|
||||||
|
While a binary heap requires \(O(\log V)\) time for this operation, the Fibonacci heap reduces it to \(O(1)\) amortized time. Based on this analysis, the Fibonacci heap is expected to outperform the binary heap, particularly in graphs with a large number of edges.
|
||||||
|
|
||||||
|
However, the experimental results show that the binary heap consistently achieves better runtime performance than the Fibonacci heap under the tested conditions.
|
||||||
|
This discrepancy indicates that asymptotic complexity alone is insufficient to explain practical performance.
|
||||||
|
|
||||||
|
A key explanation for this phenomenon lies in the difference in constant factors associated with each data structure.
|
||||||
|
Although the Fibonacci heap has a better theoretical bound, it relies on a more complex structure involving multiple trees, pointer-based node connections, and cascading operations.
|
||||||
|
These features introduce significant overhead in each operation.
|
||||||
|
In contrast, the binary heap is implemented using a simple array-based structure, allowing efficient memory access and minimal overhead.
|
||||||
|
|
||||||
|
This difference can be interpreted through a more detailed runtime model. The total runtime of Dijkstra's algorithm can be expressed as the sum of operation counts multiplied by their respective costs:
|
||||||
|
|
||||||
|
\[
|
||||||
|
T = c_1 \cdot (\text{extract-min operations}) + c_2 \cdot (\text{decrease-key operations})
|
||||||
|
\]
|
||||||
|
|
||||||
|
where \(c_1\) and \(c_2\) represent the actual cost of each operation. Although the Fibonacci heap reduces the asymptotic cost of the decrease-key operation, the corresponding constant \(c_2\) is significantly larger due to implementation overhead.
|
||||||
|
As a result, the practical runtime is dominated by these constant factors rather than asymptotic differences.
|
||||||
|
|
||||||
|
From the perspective of algorithm engineering, this result highlights an important limitation of asymptotic analysis.
|
||||||
|
Big-O notation describes the growth rate of an algorithm but ignores constant factors and low-level implementation details, which can have a substantial impact on performance in real-world environments.
|
||||||
|
Therefore, an algorithm with better theoretical complexity does not necessarily guarantee superior practical performance.
|
||||||
|
|
||||||
|
In addition, the programming environment further amplifies these effects.
|
||||||
|
In Python, object-oriented structures and pointer-based data manipulation incur additional overhead compared to contiguous array-based structures.
|
||||||
|
The Fibonacci heap, which heavily relies on such operations, becomes less efficient in this context.
|
||||||
|
On the other hand, the binary heap benefits from Python's optimized list operations and memory locality.
|
||||||
|
|
||||||
|
It is important to note that these findings may not generalize across all programming languages.
|
||||||
|
In lower-level languages such as C or C++, where memory management and pointer operations can be more efficiently controlled, the relative performance of Fibonacci heaps may differ.
|
||||||
|
Therefore, the observed performance gap is influenced not only by the algorithm itself but also by the implementation environment.
|
||||||
|
|
||||||
|
Overall, the results suggest that the practical inefficiency of the Fibonacci heap arises not from its asymptotic complexity, but from large constant factors and implementation overhead.
|
||||||
|
This explains why binary heaps often outperform Fibonacci heaps in real-world applications of Dijkstra's algorithm, despite their inferior theoretical complexity.
|
||||||
|
|
||||||
|
% \newpage
|
||||||
\section{Conclusion}
|
\section{Conclusion}
|
||||||
\begin{itemize}
|
% \begin{itemize}
|
||||||
\item main result summary
|
% \item main result summary
|
||||||
\item answer RQ
|
% \item answer RQ
|
||||||
\item ending
|
% \item ending
|
||||||
\end{itemize}
|
% \end{itemize}
|
||||||
|
|
||||||
|
I tried to figure about the gap between empirical practice and the time complexity theory.
|
||||||
|
|
||||||
|
Extract properties from real data and synthesize various situation.
|
||||||
|
Run dijkstra with each heap type.
|
||||||
|
|
||||||
|
In result, binary heaps outperform everytime.
|
||||||
|
This is due to the constant(coefficient).
|
||||||
|
|
||||||
|
Thererfore, it shows limitation of asymptotic time complexity theory.
|
||||||
|
|
||||||
|
However, there are limitation of the study too.
|
||||||
|
- Environment
|
||||||
|
- Data
|
||||||
|
|
||||||
\newpage
|
\newpage
|
||||||
\printbibliography[
|
\printbibliography[
|
||||||
|
|||||||