Merge Sort and Quicksort share an \(O(N \log N)\) average-case time complexity on randomized arrays, but Quicksort typically has a smaller constant factor, making it faster in practice.

library(plotly)
library(htmlwidgets)
sizes <- seq(1000, 3000, by = 500)
sorted_ratios <- seq(0, 1, by = 0.2)
plot_data <- data.frame()
for(n in sizes) {
for(p in sorted_ratios) {
num_sorted <- floor(n * p)
random_part <- c()
if (n - num_sorted > 0) {
random_part <- (num_sorted + 1):n
if (length(random_part) > 1) random_part <- sample(random_part)
}
test_arr <- c(if(num_sorted > 0) 1:num_sorted else c(), random_part)
# run each algorithm 15 times and take the median to filter out OS noise
t_merge <- median(replicate(15, system.time(merge_sort(test_arr))["elapsed"] * 1000))
t_quick <- median(replicate(15, system.time(quick_sort(test_arr))["elapsed"] * 1000))
plot_data <- rbind(plot_data,
data.frame(N = n, Sortedness = p, Time = t_merge, Algorithm = "merge_sort"),
data.frame(N = n, Sortedness = p, Time = t_quick, Algorithm = "quick_sort"))
}
}
p <- plot_ly(data = plot_data, x = ~N, y = ~Sortedness, z = ~Time, color = ~Algorithm,
colors = c("#8C1D40", "#FFC627"),
type = "scatter3d", mode = "markers",
marker = list(size = 6, opacity = 0.9),
width = 700, height = 450) %>%
layout(scene = list(
xaxis = list(title = 'Input Size (N)'),
yaxis = list(title = 'Ratio Sorted'),
zaxis = list(title = 'Time (ms)')
),
margin = list(l = 0, r = 0, b = 0, t = 0))
# save the plot independently (because it takes a while to generate)
saveWidget(p, file = "plotly_3d_isolated.html", selfcontained = TRUE)