Administrator
发布于 2025-09-26 / 12 阅读
1
0

C#6.4 排序和查找-冒泡排序法

冒泡排序是一种简单的排序算法,它重复地遍历要排序的列表,比较相邻的元素,如果顺序错误就交换它们。

基本思想

  1. 从列表的第一个元素开始,比较相邻的两个元素

  2. 如果顺序错误(前一个比后一个大),就交换它们

  3. 对每一对相邻元素重复这个过程,直到列表末尾

  4. 重复上述步骤,每次遍历都会将最大的元素"冒泡"到正确位置

int[] arr = { 6, 3, 4, 1, 2 };
for (int i = 0; i < arr.Length - 1; i++)
{
    for (int j = 0; j < arr.Length - i- 1; j++)
    {
        if (arr[j] > arr[j+1])
        {
            int temp = arr[j];
            arr[j] = arr[j+1];
            arr[j+1] = temp;
        }
    }
    Console.WriteLine(string.Join(",",arr));
}  

为什么是n-1?

  • 有n个元素,需要确定n个位置

  • 但每轮冒泡可以确定1个最大元素的位置

  • 当确定了n-1个元素的位置后,最后1个元素自然就在正确位置

  • 所以只需要 n-1

原始数组: [5, 3, 8, 1, 2] (n=5)
​
第1轮: 将最大的8冒泡到最后 → [3, 5, 1, 2, 8]
第2轮: 将第二大的5冒泡到最后 → [3, 1, 2, 5, 8]  
第3轮: 将第三大的3冒泡到最后 → [1, 2, 3, 5, 8]
第4轮: 将第四大的2冒泡到最后 → [1, 2, 3, 5, 8]
​
此时只剩下1个元素[1],它已经在正确位置,不需要第5轮!

为什么是n-i-1

因为每一轮排序后,最大的元素会“冒泡”到当前未排序序列的末尾。因此,下一轮就不需要再比较已经排好序的部分。 第0轮(i=0)时,未排序部分包括所有n个元素,我们需要比较n-1次(因为比较是相邻两个元素,所以从0到n-2)。 第1轮(i=1)时,最后一个元素已经排好序,所以只需要比较前n-1个元素,即比较n-2次。 所以,内层循环的边界是j < n - i - 1。

比如:内层循环中我们比较的是 arr[j]arr[j+1]

  • j = n-2 时,比较的是 arr[n-2]arr[n-1]

  • 如果 j 达到 n-1,那么 j+1 = n 就会数组越界!

初始: [5, 3, 8, 1, 2]
      ↑  ↑  ↑  ↑  ↑
索引: 0  1  2  3  4
​
第1轮(i=0): j < 4 → 比较: 0-1, 1-2, 2-3, 3-4
第2轮(i=1): j < 3 → 比较: 0-1, 1-2, 2-3  (跳过索引4)
第3轮(i=2): j < 2 → 比较: 0-1, 1-2      (跳过索引3-4)
第4轮(i=3): j < 1 → 比较: 0-1          (跳过索引2-4)

冒泡排序 vs 选择排序

特性

冒泡排序

选择排序

交换次数

较多

较少

比较次数

相同

相同

稳定性

稳定

不稳定

适用场景

小规模或基本有序数据

小规模数据

冒泡排序虽然效率不高,但它的实现简单,是理解排序算法原理的好起点!

1. 用生活中的比喻开始

想象一下你在整理一排水杯,从左到右按从矮到高的顺序排列:

初始顺序:5, 3, 8, 1, 2

冒泡排序就像这样工作:

  • 从左边开始,比较相邻的两个水杯

  • 如果左边的比右边的高,就交换它们的位置

  • 一轮比较下来,最高的水杯就会"冒泡"到最右边

  • 重复这个过程,直到所有水杯都排好序

2. 简单直观的解释

// 初始:5, 3, 8, 1, 2
​
// 第1轮冒泡:
// 比较5和3 → 交换 → 3,5,8,1,2
// 比较5和8 → 不交换 → 3,5,8,1,2  
// 比较8和1 → 交换 → 3,5,1,8,2
// 比较8和2 → 交换 → 3,5,1,2,8 (8冒泡到最后)
​
// 第2轮冒泡:
// 比较3和5 → 不交换 → 3,5,1,2,8
// 比较5和1 → 交换 → 3,1,5,2,8
// 比较5和2 → 交换 → 3,1,2,5,8 (5冒泡到倒数第二)
​
// 继续这个过程...

3. 基础代码演示(带详细注释)

using System;
​
class Program
{
    static void Main()
    {
        // 我们要排序的数字数组
        int[] numbers = { 5, 3, 8, 1, 2 };
        
        Console.WriteLine("=== 冒泡排序演示 ===");
        Console.WriteLine("原始数组: " + string.Join(", ", numbers));
        
        // 调用冒泡排序方法
        BubbleSort(numbers);
        
        Console.WriteLine("\n最终结果: " + string.Join(", ", numbers));
    }
    
