数组元素奇偶排序程序中的死循环引起的思考

一、问题描述

有一数组:
a[8] = {56,68,20,15,17,101,132,119,}
对该数组进行排序使得奇数全部在偶数的前面,并且不可以引进分配新的数组空间。

二、问题解决

看到这样的题目,也许会觉得很简单,因为题目的目的很明确,只要将奇数全部移到偶数的前面即可,而奇数与奇数之间、偶数与偶数之间都不要求顺序。最开始我想到了如下代码:

void sort_one(int * a,int len)
{
    int i, j;
    int temp;

    for (i=1; i<len; i++)
    {
        if (a[i]%2 != 0)
        {
             temp = a[i];
             for (j=i; j>0; j--)
                 a[j] = a[j-1];
             a[0] = temp;
        }
    }
}

由于该段代码中a[i]只要是奇数就将a[i]移动到数组首位置,而a[0]至a[i-1]依次向后移动到a[2]至a[i]的位置,该方法决定了判断只需从a[1]开始。若是不满足if语句,i就加1,判断下一个即可,外层for循环执行完成时,奇偶排序也就完成了。

三、虽为“同胞兄弟”确是“死胎”的一段代码

上面的程序是我自己见到这样的问题时的第一想法,当时写完之后没有出错也就没有再管,而一个月后,又看到别人写了下面这段类似于sort_one(int * a,int len),却无任何输出的程序:

void sort_error(int * a, int len)
{
    int temp;
    int i = 0;

    while(i<len)//只要数组中有一个偶数就会陷入死循环
    {
        if(a[i]%2 != 0)
            i++;
        else
        {
            temp = a[i];
            for(int j = i; j<len; j++)
                a[j] = a[j+1];
            a[j] = temp;
        }
    }

}

该程序思路与sort_one(int * a,int len)思路刚好相反,sort_one(int * a,int len)是将奇数放到最前面,sort_error(int * a,int len)则是将偶数移到到数组最后面,判断并处理完成之后,也是奇数在前偶数在后。从思路上看起来似乎可行,但是却没有任何输出,这段程序引发了我的思考。用图来说明:

这里写图片描述
当i=0时,a[0]=56是偶数,移到最后面;68向前移到a[0],依旧是偶数,移到后面;20向前移到a[0],仍然是偶数,再移到最后面;15向前移到a[0],不是偶数,则i++;17、101、131、119均是奇数,i继续自加。
这里写图片描述
当i加到如上图所示:a[i]=56,是偶数,则56移到数组最后;68、20前移,a[i]=68,是偶数,则68移到数组最后;20、56前移,a[i]=20,是偶数,则20移到数组最后;56、68再前移,a[i]=56……,由于没有奇数,i便不能自加,i < len一直成立,循环无法退出便进入了死循环。只要数组中有一个偶数,便会形成死循环,此段程序也就没有任何作用。

四、妙手回春

对于sort_error(int * a,int len),其主要问题在于它自身的退出循环条件与程序的执行方式相互冲突:i在遇到偶数时不能自加,而把偶数都移到最后导致i始终不能加到len,程序陷入死循环可谓是“自作孽,不可活”。那么我们的解决方法就是使循环结束条件与执行方式不冲突。
而唯一的办法就是当偶数向后移动一次,退出循环的条件就不能再是i < len了,而是在i < len-1,若是移动了两次,那么数组最后应有两个偶数,而退出条件也应该成为i < len-2 ……,所以我们不妨设定一个标志point = len-1,每当偶数向后移动一个,point就自减一次,而退出循环的条件改为i < point即可。代码如下:

void sort_two(int * a, int len)
{
    int i = 0;
    int j, temp;
    int point = len-1;

    while(i < point)
    {
        if(a[i] % 2 != 0)
            i ++;
        else
        {
            temp = a[i];
            for(j = i; j < point; j++)
                a[j] = a[j+1];
            a[j] = temp;
            point --;
        }
    }
}

即使数组全是偶数i不自加,但每移动一次point自减一次,最终i < point也能不成立,不会陷入死循环。

五、更胜一筹的算法

