[黑马程序员]03[c语言]算法设计中标记思想的优化,耶稣门徒问题,生成打印问题

本文通过两个实例展示了如何使用标记法来简化算法设计过程。一是解决耶稣门徒问题,通过设置标记来代替复杂的数组操作;二是高效查找重复数字的方法,通过标记数组元素的值来快速确定重复项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

------- android培训java培训IOS培训、期待与您交流! ----------


前言:由于个人比较习惯从敲代码中熟悉掌握语法和具体语句的使用,因此不少的学习心得体会都是从具体的例题的不


同做法中总结出来的. 这里也将提到两个题的解题思想, 在比较自己做法和他人代码实现的过程中,我体会到了在适当的


时候设立一个标记变量,对代码算法的清晰化和简洁化有时候会提供很大的帮助.


这里作为博客日志的方式记录下来,方便以后翻阅.

 

正文:


耶稣门徒问题:耶稣有13个门徒,其中有一个就是出卖耶稣的叛徒,请用排除法找出这位叛徒:13人围坐一圈,从第一个开始报号:123123...。凡是报到“3”就退出圈子,最后留在圈子内的人就是出卖耶稣的叛徒。请找出它原来的序号。

 

//我自己的思路是这样的:

//代码思路

//intnums[1,2,3,4,5,6,7,8,9,10,11,12,13];

//while(length不为1)则循环

//{}

//给一个报号标记intn,并将其初始化为1,每报到3就把他从数组中剔除,并且将断点后全体数组缩进1,一直读到数组末尾

//跳回数组开头继续读,重复上面的操作,直到Length1,返回数组[0]

//

//while(length !=1)

//{

//    int n =1;

//    当前该行的length:count

//    while(没有读完一行length!=count||i!=count)

//    {

//        if(n%3!=0)

//        {

//           收入下一个数组

//            n++;

//            i++;

//        }

//        else

//        {

//           剔除并集体缩进1

//            for(int j=0;j<length,j++)

//            {

//                a[j]=a[j+1];

//            }

//            length--;

//            n=1;

//        }

//    }

//}

 

 

具体实现代码是这样的:

实现代码

#include<stdio.h>

#define LENGTH13

 

int smoothNum(int *array);

 

int main()

{

    int nums[LENGTH] ={1,2,3,4,5,6,7,8,9,10,11,12,13};

   

    printf("出卖耶稣的是第%d个人\n",smoothNum(nums));

   

    return0;

}

 

int smoothNum(int *array)

{

    int n=1;

    int length =LENGTH;

    while (length !=1) {

        int count =(length-(length+(n-1))/3);

        //当前行的长度由什么决定

        int i=0;

            while((length!=count)||(i!=count))

            {

                if (n%3 !=0) {

                    n++;

                    i++;

                }

                else{//从当前断点整体向前缩进1

                        for (int j=i; j <length; j++) {

                            array[j]=array[j+1];

                    }

                    length--;

                    n =1;

                }

            }

        }

        return array[0];

}

 

在由伪代码转向具体代码的时候在调试上出了点问题,花了不少时间才调试正确,主要是由于算法实现的基本思路是考


虑找到报数为3的人的时候,将其从当前数组中剔除,然后其后的数组集体向前缩进1,但是由于起始报数n的动态变化,当


前行的length并不能简单地视为上一行的长度减去剔除元素个数,还需要跟随当前新数组起始元素的报数n而变化,因此


花了不少时间逐行单步调试才修正过来.



但是看到别人的一种算法的时候,我发现采用标记后代码的清晰度会好很多,完全避免了因为动态n和动态length缩进导


致的这种条件判断出错

#define ALL_NUM13

#define OUT_NUM3

 

int main(void)