    static void BubbleSort(int[] arr)
    {
        int n = arr.Length;
        
        // 外层循环:控制需要多少轮冒泡
        // 每轮会让一个最大的数"冒泡"到正确位置
        for (int i = 0; i < n - 1; i++)
        {
            Console.WriteLine($"\n🎯 第{i+1}轮冒泡开始:");
            Console.WriteLine("当前数组: " + string.Join(", ", arr));
            
            // 内层循环:进行相邻元素的比较和交换
            for (int j = 0; j < n - i - 1; j++)
            {
                // 显示比较的两个数字
                Console.Write($"  比较 [{arr[j]}] 和 [{arr[j+1]}]");
                
                // 如果左边的数比右边大,就交换它们
                if (arr[j] > arr[j + 1])
                {
                    // 交换两个数字
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    
                    Console.WriteLine(" → 🔄 交换!");
                }
                else
                {
                    Console.WriteLine(" → ✅ 顺序正确,不交换");
                }
            }
            
            // 显示本轮结束后的结果
            Console.WriteLine($"第{i+1}轮结束: " + string.Join(", ", arr));
            
            // 显示哪个数字"冒泡"到了右边
            if (n - i - 1 > 0)
            {
                Console.WriteLine($"数字 {arr[n - i - 1]} 冒泡到了正确位置");
            }
        }
    }
}

4. 可视化执行过程

用箭头和颜色展示每一轮的变化:

初始: [5, 3, 8, 1, 2]
​
第1轮:
5↔3 → 交换 → [3, 5, 8, 1, 2]
5↔8 → 不换 → [3, 5, 8, 1, 2]  
8↔1 → 交换 → [3, 5, 1, 8, 2]
8↔2 → 交换 → [3, 5, 1, 2, 8]  ← 8冒泡到最后
​
第2轮:
3↔5 → 不换 → [3, 5, 1, 2, 8]
5↔1 → 交换 → [3, 1, 5, 2, 8]
5↔2 → 交换 → [3, 1, 2, 5, 8]  ← 5冒泡到倒数第二
​
第3轮:
3↔1 → 交换 → [1, 3, 2, 5, 8]
3↔2 → 交换 → [1, 2, 3, 5, 8]  ← 3冒泡到倒数第三
​
第4轮:
1↔2 → 不换 → [1, 2, 3, 5, 8]  ← 排序完成!

5. 关键概念解释

为什么叫"冒泡"排序?

因为大的数字会像气泡一样慢慢向上浮(向右移动)到正确的位置。

相邻比较的重要性

// 我们只比较相邻的两个元素
if (arr[j] > arr[j + 1])  // j和j+1是相邻的
{
    // 交换它们
}

为什么内层循环条件是 j < n - i - 1

  • n - i:排除已经排好序的元素(每轮结束后,右边的i个元素已经有序)

  • -1:防止数组越界(因为我们要比较j和j+1)

6. 优化版本:提前结束

如果一轮中没有发生任何交换,说明数组已经有序,可以提前结束:

static void OptimizedBubbleSort(int[] arr)
{
    int n = arr.Length;
    bool swapped; // 标记是否发生了交换
    
    for (int i = 0; i < n - 1; i++)
    {
        swapped = false; // 每轮开始时重置标记
        Console.WriteLine($"\n第{i+1}轮开始:");
        
        for (int j = 0; j < n - i - 1; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                // 交换代码...
                swapped = true; // 发生了交换
            }
        }
        
        // 如果一轮中没有发生交换,说明已经有序
        if (!swapped)
        {
            Console.WriteLine("🌟 没有发生交换,数组已经有序!");
            break; // 提前结束排序
        }
    }
}

7. 动手练习

练习1: 修改代码实现从大到小排序

// 提示:只需要修改比较符号
// 把 > 改为 <

练习2: 对字符串数组按字母顺序排序

string[] words = {"banana", "apple", "cherry", "date"};
// 提示:使用 string.Compare(str1, str2)

8. 常见问题解答

Q: 冒泡排序和选择排序有什么区别? A:

  • 选择排序:每轮找到最小的放到前面

  • 冒泡排序:每轮通过相邻比较让最大的冒泡到最后

Q: 冒泡排序快吗? A: 对于小数据量可以,大数据量比较慢。但实现简单,容易理解。

Q: 为什么内层循环每次都要从头开始? A: 因为我们需要确保每个大的数字都能一步步"冒泡"到正确位置。

9. 记忆口诀

"相邻比较,大的右移, 一轮结束,最大沉底, 重复进行,直到有序。"

10. 总结

冒泡排序的核心思想:

  1. 比较:比较相邻的两个元素

  2. 交换:如果顺序错误就交换

  3. 重复:多次遍历直到没有交换发生


评论