1. 冒泡排序的基本原理与实现冒泡排序是计算机科学中最基础的排序算法之一也是C初学者必须掌握的经典算法。它的核心思想就像水中的气泡一样较小的元素会逐渐浮到数组的顶端前端而较大的元素会沉到底部后端。1.1 算法工作原理冒泡排序通过重复遍历待排序的数列来实现排序。每次遍历时它会比较相邻的两个元素如果它们的顺序错误即前一个比后一个大就交换它们的位置。这个过程会一直重复直到整个数列变得有序。让我们用一个简单的例子来说明 假设有一个数组 [5, 3, 8, 6, 2]冒泡排序的过程如下第一次遍历 比较5和3 → 交换 → [3,5,8,6,2] 比较5和8 → 不交换 比较8和6 → 交换 → [3,5,6,8,2] 比较8和2 → 交换 → [3,5,6,2,8]第二次遍历 比较3和5 → 不交换 比较5和6 → 不交换 比较6和2 → 交换 → [3,5,2,6,8]第三次遍历 比较3和5 → 不交换 比较5和2 → 交换 → [3,2,5,6,8]第四次遍历 比较3和2 → 交换 → [2,3,5,6,8]可以看到每次遍历都能确保当前未排序部分的最大元素冒泡到正确的位置。1.2 C基础实现下面是一个最基本的冒泡排序C实现#include iostream using namespace std; void bubbleSort(int arr[], int n) { for (int i 0; i n-1; i) { for (int j 0; j n-i-1; j) { if (arr[j] arr[j1]) { // 交换相邻元素 int temp arr[j]; arr[j] arr[j1]; arr[j1] temp; } } } } int main() { int arr[] {64, 34, 25, 12, 22, 11, 90}; int n sizeof(arr)/sizeof(arr[0]); bubbleSort(arr, n); cout 排序后的数组: \n; for (int i0; i n; i) cout arr[i] ; cout endl; return 0; }这个实现有几个关键点需要注意外层循环控制遍历的轮数共需要n-1轮内层循环负责实际的比较和交换操作每完成一轮外层循环最大的元素就会被放到正确的位置所以内层循环的范围可以逐渐缩小提示在实际编程中建议使用标准库中的swap函数来交换元素而不是手动实现交换逻辑这样代码更简洁且不易出错。1.3 时间复杂度分析冒泡排序的时间复杂度是算法性能的重要指标。在最坏情况下数组完全逆序冒泡排序需要进行n(n-1)/2次比较和交换操作因此其时间复杂度为O(n²)。在最好情况下数组已经有序经过优化后的冒泡排序可以达到O(n)的时间复杂度。空间复杂度方面冒泡排序是原地排序算法只需要常数级别的额外空间用于临时变量因此空间复杂度为O(1)。2. 冒泡排序的优化策略虽然冒泡排序是最基础的排序算法之一但通过一些优化手段可以显著提高其性能特别是在处理部分有序的数据时。2.1 提前终止优化一个常见的优化是添加一个标志位用于检测某一轮遍历中是否发生了交换。如果某一轮没有发生任何交换说明数组已经有序可以提前终止排序过程。优化后的实现如下void optimizedBubbleSort(int arr[], int n) { bool swapped; for (int i 0; i n-1; i) { swapped false; for (int j 0; j n-i-1; j) { if (arr[j] arr[j1]) { swap(arr[j], arr[j1]); swapped true; } } // 如果这一轮没有发生交换数组已经有序 if (!swapped) break; } }这种优化对于接近有序的数组特别有效可以显著减少不必要的比较操作。2.2 记录最后交换位置另一种优化方法是记录最后一次交换发生的位置。在这个位置之后的元素已经是有序的下一轮遍历时就不需要再比较这些元素了。实现代码如下void improvedBubbleSort(int arr[], int n) { int lastSwapPos 0; int sortBorder n - 1; for (int i 0; i n-1; i) { bool swapped false; for (int j 0; j sortBorder; j) { if (arr[j] arr[j1]) { swap(arr[j], arr[j1]); swapped true; lastSwapPos j; } } sortBorder lastSwapPos; if (!swapped) break; } }这种优化进一步减少了比较次数特别是对于大型数组效果更明显。2.3 双向冒泡排序鸡尾酒排序传统的冒泡排序只在一个方向上进行比较和交换。双向冒泡排序也称为鸡尾酒排序则是在两个方向上交替进行排序先从左到右将最大的元素移到最右然后从右到左将最小的元素移到最左如此往复。实现示例void cocktailSort(int arr[], int n) { bool swapped true; int start 0; int end n - 1; while (swapped) { swapped false; // 从左到右 for (int i start; i end; i) { if (arr[i] arr[i1]) { swap(arr[i], arr[i1]); swapped true; } } if (!swapped) break; swapped false; end--; // 缩小右边界 // 从右到左 for (int i end-1; i start; i--) { if (arr[i] arr[i1]) { swap(arr[i], arr[i1]); swapped true; } } start; // 缩小左边界 } }这种变体对于某些特定分布的数据如大部分元素已经有序只有少数元素需要排序有更好的性能表现。3. 使用冒泡排序解决最值问题冒泡排序不仅可以用于排序还可以巧妙地解决一些最值问题。这是因为在排序过程中最大值和最小值会自然地冒泡到数组的两端。3.1 寻找数组最大值通过观察冒泡排序的过程我们可以发现在完整的一轮冒泡排序后最大的元素一定会被移动到数组的末尾。因此如果我们只需要找到数组中的最大值可以简化冒泡排序的过程int findMax(int arr[], int n) { // 只需执行一轮冒泡 for (int i 0; i n-1; i) { if (arr[i] arr[i1]) { swap(arr[i], arr[i1]); } } return arr[n-1]; // 最后一个元素就是最大值 }这种方法的时间复杂度是O(n)与直接遍历数组寻找最大值的时间复杂度相同但提供了另一种思路。3.2 寻找数组最小值类似地我们可以修改冒泡排序的方向来寻找最小值int findMin(int arr[], int n) { // 从后向前冒泡一轮 for (int i n-1; i 0; i--) { if (arr[i] arr[i-1]) { swap(arr[i], arr[i-1]); } } return arr[0]; // 第一个元素就是最小值 }3.3 同时寻找最大值和最小值结合上述两种方法我们可以在一次遍历中同时找到最大值和最小值void findMinMax(int arr[], int n, int minVal, int maxVal) { if (n 0) return; minVal maxVal arr[0]; for (int i 0; i n; i) { if (arr[i] minVal) { minVal arr[i]; } else if (arr[i] maxVal) { maxVal arr[i]; } } }虽然这种方法看起来与冒泡排序关系不大但它展示了如何从排序算法中提取出解决特定问题的思路。注意在实际应用中直接遍历数组寻找最值通常比使用排序算法更高效。这些示例主要是为了展示算法思想的应用。4. 冒泡排序在实际应用中的考量虽然冒泡排序在大多数情况下不是最优选择但理解它的工作原理和特性对于学习算法和编程基础非常重要。在实际应用中我们需要考虑以下几个方面4.1 适用场景分析冒泡排序最适合以下场景数据量非常小n 100数据已经接近有序需要简单实现的排序算法作为教学工具理解排序算法基本原理对于大规模数据排序更高效的算法如快速排序、归并排序或堆排序通常是更好的选择。4.2 稳定性分析冒泡排序是一种稳定的排序算法这意味着相等元素的相对顺序在排序后不会改变。这一特性在某些应用场景中非常重要比如当我们需要对具有多个排序键的对象进行排序时。4.3 与其他排序算法的比较让我们将冒泡排序与其他常见排序算法进行简单比较算法平均时间复杂度最坏时间复杂度空间复杂度稳定性冒泡排序O(n²)O(n²)O(1)稳定选择排序O(n²)O(n²)O(1)不稳定插入排序O(n²)O(n²)O(1)稳定快速排序O(n log n)O(n²)O(log n)不稳定归并排序O(n log n)O(n log n)O(n)稳定堆排序O(n log n)O(n log n)O(1)不稳定从表中可以看出冒泡排序在时间复杂度上并不占优势但它的简单性和稳定性使其在特定情况下仍有应用价值。4.4 实际编程中的注意事项在实际使用冒泡排序时需要注意以下几点数组边界处理确保循环条件正确避免数组越界性能考量对于大型数据集考虑使用更高效的算法代码可读性适当添加注释说明算法步骤泛型实现可以考虑使用模板使算法适用于不同类型的数据下面是一个使用模板的泛型冒泡排序实现template typename T void genericBubbleSort(T arr[], int n) { bool swapped; for (int i 0; i n-1; i) { swapped false; for (int j 0; j n-i-1; j) { if (arr[j] arr[j1]) { swap(arr[j], arr[j1]); swapped true; } } if (!swapped) break; } }这个实现可以用于任何定义了比较运算符的数据类型包括内置类型和自定义类。5. 常见问题与调试技巧在学习实现冒泡排序和解决最值问题时初学者常会遇到一些典型问题。了解这些问题及其解决方案可以帮助你更快地掌握这些概念。5.1 数组越界问题最常见的错误之一是数组越界访问。在冒泡排序中内层循环的条件通常是for (int j 0; j n-i-1; j)而不是for (int j 0; j n-i; j) // 错误的会导致数组越界后者会导致访问arr[j1]时越界。要特别注意循环边界条件的设置。5.2 排序不完整问题另一个常见问题是排序不完整这通常是由于外层循环次数不足造成的。冒泡排序在最坏情况下需要n-1轮遍历才能确保数组完全有序。如果外层循环次数不足可能会导致数组没有完全排序。5.3 性能问题诊断当发现冒泡排序性能异常低下时可以考虑以下检查点是否使用了优化版本提前终止标志数据是否已经接近有序优化版本对此类数据效果更好是否在不需要完整排序的情况下使用了完整排序如只需要最大值时5.4 调试技巧调试排序算法时可以采用以下方法在每轮外层循环后打印数组状态观察排序过程使用小规模测试数据5-10个元素便于手动验证添加计数器统计比较和交换次数分析算法效率使用断言(assert)检查数组是否有序下面是一个添加了调试输出的冒泡排序实现void debugBubbleSort(int arr[], int n) { cout 初始数组: ; printArray(arr, n); for (int i 0; i n-1; i) { bool swapped false; for (int j 0; j n-i-1; j) { if (arr[j] arr[j1]) { swap(arr[j], arr[j1]); swapped true; } } cout 第 i1 轮后: ; printArray(arr, n); if (!swapped) { cout 提前终止于第 i1 轮\n; break; } } cout 最终结果: ; printArray(arr, n); }这种调试方法可以直观地展示排序过程帮助理解算法的工作原理。6. 扩展应用与变体冒泡排序虽然简单但它的思想可以扩展到解决其他问题或衍生出新的算法变体。这些扩展不仅有助于深入理解算法思想还能培养解决问题的灵活思维。6.1 字符串排序冒泡排序同样适用于字符串数组的排序。在C中字符串可以直接使用比较运算符进行比较因此冒泡排序的实现几乎不需要修改void bubbleSortStrings(string arr[], int n) { bool swapped; for (int i 0; i n-1; i) { swapped false; for (int j 0; j n-i-1; j) { if (arr[j] arr[j1]) { swap(arr[j], arr[j1]); swapped true; } } if (!swapped) break; } }这个实现会按照字典序对字符串进行排序。6.2 结构体排序对于自定义结构体我们可以通过重载比较运算符或提供比较函数来使用冒泡排序。例如对学生结构体按成绩排序struct Student { string name; int score; }; // 重载比较运算符 bool operator(const Student a, const Student b) { return a.score b.score; } void sortStudents(Student students[], int n) { bool swapped; for (int i 0; i n-1; i) { swapped false; for (int j 0; j n-i-1; j) { if (students[j] students[j1]) { swap(students[j], students[j1]); swapped true; } } if (!swapped) break; } }6.3 链表排序冒泡排序也可以应用于链表数据结构。虽然链表的元素访问方式与数组不同但排序原理是相同的struct Node { int data; Node* next; }; void bubbleSortLinkedList(Node* head) { if (!head || !head-next) return; bool swapped; Node* ptr1; Node* lptr nullptr; do { swapped false; ptr1 head; while (ptr1-next ! lptr) { if (ptr1-data ptr1-next-data) { swap(ptr1-data, ptr1-next-data); swapped true; } ptr1 ptr1-next; } lptr ptr1; } while (swapped); }这种实现通过交换节点数据而非节点本身来完成排序保持了算法的简洁性。6.4 并行化冒泡排序虽然冒泡排序本质上是一个串行算法但可以通过一些技巧实现部分并行化。例如奇偶排序(Odd-Even Sort)是冒泡排序的一种并行化变体void oddEvenSort(int arr[], int n) { bool isSorted false; while (!isSorted) { isSorted true; // 奇数索引元素与下一个偶数索引元素比较 for (int i 1; i n-2; i 2) { if (arr[i] arr[i1]) { swap(arr[i], arr[i1]); isSorted false; } } // 偶数索引元素与下一个奇数索引元素比较 for (int i 0; i n-2; i 2) { if (arr[i] arr[i1]) { swap(arr[i], arr[i1]); isSorted false; } } } }这种变体允许奇数对和偶数对的比较交换操作并行执行适合在多核处理器上实现。7. 性能测试与比较为了更直观地理解冒泡排序的性能特点我们可以进行一系列测试比较不同实现和不同数据规模下的表现。7.1 测试环境设置在进行性能测试时我们需要考虑以下因素测试数据规模从小规模(10-100)到中等规模(1000-10000)数据分布随机数据、已排序数据、逆序数据、部分有序数据实现版本基础实现、优化实现、双向冒泡等度量指标执行时间、比较次数、交换次数下面是一个简单的性能测试框架示例#include chrono #include random // 生成随机数组 void generateRandomArray(int arr[], int n, int minVal, int maxVal) { random_device rd; mt19937 gen(rd()); uniform_int_distribution dist(minVal, maxVal); for (int i 0; i n; i) { arr[i] dist(gen); } } // 测试排序函数性能 void testSortPerformance(void (*sortFunc)(int[], int), int arr[], int n, const string name) { auto start chrono::high_resolution_clock::now(); sortFunc(arr, n); auto end chrono::high_resolution_clock::now(); auto duration chrono::duration_castchrono::microseconds(end - start); cout name 用时: duration.count() 微秒 endl; }7.2 不同数据规模下的表现我们可以测试不同规模数据下的排序时间int main() { const int smallSize 100; const int mediumSize 1000; const int largeSize 10000; int smallArr[smallSize]; int mediumArr[mediumSize]; int largeArr[largeSize]; // 生成测试数据 generateRandomArray(smallArr, smallSize, 0, 1000); generateRandomArray(mediumArr, mediumSize, 0, 10000); generateRandomArray(largeArr, largeSize, 0, 100000); // 测试不同规模下的性能 cout 小规模数据( smallSize 个元素):\n; testSortPerformance(bubbleSort, smallArr, smallSize, 基础冒泡排序); cout \n中等规模数据( mediumSize 个元素):\n; testSortPerformance(bubbleSort, mediumArr, mediumSize, 基础冒泡排序); cout \n大规模数据( largeSize 个元素):\n; testSortPerformance(bubbleSort, largeArr, largeSize, 基础冒泡排序); return 0; }7.3 不同实现版本的比较比较基础实现与优化实现的性能差异int main() { const int size 5000; int arr1[size]; int arr2[size]; generateRandomArray(arr1, size, 0, 100000); copy(arr1, arr1 size, arr2); // 复制相同数据 cout 随机数据测试:\n; testSortPerformance(bubbleSort, arr1, size, 基础冒泡排序); testSortPerformance(optimizedBubbleSort, arr2, size, 优化冒泡排序); // 测试已排序数据 sort(arr1, arr1 size); sort(arr2, arr2 size); cout \n已排序数据测试:\n; testSortPerformance(bubbleSort, arr1, size, 基础冒泡排序); testSortPerformance(optimizedBubbleSort, arr2, size, 优化冒泡排序); return 0; }7.4 结果分析与实际观察通过测试我们通常会观察到以下现象对于小规模数据各种实现差异不大对于大规模随机数据优化版本的性能提升明显对于已排序或接近有序的数据优化版本特别是带提前终止的版本性能显著优于基础版本随着数据规模增大冒泡排序的性能劣势越来越明显这些观察结果验证了我们之前对算法复杂度的分析也说明了优化策略的实际价值。8. 教学价值与学习建议冒泡排序虽然在实际应用中较少使用但它在计算机科学教育中有着不可替代的价值。理解冒泡排序不仅是为了掌握一种排序方法更是为了培养算法思维和编程基础能力。8.1 为什么从冒泡排序开始学习冒泡排序是理想的入门算法原因包括概念简单直观易于理解和实现涵盖了算法基础要素循环、条件判断、变量交换展示了基本的算法优化思路为理解更复杂算法奠定基础8.2 常见学习误区与纠正学习冒泡排序时初学者常有以下误区认为冒泡排序在实际中广泛使用实际上它主要用于教学忽视优化版本的学习认为所有冒泡排序都一样低效不理解算法背后的思想只是机械记忆代码不能将排序思想应用到其他问题上纠正这些误区的方法包括实际测试不同规模数据下的性能表现比较不同优化版本的效率差异尝试用冒泡排序思想解决其他问题如最值问题手动模拟算法执行过程真正理解其工作原理8.3 进阶学习路径掌握冒泡排序后可以沿着以下路径继续学习其他基础排序算法选择排序、插入排序更高效的排序算法快速排序、归并排序、堆排序线性时间排序算法计数排序、桶排序、基数排序排序算法的理论分析时间复杂度、空间复杂度、稳定性实际应用中的排序STL中的sort函数、数据库排序8.4 实用学习建议为了更有效地学习冒泡排序及相关算法建议手动实现多种变体比较它们的差异使用可视化工具观察排序过程尝试对不同类型数据整数、浮点数、字符串、结构体进行排序参与在线编程练习平台的排序相关问题阅读标准库中排序实现的源代码如C STL的sort下面是一个简单的学习练习示例可以检验对冒泡排序的理解// 练习修改冒泡排序使其按降序排列 void descendingBubbleSort(int arr[], int n) { // 实现代码 } // 练习修改冒泡排序使其同时记录交换次数 int bubbleSortWithSwapCount(int arr[], int n) { int swapCount 0; // 实现代码 return swapCount; } // 练习实现一个模板函数可以对任意可比较类型进行冒泡排序 template typename T void genericBubbleSort(T arr[], int n) { // 实现代码 }通过这些练习可以加深对冒泡排序的理解并培养灵活应用算法思想的能力。
企业数字化 ERP 产品动态
相关推荐
城市生命线工程智慧化转型:物联网与数字孪生技术实践 1. 城市生命线工程的智慧化转型城市生命线工程就像人体的血管和神经系统,维系着现代都市的正常运转。从供水管网到燃气管线,从电力网络到交通枢纽,这些基础设施构成了城市运行的"生命线"。传统管理模式往往面临数据孤岛、响应滞后、… · 2026/11/3 4:55:53
Element Plus el-upload二次失效原因与四套生产级解决方案 1. 问题本质:不是组件“坏了”,而是状态管理被悄悄劫持了“element-plus:el-upload上传文件只能一次,第二次失效”——这个标题在Element Plus使用者的日常中出现频率极高,几乎成了前端开发群里的“暗号”。它背后根本… · 2026/11/3 4:55:53
Element Plus el-upload二次上传失效原因与解决方案 1. 问题本质与真实场景还原“element-plus:el-upload上传文件只能一次,第二次失效”——这个标题不是一句模糊抱怨,而是大量前端开发者在真实业务中反复踩坑后提炼出的精准症状。它背后往往对应着一个正在上线的内部管理系统、一个需要频繁提… · 2026/11/3 4:55:53
二手整机与笔记本完整验机:图吧工具箱硬件检测与烤机测试指南 二手整机、自组台式机、笔记本到手之后,很多人第一反应是装个跑分软件看个数字,分数漂亮就安心了。我这些年帮人看机器的经验恰恰相反:跑分只是最后一道仪式,真正能查出问题的,是图吧工具箱这类硬件检测合集里的那些&q… · 2026/11/3 5:22:59
源码静态评测:Strata 如何在消费级硬件跑本地大模型 本地跑大模型这件事,我从折腾第一台只有集显的笔记本起就没停过。那时候为了把一个 7B 模型塞进内存,光是量化格式就换了四五种,最后跑起来一秒吐三个字,风扇声比输出还热闹。所以当代码托管平台趋势榜第一的位置上出现一个叫 Str… · 2026/11/3 5:22:59
微信小程序开发全攻略:从基础到企业级实践 1. 微信小程序开发基础认知微信小程序作为腾讯生态的核心产品,其技术架构与传统网页开发存在本质差异。小程序采用双线程模型:渲染层使用WebView进行界面渲染,逻辑层运行在独立的JSCore中,通过Native进行桥接通信。这种架构设计既… · 2026/11/3 5:22:59
Paperclip:面向AI Agent的Node.js轻量运行时框架 1. “Paperclip”不是回形针:它是一套面向AI Agent开发的轻量级Node.js运行时框架最近在几个开源社区和前端技术群聊里,频繁看到“paperclip”这个词被提起——不是办公用品,也不是某款设计工具,而是一个正在快速积累关注的开源项… · 2026/11/3 5:22:59
新机开箱验机指南:硬件检测、硬盘健康与烤机测试全流程 某同学大促下单了一台整机,开箱当天跑了两把游戏,帧数漂亮,风扇声也在预期内,第二天就点了确认收货。三天后他想起我,让我帮忙看看。我打开图吧工具箱里那颗硬盘的健康页,通电次数 47 次、通电时间 186 小时… · 2026/11/3 5:22:59
TensorFlow安装与生产部署全链路指南 1. 这不是“装个库就完事”的事:TensorFlow到底在解决什么问题你搜“tensorflow安装”,页面跳出一堆报错截图和“pip install tensorflow失败”的求助帖;刷技术社区,总有人问“2024年还该学TensorFlow吗”;刚入门的同学… · 2026/11/3 5:21:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18