{

    int people[ALL_NUM];

    int pos, people_num= ALL_NUM, step =0;

   

    for(pos =0; pos <ALL_NUM; pos++)

      people[pos] =pos + 1;

    pos = 0;

    while(people_num >0)

    {

      if(people[pos])

         step++;

      if(people[pos]&& step == OUT_NUM){

         printf("%d out\n", people[pos]);

         people[pos]= 0;

         people_num--;

      }

      pos++;

      if(pos == ALL_NUM)

         pos = 0;

      if(step == OUT_NUM)

         step = 0;

    }

   

    return0;

}

 

这个就是采用标记后的耶稣问题算法,具体的算法实现就不提了,基本思路还是一样的,但是很重要的一点,也是我感觉对


于自己代码思路可以进一步优化改善的一点是, 这种写法加入了一个标记的思想, 每当查找到一个报数为3的人的时候,


并不需要将其从数组中提出从而带来繁琐的数组缩进以及遍历长度重定义的问题,而是强制性地将其内部值置为零,因


为我们需要返回的是叛徒的序列号,因此序列号本身是不为零的,这里将查找后需要踢出循环的数标记为0后,就可以通过


它的值是否为零的附加条件判断来跳过这个应该被剔除的数,从而达到同样地功能,这种标记的好处,标记思想的运用,给


了我很大的启发

 

 

 

另外则是一个普通的重复查找问题:

3. 1000000个数,每个数取值范围是0-999999,找出其中重复的数。

#include<stdio.h>

#include<stdlib.h>

#define LENGTH10

#define RANGE10//改成1000000是一样的,然而计算量太大,需要很久很久

 

void createArray(int *array);

 

void printArray(int *array,int length);

 

void findRepeat(int *array);

 

int main()

{

    int nums[LENGTH];

   

    createArray(nums);

   

    printArray(nums,LENGTH);

   

    findRepeat(nums);

}

 

void createArray(int *array)

{

    for (int i=0; i <LENGTH; i++) {

        array[i] = arc4random_uniform(RANGE);

    }

}

 

void findRepeat(int *array)

{

    int repNums[LENGTH];

    int k =0,i=0;

N:

    for (; i<LENGTH; i++) {

        for (int j=i+1; j<LENGTH; j++) {

            if (array[i]==array[j]) {

                //如果不重复,放进去

                int m=0;

                while(m<=k){

                    if (array[i]==repNums[m])

                    {

                        i++;

                        goto N;

                    }

                    m++;

                }

                repNums[k++]=array[i];

            }

        }

    }

   

    printArray(repNums, k);

}

 

void printArray(int *array,int length)

{

    printf("重复的数字元素为:\n");

    for (int i=0; i<length; i++) {

        printf("%d ",array[i]);

    }

    printf("\n");

}

 

上面我本身的做法是封装成几个函数,用for循环嵌套的方式锁定一个数,依次查找其他元素来判断是否重复,这种做法极


其繁琐,数量小还行,像这里达到七位数后得到结果即使是计算机的计算能力,也需要运行很久,于是在网上搜索,找到二楼


一种相比较而言非常简单地做法:

#define COUNT1000000

 

int main(void)

{

    int a[COUNT] = {0};

    for (int i =0; i < COUNT; i++) {

        int number =arc4random_uniform(COUNT)%COUNT;

        //把随机数作为下标,值作为出现次数

        a[number - 1] ++;

    }

    //输出重复的数字以及重复次数

    for(int i =0 ;i < COUNT;i++){

        if (a[i] > 1) {

            printf("%d repeats %dtimes\n",i+1,a[i]);

        }

    }

 

这种做法的思路是直接定义一个跟数据大小相同的数组,直接把数组下标当成元素在数组中得位置, 采用标记的思想将


新建数组的全体元素置为零,每当生成一个大小为Number的随机数时,数组位置为nums[Number-1]元素的值就加一,全


体随机完成后仅需要判定数组元素值是否大于1就可以判断是否生成重复数据,并且得到重复次数.

 

 

这两个题都是运用了适当取一个标记的思想来简化算法,对我受益匪浅,适当标记来简化算法的思路,相信也会渗透到我


今后代码算法的思想里去.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值