Skip to content

Commit 2f26cbb

Browse files
committed
[Feature] add for new
1 parent 7c33018 commit 2f26cbb

File tree

2 files changed

+332
-0
lines changed

2 files changed

+332
-0
lines changed

T164-bucket-sort.html

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
<!DOCTYPE html>
2+
<html lang="zh-CN">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>T164 最大间距桶排序可视化</title>
7+
<style>
8+
body {
9+
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
10+
margin: 20px;
11+
background: #f5f5f5;
12+
}
13+
h1 {
14+
text-align: center;
15+
}
16+
a#homeLink {
17+
display: inline-block;
18+
margin-bottom: 15px;
19+
text-decoration: none;
20+
font-weight: bold;
21+
color: #007acc;
22+
}
23+
.input-area {
24+
margin-bottom: 15px;
25+
text-align: center;
26+
}
27+
input[type="text"] {
28+
width: 60%;
29+
padding: 6px 10px;
30+
font-size: 16px;
31+
}
32+
input[type="number"] {
33+
width: 60px;
34+
padding: 4px;
35+
font-size: 16px;
36+
}
37+
button {
38+
margin-left: 10px;
39+
padding: 6px 12px;
40+
font-size: 16px;
41+
cursor: pointer;
42+
}
43+
#arrayContainer {
44+
margin: 20px auto;
45+
width: 90%;
46+
display: flex;
47+
justify-content: center;
48+
gap: 6px;
49+
flex-wrap: wrap;
50+
}
51+
.array-item {
52+
width: 40px;
53+
height: 40px;
54+
background: #3498db;
55+
color: white;
56+
display: flex;
57+
justify-content: center;
58+
align-items: center;
59+
border-radius: 6px;
60+
font-weight: bold;
61+
user-select: none;
62+
transition: background-color 0.3s;
63+
}
64+
.highlight {
65+
background-color: #e74c3c !important;
66+
}
67+
.bucket {
68+
border: 2px dashed #888;
69+
border-radius: 6px;
70+
padding: 10px;
71+
margin: 10px;
72+
min-width: 60px;
73+
min-height: 40px;
74+
text-align: center;
75+
background-color: #ecf0f1;
76+
position: relative;
77+
}
78+
.bucket-label {
79+
font-size: 12px;
80+
margin-bottom: 4px;
81+
color: #555;
82+
}
83+
#bucketsContainer {
84+
display: flex;
85+
justify-content: center;
86+
flex-wrap: wrap;
87+
margin-top: 20px;
88+
gap: 6px;
89+
}
90+
#log {
91+
background-color: #111;
92+
color: #eee;
93+
font-family: monospace;
94+
padding: 12px;
95+
height: 200px;
96+
overflow-y: auto;
97+
white-space: pre-wrap;
98+
border-radius: 6px;
99+
margin-top: 20px;
100+
}
101+
#infoArea {
102+
margin-top: 15px;
103+
font-size: 16px;
104+
text-align: center;
105+
min-height: 24px;
106+
}
107+
</style>
108+
</head>
109+
<body>
110+
<h1>力扣 T164 最大间距桶排序可视化</h1>
111+
<a href="index.html" id="homeLink">【返回首页】</a>
112+
<div class="input-area">
113+
<label for="inputArray">输入数组(逗号分隔): </label>
114+
<input type="text" id="inputArray" value="3,6,9,1,24,25,48" />
115+
<label for="intervalInput">动画间隔(秒):</label>
116+
<input type="number" id="intervalInput" value="1" min="0.1" step="0.1" />
117+
<button id="startBtn">开始可视化</button>
118+
</div>
119+
120+
<div id="arrayContainer"></div>
121+
<div id="bucketsContainer"></div>
122+
123+
<div id="infoArea"></div>
124+
<div id="log"></div>
125+
126+
<script>
127+
(() => {
128+
const startBtn = document.getElementById("startBtn");
129+
const inputArray = document.getElementById("inputArray");
130+
const intervalInput = document.getElementById("intervalInput");
131+
const arrayContainer = document.getElementById("arrayContainer");
132+
const bucketsContainer = document.getElementById("bucketsContainer");
133+
const logArea = document.getElementById("log");
134+
const infoArea = document.getElementById("infoArea");
135+
136+
let animationInterval = 1000;
137+
let timerId = null;
138+
139+
// 渲染数组
140+
function renderArray(arr, highlightIndices = []) {
141+
arrayContainer.innerHTML = "";
142+
arr.forEach((v, i) => {
143+
const div = document.createElement("div");
144+
div.className = "array-item";
145+
div.textContent = v;
146+
if (highlightIndices.includes(i)) {
147+
div.classList.add("highlight");
148+
}
149+
arrayContainer.appendChild(div);
150+
});
151+
}
152+
153+
// 渲染桶状态
154+
function renderBuckets(buckets, min, bucketWidth, highlightIndex = -1) {
155+
bucketsContainer.innerHTML = "";
156+
buckets.forEach((bucket, i) => {
157+
const div = document.createElement("div");
158+
div.className = "bucket";
159+
if (i === highlightIndex) {
160+
div.style.backgroundColor = "#f39c12";
161+
} else {
162+
div.style.backgroundColor = "#ecf0f1";
163+
}
164+
const label = document.createElement("div");
165+
label.className = "bucket-label";
166+
label.textContent = `桶${i}`;
167+
div.appendChild(label);
168+
169+
if (bucket[0] === Number.MAX_SAFE_INTEGER) {
170+
div.appendChild(document.createTextNode("空"));
171+
} else {
172+
div.appendChild(document.createTextNode(`最小: ${bucket[0]}, 最大: ${bucket[1]}`));
173+
}
174+
bucketsContainer.appendChild(div);
175+
});
176+
}
177+
178+
// 输出日志,自动滚动到底部
179+
function log(msg) {
180+
logArea.textContent += msg + "\n";
181+
logArea.scrollTop = logArea.scrollHeight;
182+
}
183+
184+
// 清理日志和信息
185+
function resetUI() {
186+
logArea.textContent = "";
187+
infoArea.textContent = "";
188+
bucketsContainer.innerHTML = "";
189+
arrayContainer.innerHTML = "";
190+
}
191+
192+
// 主动画函数:桶排序可视化
193+
async function visualizeMaximumGap(arr, intervalSec) {
194+
resetUI();
195+
if (arr.length < 2) {
196+
infoArea.textContent = "数组长度小于2,无需排序,最大间距为0";
197+
log("数组长度小于2,直接返回0");
198+
renderArray(arr);
199+
return;
200+
}
201+
202+
log(`输入数组: [${arr.join(", ")}]`);
203+
renderArray(arr);
204+
205+
let min = Number.MAX_SAFE_INTEGER;
206+
let max = Number.MIN_SAFE_INTEGER;
207+
for (const num of arr) {
208+
if (num > max) max = num;
209+
if (num < min) min = num;
210+
}
211+
log(`最小值: ${min}, 最大值: ${max}`);
212+
213+
if (min === max) {
214+
log("最小值和最大值相同,最大间距为0");
215+
infoArea.textContent = "所有元素相同,最大间距为0";
216+
return;
217+
}
218+
219+
const bucketSize = arr.length + 1;
220+
let buckets = new Array(bucketSize);
221+
for (let i = 0; i < bucketSize; i++) {
222+
// 用大整数代替Integer.MAX_VALUE
223+
buckets[i] = [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER];
224+
}
225+
226+
// 计算桶宽度
227+
const bucketWidth = Math.ceil((max - min) / bucketSize);
228+
log(`桶数量: ${bucketSize}, 桶宽度: ${bucketWidth}`);
229+
230+
// 显示初始空桶
231+
renderBuckets(buckets, min, bucketWidth);
232+
233+
// 放入桶中
234+
for (let i = 0; i < arr.length; i++) {
235+
const num = arr[i];
236+
let bucketIndex = Math.floor((num - min) / bucketWidth);
237+
if (bucketIndex >= bucketSize) bucketIndex = bucketSize - 1;
238+
239+
infoArea.textContent = `第${i + 1}个元素 ${num} 放入桶 ${bucketIndex}`;
240+
log(`元素 ${num} 计算桶索引: ${bucketIndex}`);
241+
242+
// 高亮当前元素
243+
renderArray(arr, [i]);
244+
// 高亮当前桶
245+
renderBuckets(buckets, min, bucketWidth, bucketIndex);
246+
247+
// 更新桶内最大最小值
248+
buckets[bucketIndex][0] = Math.min(buckets[bucketIndex][0], num);
249+
buckets[bucketIndex][1] = Math.max(buckets[bucketIndex][1], num);
250+
251+
log(`更新桶${bucketIndex} => 最小值: ${buckets[bucketIndex][0]}, 最大值: ${buckets[bucketIndex][1]}`);
252+
253+
await sleep(intervalSec);
254+
}
255+
256+
// 计算最大间距
257+
let maxGap = 0;
258+
let prev = min;
259+
infoArea.textContent = "开始计算最大间距...";
260+
renderBuckets(buckets, min, bucketWidth);
261+
262+
for (let i = 0; i < buckets.length; i++) {
263+
if (buckets[i][0] === Number.MAX_SAFE_INTEGER) {
264+
log(`桶${i}为空,跳过`);
265+
continue;
266+
}
267+
268+
let gap = buckets[i][0] - prev;
269+
log(`桶${i}的最小值 ${buckets[i][0]} - 上一个最大值 ${prev} = 间距 ${gap}`);
270+
if (gap > maxGap) {
271+
maxGap = gap;
272+
log(`更新最大间距为 ${maxGap}`);
273+
}
274+
275+
prev = buckets[i][1];
276+
277+
infoArea.textContent = `检查桶${i},当前最大间距: ${maxGap}`;
278+
renderBuckets(buckets, min, bucketWidth, i);
279+
280+
await sleep(intervalSec);
281+
}
282+
283+
infoArea.textContent = `最大间距计算完成: ${maxGap}`;
284+
log(`返回最大间距: ${maxGap}`);
285+
286+
// 最后高亮全部数组
287+
renderArray(arr);
288+
renderBuckets(buckets, min, bucketWidth);
289+
}
290+
291+
function sleep(seconds) {
292+
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
293+
}
294+
295+
// 事件绑定
296+
startBtn.addEventListener("click", () => {
297+
if (timerId) {
298+
clearTimeout(timerId);
299+
timerId = null;
300+
}
301+
const rawInput = inputArray.value.trim();
302+
if (!rawInput) {
303+
alert("请输入有效数组!");
304+
return;
305+
}
306+
let arr = rawInput.split(",").map(s => parseInt(s.trim())).filter(n => !isNaN(n));
307+
if (arr.length === 0) {
308+
alert("请输入有效数字数组!");
309+
return;
310+
}
311+
let intervalSec = parseFloat(intervalInput.value);
312+
if (isNaN(intervalSec) || intervalSec <= 0) {
313+
intervalSec = 1;
314+
}
315+
animationInterval = intervalSec;
316+
317+
visualizeMaximumGap(arr, animationInterval);
318+
});
319+
320+
// 页面加载时默认渲染数组
321+
renderArray(inputArray.value.split(",").map(s => parseInt(s.trim())).filter(n => !isNaN(n)));
322+
})();
323+
</script>
324+
</body>
325+
</html>

index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,13 @@ <h1>算法可视化 · 资源导航</h1>
140140

141141
<div class="grid" id="cardGrid">
142142
<!-- 示例卡片 -->
143+
144+
<div class="card" data-tags="T164 sort bucket-sort array">
145+
<div class="card-title">T164 最大间距-桶排序</div>
146+
<div class="card-desc">T164 最大间距-桶排序</div>
147+
<a href="./T164-bucket-sort.html" target="_blank">打开页面</a>
148+
</div>
149+
143150
<div class="card" data-tags="T192 sort select-sort array">
144151
<div class="card-title">T192 数组选择排序</div>
145152
<div class="card-desc">T192 数组选择排序</div>

0 commit comments

Comments
 (0)