以上两种算法,都是我们最直接最容易想到的,但是一般情况下最直接的方法都不是最高效的算法。因为我们为了解决问题而解决问题都会想到思维量小的方法,而思维量越小发现问题的本质不会深入,得出的结果自然不会高效。

void sort_two()和void sort_one()由于每次移动一个满足判断条件的数,都要成批地依次移动起点到目的点之间的所有数据元素,把不必要的步骤执行了许多遍。那么如果我们让i从前往后找偶数,让j从后向前找奇数,并把从前面找到的第一个偶数与从后面找到的第一个奇数互换位置,而不动其它不相关的数,之后继续执行以上操作,把从前面找到的第二个偶数与从后面找到的第二个奇数互换位置……当i与j的相对位置变成i后j前时,奇数就全部移到偶数的前面了。代码如下:

void sort_three(int * a, int len)
{
    int i = 0;
    int j = len-1;
    int temp;
    while(i<j)
    {
        while(i<len && a[i]%2 != 0) 
            i++;
        while(j>-1 && a[j]%2 == 0)  
            j--;
        if(i<j)
        {
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
    }
}

循环继续执行的条件是i < j,而a[i]与a[j]交换的条件也是i < j。因为当i > j时,如果交换a[i]与a[j],就又把奇数从前面移到后面,偶数从后面移到前面了。

例如:原题中代码a[8] = {56,68,20,15,17,101,132,119,}
用sort_three()只需要将不同的a[i]与a[j]互换三次即可,即使奇数全在偶数后面,最多也仅仅是需要互换len/2次。而sort_one()和sort_two()仅仅是移动元素就需要移动二三十次,当数组过长时其缺点就显得尤为突出。

六、算法时间复杂度与高效性的非必然联系

在以上所有程序都是两层循环,时间复杂度都是O(n^2),我们将sort_three()稍作修改就可以降低其时间复杂度,代码如下:

void sort_four(int *a, int len)
    {
        int i = 0;
        int j = len-1;
        int temp;
        while (i<j)
        {
            if(a[i] % 2 != 0)
                i++;
            else if(a[j] % 2 == 0)
                j--;
            else 
            {
                temp = a[i];
                a[i] = a[j];
                a[j] = temp;
            }
         }
    }

少了一层循环嵌套,时间复杂度虽然降低了,但是并没有比sort_three()高效,因为其算法相同,代码执行步骤也是一样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以使用以下的方法来实现C语言数组元素奇偶排序: 1. 遍历数组,找出所有的奇数和偶数,并分别存放在两个新的数组。 2. 对两个新的数组进行排序,可以使用冒泡排序、插入排序或者快速排序排序算法。 3. 将两个排序后的数组合并成一个新的数组,即先将奇数数组的元素复制到新数组,再将偶数数组的元素复制到新数组的末尾。 4. 最后,新的数组的元素就是按照奇偶排序的结果。 以下是一个示例代码: ```c #include <stdio.h> // 交换两个元素的值 void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } // 冒泡排序 void bubbleSort(int arr[], int size) { for (int i = 0; i < size - 1; i++) { for (int j = 0; j < size - i - 1; j++) { if (arr[j] > arr[j + 1]) { swap(&arr[j], &arr[j + 1]); } } } } int main() { int arr[] = { 3, 1, 4, 2, 5, 6 }; int size = sizeof(arr) / sizeof(arr[0]); int odd[size], even[size]; int oddCount = 0, evenCount = 0; // 将奇数和偶数分别存放在两个数组 for (int i = 0; i < size; i++) { if (arr[i] % 2 == 0) { even[evenCount++] = arr[i]; } else { odd[oddCount++] = arr[i]; } } // 对两个数组进行排序 bubbleSort(odd, oddCount); bubbleSort(even, evenCount); // 合并两个数组 int merged[size]; for (int i = 0; i < oddCount; i++) { merged[i] = odd[i]; } for (int i = 0; i < evenCount; i++) { merged[oddCount + i] = even[i]; } // 打印排序后的结果 for (int i = 0; i < size; i++) { printf("%d ", merged[i]); } return 0; } ``` 该代码首先将原始数组奇数和偶数分别存放在两个新的数组,然后对这两个数组进行排序。最后,将排序后的奇数数组和偶数数组合并成一个新的数组,并打印出排序后的结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值