C Day 23

完整一轮没有交换,
就可以提前结束

今天在昨天的冒泡排序上增加交换标记和两个计数器,观察已经有序、近乎有序与反向排列的数组需要多少次比较和交换。

当前一对没交换,不等于整个数组有序

对于 {1,3,2,4},第一对 1 和 3 不需要交换,但下一对 3 和 2 需要交换。所以必须从左到右完成一整轮比较,才能判断这一轮是否真的没有交换。

用 swapped 记录本轮是否交换过

每轮开始时把 swapped 设为 0;只要真正交换过一次,就设为 1,并在本轮剩余时间保持 1。它记录的是“本轮是否曾经交换”,不是“刚才这一对是否交换”。

一开始,我把同一轮的第二次比较误当成第二轮,认为不交换后应该把标记改回 0。逐步追踪后,确认只有下一轮开始时才重置,后面的不交换不能抹掉本轮已经发生的交换。

在内层循环结束后判断

完整内层循环结束后,如果 swapped == 0,就执行 break。这里的 break 位于内层循环外、外层循环内,结束的是外层排序循环;之后仍会输出数组和统计结果。

对于 {2,1,3,4},第一轮交换后数组已经有序,但标记为 1,因此还会进入第二轮;第二轮没有交换,才通过标记确认可以提前结束。

区分本轮标记与全程计数

  • swapped:每轮重置,只回答本轮是否交换过。
  • compare_count:在外层循环前初始化,每比较一对相邻元素就加 1,不交换也要计数。
  • swap_count:在外层循环前初始化,仅在交换分支里加 1,累计整个排序过程的交换次数。

比较次数只统计数组元素之间的大小比较,不包含 for 的继续条件判断。三条赋值共同完成一对元素的交换,因此算一次交换。

最终练习代码

day23.c · 最终保存版本
#include <stdio.h>

int main(void)
{
    int values[] = { 4, 3, 2, 1 };
    size_t count = sizeof(values) / sizeof(values[0]);

    size_t compare_count = 0;
    size_t swap_count = 0;

    for (size_t round = 0; round < count - 1; round++)
    {
        int swapped = 0;

        for (size_t index = 0; index < count - 1 - round; index++)
        {
            compare_count++;

            if (values[index] > values[index + 1])
            {
                int temp = values[index];
                values[index] = values[index + 1];
                values[index + 1] = temp;
                swapped = 1;

                swap_count++;

            }
        }

        printf("第 %zu 轮结束,swapped = %d\n", round + 1, swapped);

        if (swapped == 0)
        {
            printf("已经有序,提前结束\n");
            break;
        }
    }

    printf("排序后的数组:\n");

    for (size_t index = 0; index < count; index++)
    {
        printf("%d ", values[index]);
    }

    printf("\n");

    printf("比较次数:%zu\n", compare_count);
    printf("交换次数:%zu\n", swap_count);

    return 0;

}

三组实际运行验证

初始数组       比较次数  交换次数
{2,1,3,4}          5         1
{1,2,3,4}          3         0
{4,3,2,1}          6         6

三组最终数组:1 2 3 4

{2,1,3,4} 的两轮标记依次为 1、0,第二轮后提前结束;{1,2,3,4} 第一轮就确认有序,只比较 3 次。昨天没有提前结束的版本对四个元素固定比较 3+2+1=6 次。

{4,3,2,1} 每轮都发生交换,三轮标记均为 1;数组依次变为 {3,2,1,4}、{2,1,3,4}、{1,2,3,4}。它不会显示提前结束提示,而是完成规定轮数后自然结束。三组运行均正常退出,代码为 0。

订正与最后的理解检查

初次统计 {2,1,3,4} 时,我把每轮比较算成了一次。逐对列出后,确认第一轮比较 3 次,第二轮比较 2 次,总共 5 次;即使没有交换,也已经进行了一次比较。

反向排列题中,三轮数组和每轮次数都预测正确,只是在问总数时漏看了“总共”,回答了第三轮次数。补答总计 6 次比较、6 次交换后确认无误,这次属于审题遗漏。

最后能说明标记为什么每轮重置、计数器为什么全程累计;也能指出把 compare_count++ 放到交换分支里,实际就变成了交换次数。判断有序要等完整一轮结束,因为下一对仍可能需要交换。

下一课是 C 第 24 天阶段检测,复习第 17—23 天的数组、输入遍历、最值、长度与平均值、查找计数、交换反转和冒泡排序。