<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>冒泡排序可视化演示</title>
<!-- 引入外部资源 -->
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
<!-- Tailwind 配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
secondary: '#36CFC9',
neutral: '#64748b',
comparing: '#f97316',
sorted: '#10b981',
swapping: '#ef4444',
background: '#f8fafc',
card: '#ffffff',
},
fontFamily: {
inter: ['Inter', 'sans-serif'],
code: ['Fira Code', 'monospace'],
},
animation: {
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
}
},
}
}
</script>
<!-- 自定义工具类 -->
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.transition-height {
transition-property: height;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
}
.bar-shadow {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.stat-card {
@apply bg-card rounded-xl p-4 shadow-md hover:shadow-lg transition-all duration-300 border border-gray-100;
}
.control-btn {
@apply px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center justify-center gap-2;
}
.control-btn-primary {
@apply bg-primary text-white hover:bg-primary/90 active:bg-primary/80;
}
.control-btn-secondary {
@apply bg-gray-100 text-gray-800 hover:bg-gray-200 active:bg-gray-300;
}
}
</style>
</head>
<body class="font-inter bg-background text-gray-800 min-h-screen flex flex-col">
<!-- 顶部导航栏 -->
<header class="bg-white shadow-sm sticky top-0 z-50 transition-all duration-300">
<div class="container mx-auto px-4 py-4 flex flex-wrap items-center justify-between">
<div class="flex items-center gap-2">
<i class="fa fa-exchange text-primary text-2xl"></i>
<h1 class="text-[clamp(1.25rem,3vw,1.75rem)] font-bold text-gray-800">冒泡排序可视化</h1>
</div>
<nav class="hidden md:flex items-center gap-6 mt-4 md:mt-0">
<a href="#visualization" class="text-gray-600 hover:text-primary transition-colors">可视化</a>
<a href="#code" class="text-gray-600 hover:text-primary transition-colors">代码实现</a>
<a href="#explanation" class="text-gray-600 hover:text-primary transition-colors">算法说明</a>
</nav>
<button class="md:hidden text-gray-600 hover:text-primary" id="menu-toggle">
<i class="fa fa-bars text-xl"></i>
</button>
</div>
<!-- 移动端菜单 -->
<div id="mobile-menu" class="hidden md:hidden bg-white border-t">
<div class="container mx-auto px-4 py-3 flex flex-col gap-3">
<a href="#visualization" class="text-gray-600 hover:text-primary py-2 transition-colors">可视化</a>
<a href="#code" class="text-gray-600 hover:text-primary py-2 transition-colors">代码实现</a>
<a href="#explanation" class="text-gray-600 hover:text-primary py-2 transition-colors">算法说明</a>
</div>
</div>
</header>
<main class="flex-grow container mx-auto px-4 py-6">
<!-- 可视化区域 -->
<section id="visualization" class="mb-12">
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
<!-- 左侧控制面板 -->
<div class="lg:col-span-3 space-y-6">
<div class="bg-card rounded-xl shadow-md p-5 border border-gray-100">
<h2 class="text-lg font-semibold mb-4 pb-2 border-b border-gray-100">控制面板</h2>
<!-- 控制按钮组 -->
<div class="grid grid-cols-2 gap-3 mb-5">
<button id="new-array" class="control-btn control-btn-secondary">
<i class="fa fa-refresh"></i> 新数组
</button>
<button id="start-sort" class="control-btn control-btn-primary">
<i class="fa fa-play"></i> 开始
</button>
<button id="pause-sort" class="control-btn control-btn-secondary" disabled>
<i class="fa fa-pause"></i> 暂停
</button>
<button id="reset-sort" class="control-btn control-btn-secondary">
<i class="fa fa-stop"></i> 重置
</button>
</div>
<!-- 显示样式切换 -->
<div class="mb-5">
<label class="block text-sm font-medium text-gray-700 mb-2">显示样式</label>
<div class="flex items-center gap-4">
<label class="inline-flex items-center cursor-pointer">
<input type="radio" name="display-style" value="bar" checked class="sr-only peer">
<div class="w-9 h-9 border-2 border-gray-300 peer-checked:border-primary rounded flex items-center justify-center transition-all">
<i class="fa fa-columns text-gray-500 peer-checked:text-primary"></i>
</div>
<span class="ml-2 text-sm text-gray-700">柱状</span>
</label>
<label class="inline-flex items-center cursor-pointer">
<input type="radio" name="display-style" value="cylinder" class="sr-only peer">
<div class="w-9 h-9 border-2 border-gray-300 peer-checked:border-primary rounded flex items-center justify-center transition-all">
<i class="fa fa-circle-o text-gray-500 peer-checked:text-primary"></i>
</div>
<span class="ml-2 text-sm text-gray-700">圆柱</span>
</label>
</div>
</div>
<!-- 数组大小滑块 -->
<div class="mb-5">
<div class="flex justify-between items-center mb-2">
<label for="array-size" class="text-sm font-medium text-gray-700">数组大小: <span id="array-size-value">20</span></label>
</div>
<input
type="range"
id="array-size"
min="5"
max="50"
value="20"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-primary"
>
</div>
<!-- 排序速度滑块 -->
<div>
<div class="flex justify-between items-center mb-2">
<label for="sort-speed" class="text-sm font-medium text-gray-700">排序速度: <span id="sort-speed-value">100</span>ms</label>
</div>
<input
type="range"
id="sort-speed"
min="10"
max="500"
value="100"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-primary"
>
</div>
</div>
<!-- 统计信息 -->
<div class="space-y-4">
<h2 class="text-lg font-semibold mb-2">统计信息</h2>
<div class="stat-card">
<div class="text-gray-500 text-sm mb-1">比较次数</div>
<div class="text-2xl font-bold text-primary" id="comparison-count">0</div>
</div>
<div class="stat-card">
<div class="text-gray-500 text-sm mb-1">交换次数</div>
<div class="text-2xl font-bold text-swapping" id="swap-count">0</div>
</div>
<div class="stat-card">
<div class="text-gray-500 text-sm mb-1">数组大小</div>
<div class="text-2xl font-bold text-neutral" id="current-array-size">20</div>
</div>
</div>
</div>
<!-- 中间可视化区域 -->
<div class="lg:col-span-9">
<div class="bg-card rounded-xl shadow-md p-5 border border-gray-100 h-full">
<h2 class="text-lg font-semibold mb-4 pb-2 border-b border-gray-100">排序过程可视化</h2>
<div class="flex items-end justify-center gap-[2px] md:gap-[4px] h-[400px] w-full p-2 md:p-4 overflow-x-auto" id="visualization-container">
<!-- 排序可视化元素将在这里动态生成 -->
</div>
<div class="flex flex-wrap gap-4 mt-4 justify-center text-sm">
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-neutral rounded-sm"></div>
<span>未排序</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-comparing rounded-sm"></div>
<span>比较中</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-swapping rounded-sm"></div>
<span>交换中</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-sorted rounded-sm"></div>
<span>已排序</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 代码展示区域 -->
<section id="code" class="mb-12">
<div class="bg-card rounded-xl shadow-md p-5 border border-gray-100">
<h2 class="text-xl font-semibold mb-4">冒泡排序代码实现</h2>
<!-- 代码标签页 -->
<div class="border-b border-gray-200 mb-4">
<div class="flex overflow-x-auto space-x-1 md:space-x-4">
<button class="code-tab py-2 px-4 border-b-2 border-primary text-primary font-medium" data-lang="c">C</button>
<button class="code-tab py-2 px-4 border-b-2 border-transparent text-gray-500 hover:text-gray-700" data-lang="python">Python</button>
<button class="code-tab py-2 px-4 border-b-2 border-transparent text-gray-500 hover:text-gray-700" data-lang="go">Go</button>
<button class="code-tab py-2 px-4 border-b-2 border-transparent text-gray-500 hover:text-gray-700" data-lang="java">Java</button>
</div>
</div>
<!-- 代码内容区域 -->
<div class="code-content">
<!-- C代码 -->
<div class="code-block font-code text-sm md:text-base" data-lang="c">
<pre class="bg-gray-50 p-4 rounded-lg overflow-x-auto"><code>void bubbleSort(int arr[], int n) {
int i, j;
for (i = 0; i < n-1; i++) {
// 最后i个元素已经就位
for (j = 0; j < n-i-1; j++) {
// 从0到n-i-1遍历
if (arr[j] > arr[j+1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}</code></pre>
</div>
<!-- Python代码 -->
<div class="code-block font-code text-sm md:text-base hidden" data-lang="python">
<pre class="bg-gray-50 p-4 rounded-lg overflow-x-auto"><code>def bubble_sort(arr):
n = len(arr)
# 遍历所有数组元素
for i in range(n):
# 最后i个元素已经就位
for j in range(0, n-i-1):
# 从0到n-i-1遍历
if arr[j] > arr[j+1]:
# 交换元素
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr</code></pre>
</div>
<!-- Go代码 -->
<div class="code-block font-code text-sm md:text-base hidden" data-lang="go">
<pre class="bg-gray-50 p-4 rounded-lg overflow-x-auto"><code>func bubbleSort(arr []int) {
n := len(arr)
for i := 0; i < n-1; i++ {
// 最后i个元素已经就位
for j := 0; j < n-i-1; j++ {
// 从0到n-i-1遍历
if arr[j] > arr[j+1] {
// 交换元素
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}</code></pre>
</div>
<!-- Java代码 -->
<div class="code-block font-code text-sm md:text-base hidden" data-lang="java">
<pre class="bg-gray-50 p-4 rounded-lg overflow-x-auto"><code>public class BubbleSort {
void bubbleSort(int arr[]) {
int n = arr.length;
for (int i = 0; i < n-1; i++) {
// 最后i个元素已经就位
for (int j = 0; j < n-i-1; j++) {
// 从0到n-i-1遍历
if (arr[j] > arr[j+1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}</code></pre>
</div>
</div>
</div>
</section>
<!-- 算法说明区域 -->
<section id="explanation" class="mb-12">
<div class="bg-card rounded-xl shadow-md p-5 border border-gray-100">
<h2 class="text-xl font-semibold mb-6">冒泡排序算法说明</h2>
<!-- 算法原理 -->
<div class="mb-8">
<h3 class="text-lg font-medium mb-3 text-primary">算法原理</h3>
<p class="mb-4 text-gray-700 leading-relaxed">
冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个相邻的元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
</p>
<p class="text-gray-700 leading-relaxed">
这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端,就如同水中的气泡一样向上冒,因此得名冒泡排序。
</p>
</div>
<!-- 复杂度分析 -->
<div class="mb-8">
<h3 class="text-lg font-medium mb-3 text-primary">复杂度分析</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-medium mb-2">时间复杂度</h4>
<ul class="list-disc list-inside space-y-2 text-gray-700">
<li>最佳情况:<span class="font-semibold">O(n)</span>(当数组已经排序时,使用优化版本)</li>
<li>平均情况:<span class="font-semibold">O(n²)</span></li>
<li>最坏情况:<span class="font-semibold">O(n²)</span></li>
</ul>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="font-medium mb-2">空间复杂度</h4>
<ul class="list-disc list-inside space-y-2 text-gray-700">
<li>额外空间:<span class="font-semibold">O(1)</span></li>
<li>原地排序算法,不需要额外的存储空间</li>
</ul>
</div>
</div>
</div>
<!-- 与其他排序算法的对比 -->
<div class="mb-8 overflow-x-auto">
<h3 class="text-lg font-medium mb-3 text-primary">与其他排序算法的对比</h3>
<table class="min-w-full border-collapse">
<thead>
<tr class="bg-gray-50">
<th class="border border-gray-200 px-4 py-2 text-left">排序算法</th>
<th class="border border-gray-200 px-4 py-2 text-left">最佳时间复杂度</th>
<th class="border border-gray-200 px-4 py-2 text-left">平均时间复杂度</th>
<th class="border border-gray-200 px-4 py-2 text-left">最坏时间复杂度</th>
<th class="border border-gray-200 px-4 py-2 text-left">空间复杂度</th>
<th class="border border-gray-200 px-4 py-2 text-left">稳定性</th>
</tr>
</thead>
<tbody>
<tr class="hover:bg-gray-50 transition-colors">
<td class="border border-gray-200 px-4 py-2 font-medium">冒泡排序</td>
<td class="border border-gray-200 px-4 py-2">O(n)</td>
<td class="border border-gray-200 px-4 py-2">O(n²)</td>
<td class="border border-gray-200 px-4 py-2">O(n²)</td>
<td class="border border-gray-200 px-4 py-2">O(1)</td>
<td class="border border-gray-200 px-4 py-2">稳定</td>
</tr>
<tr class="hover:bg-gray-50 transition-colors">
<td class="border border-gray-200 px-4 py-2 font-medium">选择排序</td>
<td class="border border-gray-200 px-4 py-2">O(n²)</td>
<td class="border border-gray-200 px-4 py-2">O(n²)</td>
<td class="border border-gray-200 px-4 py-2">O(n²)</td>
<td class="border border-gray-200 px-4 py-2">O(1)</td>
<td class="border border-gray-200 px-4 py-2">不稳定</td>
</tr>
<tr class="hover:bg-gray-50 transition-colors">
<td class="border border-gray-200 px-4 py-2 font-medium">插入排序</td>
<td class="border border-gray-200 px-4 py-2">O(n)</td>
<td class="border border-gray-200 px-4 py-2">O(n²)</td>
<td class="border border-gray-200 px-4 py-2">O(n²)</td>
<td class="border border-gray-200 px-4 py-2">O(1)</td>
<td class="border border-gray-200 px-4 py-2">稳定</td>
</tr>
<tr class="hover:bg-gray-50 transition-colors">
<td class="border border-gray-200 px-4 py-2 font-medium">快速排序</td>
<td class="border border-gray-200 px-4 py-2">O(n log n)</td>
<td class="border border-gray-200 px-4 py-2">O(n log n)</td>
<td class="border border-gray-200 px-4 py-2">O(n²)</td>
<td class="border border-gray-200 px-4 py-2">O(log n)</td>
<td class="border border-gray-200 px-4 py-2">不稳定</td>
</tr>
<tr class="hover:bg-gray-50 transition-colors">
<td class="border border-gray-200 px-4 py-2 font-medium">归并排序</td>
<td class="border border-gray-200 px-4 py-2">O(n log n)</td>
<td class="border border-gray-200 px-4 py-2">O(n log n)</td>
<td class="border border-gray-200 px-4 py-2">O(n log n)</td>
<td class="border border-gray-200 px-4 py-2">O(n)</td>
<td class="border border-gray-200 px-4 py-2">稳定</td>
</tr>
<tr class="hover:bg-gray-50 transition-colors">
<td class="border border-gray-200 px-4 py-2 font-medium">堆排序</td>
<td class="border border-gray-200 px-4 py-2">O(n log n)</td>
<td class="border border-gray-200 px-4 py-2">O(n log n)</td>
<td class="border border-gray-200 px-4 py-2">O(n log n)</td>
<td class="border border-gray-200 px-4 py-2">O(1)</td>
<td class="border border-gray-200 px-4 py-2">不稳定</td>
</tr>
</tbody>
</table>
</div>
<!-- 适用场景 -->
<div>
<h3 class="text-lg font-medium mb-3 text-primary">适用场景</h3>
<p class="mb-3 text-gray-700">冒泡排序由于其简单性和稳定性,在某些特定场景下仍然有用:</p>
<ul class="list-disc list-inside space-y-2 text-gray-700 mb-4">
<li>对于几乎已经排序的数据,优化版的冒泡排序性能良好</li>
<li>数据量很小的情况,简单性比效率更重要时</li>
<li>教学场景,用于解释排序算法的基本原理</li>
<li>需要稳定排序且实现简单的场景</li>
</ul>
<p class="text-gray-700">
然而,对于大规模数据集,冒泡排序通常不是一个好的选择,因为其时间复杂度为O(n²),效率较低。在这种情况下,应该选择快速排序、归并排序等更高效的算法。
</p>
</div>
</div>
</section>
</main>
<footer class="bg-white border-t border-gray-200 py-6">
<div class="container mx-auto px-4 text-center text-gray-600 text-sm">
<p>冒泡排序可视化演示 © 2023</p>
<p class="mt-1">一个直观展示冒泡排序算法工作原理的交互式工具</p>
</div>
</footer>
<!-- JavaScript 代码 -->
<script>
// 全局变量
let array = [];
let arraySize = 20;
let sortSpeed = 100; // 毫秒
let isSorting = false;
let isPaused = false;
let comparisonCount = 0;
let swapCount = 0;
let animationId = null;
let displayStyle = 'bar';
// DOM 元素
const visualizationContainer = document.getElementById('visualization-container');
const arraySizeSlider = document.getElementById('array-size');
const arraySizeValue = document.getElementById('array-size-value');
const sortSpeedSlider = document.getElementById('sort-speed');
const sortSpeedValue = document.getElementById('sort-speed-value');
const comparisonCountEl = document.getElementById('comparison-count');
const swapCountEl = document.getElementById('swap-count');
const currentArraySizeEl = document.getElementById('current-array-size');
const newArrayBtn = document.getElementById('new-array');
const startSortBtn = document.getElementById('start-sort');
const pauseSortBtn = document.getElementById('pause-sort');
const resetSortBtn = document.getElementById('reset-sort');
const menuToggle = document.getElementById('menu-toggle');
const mobileMenu = document.getElementById('mobile-menu');
const codeTabs = document.querySelectorAll('.code-tab');
const codeBlocks = document.querySelectorAll('.code-block');
const displayStyleRadios = document.querySelectorAll('input[name="display-style"]');
// 初始化
window.addEventListener('DOMContentLoaded', () => {
createNewArray();
setupEventListeners();
});
// 设置事件监听器
function setupEventListeners() {
// 数组大小滑块
arraySizeSlider.addEventListener('input', (e) => {
if (!isSorting) {
arraySize = parseInt(e.target.value);
arraySizeValue.textContent = arraySize;
currentArraySizeEl.textContent = arraySize;
createNewArray();
}
});
// 排序速度滑块
sortSpeedSlider.addEventListener('input', (e) => {
sortSpeed = parseInt(e.target.value);
sortSpeedValue.textContent = sortSpeed;
});
// 按钮事件
newArrayBtn.addEventListener('click', createNewArray);
startSortBtn.addEventListener('click', startSort);
pauseSortBtn.addEventListener('click', togglePause);
resetSortBtn.addEventListener('click', resetSort);
// 移动端菜单
menuToggle.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
});
// 代码标签页切换
codeTabs.forEach(tab => {
tab.addEventListener('click', () => {
const lang = tab.getAttribute('data-lang');
// 更新标签样式
codeTabs.forEach(t => {
t.classList.remove('border-primary', 'text-primary');
t.classList.add('border-transparent', 'text-gray-500');
});
tab.classList.remove('border-transparent', 'text-gray-500');
tab.classList.add('border-primary', 'text-primary');
// 显示对应代码块
codeBlocks.forEach(block => {
if (block.getAttribute('data-lang') === lang) {
block.classList.remove('hidden');
} else {
block.classList.add('hidden');
}
});
});
});
// 显示样式切换
displayStyleRadios.forEach(radio => {
radio.addEventListener('change', (e) => {
if (!isSorting) {
displayStyle = e.target.value;
renderArray();
}
});
});
// 滚动时导航栏效果
window.addEventListener('scroll', () => {
const header = document.querySelector('header');
if (window.scrollY > 10) {
header.classList.add('py-2', 'shadow');
header.classList.remove('py-4');
} else {
header.classList.add('py-4');
header.classList.remove('py-2', 'shadow');
}
});
}
// 创建新数组
function createNewArray() {
resetSort();
array = [];
for (let i = 0; i < arraySize; i++) {
// 生成1到100的随机数
array.push(Math.floor(Math.random() * 100) + 1);
}
renderArray();
currentArraySizeEl.textContent = arraySize;
}
// 渲染数组
function renderArray(highlightIndices = [], sortedIndices = []) {
visualizationContainer.innerHTML = '';
// 计算每个元素的宽度
const elementWidth = Math.max(10, (visualizationContainer.clientWidth - (arraySize * 4)) / arraySize);
array.forEach((value, index) => {
// 计算高度(最大高度为容器高度的90%)
const maxHeight = visualizationContainer.clientHeight * 0.9;
const height = (value / 100) * maxHeight;
// 创建元素容器
const element = document.createElement('div');
element.className = 'relative flex flex-col items-center';
element.style.width = `${elementWidth}px`;
// 创建柱状/圆柱元素
const bar = document.createElement('div');
// 根据显示样式设置不同的类
if (displayStyle === 'bar') {
bar.className = 'bar-shadow transition-all duration-200 ease-out';
bar.style.width = '100%';
bar.style.borderRadius = '4px 4px 0 0';
} else { // cylinder
bar.className = 'bar-shadow transition-all duration-200 ease-out';
bar.style.width = '80%';
bar.style.borderRadius = '50% 50% 0 0';
}
// 设置高度和背景色
bar.style.height = `${height}px`;
// 确定颜色
if (sortedIndices.includes(index)) {
bar.style.backgroundColor = '#10b981'; // sorted
} else if (highlightIndices.includes(index)) {
// 如果是两个高亮元素且正在比较
if (highlightIndices.length === 2 && highlightIndices[0] === index && array[index] > array[highlightIndices[1]]) {
bar.style.backgroundColor = '#ef4444'; // swapping
} else {
bar.style.backgroundColor = '#f97316'; // comparing
}
} else {
bar.style.backgroundColor = '#64748b'; // neutral
}
// 添加数值标签
const valueLabel = document.createElement('div');
valueLabel.className = 'text-xs mt-1 text-gray-600 font-medium';
valueLabel.textContent = value;
// 组合元素
element.appendChild(bar);
element.appendChild(valueLabel);
visualizationContainer.appendChild(element);
});
}
// 开始排序
function startSort() {
if (isSorting && isPaused) {
// 继续排序
isPaused = false;
continueSorting();
} else if (!isSorting) {
// 开始新的排序
isSorting = true;
isPaused = false;
comparisonCount = 0;
swapCount = 0;
updateCounters();
// 更新按钮状态
startSortBtn.disabled = true;
newArrayBtn.disabled = true;
arraySizeSlider.disabled = true;
pauseSortBtn.disabled = false;
// 开始冒泡排序
bubbleSort(0, 0);
}
}
// 暂停/继续排序
function togglePause() {
if (isSorting) {
if (isPaused) {
// 继续排序
isPaused = false;
pauseSortBtn.innerHTML = '<i class="fa fa-pause"></i> 暂停';
continueSorting();
} else {
// 暂停排序
isPaused = true;
pauseSortBtn.innerHTML = '<i class="fa fa-play"></i> 继续';
if (animationId) {
clearTimeout(animationId);
animationId = null;
}
}
}
}
// 继续排序
function continueSorting() {
if (isSorting && !isPaused) {
// 从当前状态继续排序
const i = window.currentI || 0;
const j = window.currentJ || 0;
bubbleSort(i, j);
}
}
// 重置排序
function resetSort() {
if (isSorting) {
isSorting = false;
isPaused = false;
if (animationId) {
clearTimeout(animationId);
animationId = null;
}
}
// 重置计数器
comparisonCount = 0;
swapCount = 0;
updateCounters();
// 重置按钮状态
startSortBtn.disabled = false;
newArrayBtn.disabled = false;
arraySizeSlider.disabled = false;
pauseSortBtn.disabled = true;
pauseSortBtn.innerHTML = '<i class="fa fa-pause"></i> 暂停';
// 重新渲染数组
renderArray();
}
// 冒泡排序算法实现(带可视化)
async function bubbleSort(i, j) {
// 保存当前索引,用于暂停后继续
window.currentI = i;
window.currentJ = j;
// 如果排序已完成
if (i >= array.length - 1) {
// 所有元素都已排序
renderArray([], Array.from({length: array.length}, (_, k) => k));
isSorting = false;
// 重置按钮状态
startSortBtn.disabled = false;
newArrayBtn.disabled = false;
arraySizeSlider.disabled = false;
pauseSortBtn.disabled = true;
return;
}
// 如果内循环已完成
if (j >= array.length - i - 1) {
// 标记当前i位置的元素为已排序
const sortedIndices = Array.from({length: i + 1}, (_, k) => array.length - 1 - k);
renderArray([], sortedIndices);
// 进入下一轮外循环
animationId = setTimeout(() => bubbleSort(i + 1, 0), sortSpeed);
return;
}
// 比较相邻元素
comparisonCount++;
updateCounters();
// 高亮显示正在比较的元素
renderArray([j, j + 1], Array.from({length: i}, (_, k) => array.length - 1 - k));
animationId = setTimeout(() => {
if (isPaused) return;
// 如果需要交换
if (array[j] > array[j + 1]) {
// 交换元素
[array[j], array[j + 1]] = [array[j + 1], array[j]];
swapCount++;
updateCounters();
// 重新渲染以显示交换后的状态
renderArray([j, j + 1], Array.from({length: i}, (_, k) => array.length - 1 - k));
// 继续下一次比较
animationId = setTimeout(() => bubbleSort(i, j + 1), sortSpeed);
} else {
// 不需要交换,继续下一次比较
bubbleSort(i, j + 1);
}
}, sortSpeed);
}
// 更新计数器显示
function updateCounters() {
comparisonCountEl.textContent = comparisonCount;
swapCountEl.textContent = swapCount;
}
</script>
</body>
</html>```