C语言Educoder——编程作业/算法/基本示例

文章目录

Educoder编程作业/算法/基本示例

字符运算:

scanf和getchar

scanf("%c",)会读入空格,而scanf("%s,")不会读入空格
getchar相当于scanf("%c",); 因此会读入空格

%c和‘ ’的理解

任务描述
本关任务:编写一个程序,从终端输入一个字符,判断该字符是否是十六进制数字字符,如果是,则输出该数字字符对应的整数,否则以十进制形式输出该字符对应的ASCII码。
例:
输入字符:‘a’ //a是十六进制字符;
输出结果: 10 //打印输出对应的十六进制整数10。
输入字符:‘g’ //g不是进制字符;
输出结果: 103 //g对应的ASCII值为103。

#include <stdio.h>

int main() {
char n;
scanf("%c",&n);
/*输入字符n,scanf存入的是这个数字的ASCII码*/
if(n>=48&&n<=57)
    printf("%d",n-48);
    /*由于%d,会打印出十进制数字,即对ASCII码算数运算后得到的结果*/
else if(n>=65&&n<=70)
    printf("%d",n-55);
else if(n>=97&&n<=102)
    printf("%d",n-87);
else
    printf("%d",n);
	return 0;
}


任务描述
输入两个实数和一个四则运算符( + - * / ),根据运算符执行相应的运算并输出结果,使用if语句完成。保留一位小数。

#include<stdio.h>
int main()
{   
    float a,b;
    char c;
    scanf("%f %f %c",&a,&b,&c);
    /*同上,c存入的还是一个数字*/
    if(c==43)
    /*c==43 相当于c=='+' ,
    字符写成单引号引起来的形式,本身表示的就是一个数字*/
        printf("%.1f",a+b);
    else if(c=='-')
        printf("%.1f",a-b);
    else if(c=='*')
        printf("%.1f",a*b);
    else if(c=='/')
        printf("%.1f",a/b);
    return 0;
}


getchar和putchar函数

  • 一般用在循环中,每一轮读入一个字符,输出一个字符。
  • getchar()就相当于scanf(%c,x);

判断输入的字符串是否符合手机号码的格式,手机号码的特点:①长度11位;②每一位都是数字,且第一位是1,第二位是3、4、5、7、8中的任意一个。要求程序能循环接受用户的输入,直至输入Ctrl+Z结束。

#include<stdio.h>
#include<ctype.h> ①*****
int main()
{
    int count_num=0,count=0;
    char number;
    while((number=getchar())!=EOF)*****
    {
    	count++;
    	if(count==1) 
    	{
    		if(number!='1')
    		{
    		printf("手机号码第一位不合法\n"); 
    		}
		}
		if(count==2)
		{
			if(number!='3'&&number!='4'&&number!='5'&&number!='7'&&number!='8')
			{
			printf("手机号码第二位不合法\n");
			}
		}
    	if(isalpha(number)) 
		{	count_num++;
			if(count_num>1) ;*****
			else printf("手机号码中无字母!\n");
		} 
		if(number=='\n')*****
		{
			if(count!=12)
			{
				printf("长度不合法\n");
			}
			else printf("恭喜!这是一个正确的电话号码!\n");
			count=0;*****
		}
	}
    return 0;
}

注:

  1. 头文件ctype.h中有很多好用的函数。
  2. 注意这一句话EOF的写法。
  3. 如果字母数大于1,就什么也不执行(空语句),从而保证只输出一次“手机号码中无字母”。
  4. 输完一个数字,敲回车键后会留下换行符,检索换行符即可知道输入了一个数字,注意这里的写法。
  5. 把count重置,以免输入下一个数字的时候依然报“长度不合法”。

输入一个仅有英文大写字母、逗号、句点和空格符组成的英文句子(字符数不超过80个)和正整数k(1≤x≤26),将其中的大写英文字母替换成字母表中该字母开始的第k个字母,其它字符保持不变。例如,k取9时,A替换成I,Y替换成G。为了使原句难于破译,再将上述转换后的句子自左至右两两字符交换,若最后仅剩下单个字符则不换。输出最后的密文句子。

#include<stdio.h>
int main()
{
    int k,count=0;
    char n[81],temp;//数组长度要多1个,用于存放结尾字符
    printf("请输入一个1到26之间的整数:\n");
    scanf("%d",&k);
    getchar();*****
    printf("请输入一个仅有英文大写字母、逗号、句点和空格符组成的英文句子,且字符数不超过80个:");
    for(count=0;(n[count]=getchar())!='\n';count++)*****
    {
        if(count==80) break;
        if(n[count]>='A'&&n[count]<='Z')
            n[count]=(n[count]-'A'+k-1)%26+'A';
        putchar(n[count]);
    }
    n[count]='\0';*****
    printf("\n大写转换结果:%s",n);
    count=0;
    while(n[count]!='\0')
    {
        if (n[count+1]=='\0')
            break;
        temp=n[count];
        n[count]=n[count+1];
        n[count+1]=temp;
        count+=2;
        /*实现两两交换的算法,还是需要数组的*/
    }
    printf("\n两两交换结果:%s",n);
    return 0;
}

注:

  1. 清除scanf遗留的回车字符 (重要)
  2. 回车作为结束。
  3. 添加字符串结尾字符。

注意数组名是n,不是char,正确写法是n[count]。


位运算

整数的二进制表示

#include <stdio.h>
void Binary(int n, char *buf);

int main(void)
{
    char buf[128] = { 0 };
    /* */
    int n;
    scanf("%d",&n);
    Binary(n,buf);
    puts(buf);
    /*打印数组内所有元素*/
}

void Binary(int n,char *buf)
{
    for(unsigned i=0,m=(1 <<31);i < 32; ++i, m >>=1)
    /*m>>=1 的意思是将m向右移位1次的值,再赋给新的m*/
    {
        *buf++ = (n & m) ? '1' : '0';
        /*
        检验每一位的值是1还是0
        只有最左边一位是1,其余均为0
        */
        if ((i & 3) == 3) *buf++=' ';
        /*控制输出空格*/
    }
    *buf=0;
}

浮点数的二进制表示

不需要关注浮点数是怎么存入计算机的,只要把它的每一位提取出来即可

从键盘输入10个单精度浮点数,以二进制形式存入文件float.dat中(将此功能定义成函数)。再从文件中读出这10个单精度浮点数,把每个单精度浮点数,按每个字节的二进制形式显示在屏幕上(将此功能定义成函数)。

#include <stdio.h>
#include <stdlib.h>
void writeFloats(const char *fileName);
void readFloats(const char *fileName);

int main(){
    const char fileName[] = "float.dat";
    writeFloats(fileName);
    readFloats(fileName);
    return 0;
}

void writeFloats(const char *fileName){
    FILE *fp;
    fp = fopen(fileName,"wb");
    float nums[10];
    for (int i = 0; i < 10; ++i)
    {
        scanf("%f", nums + i);
    }
    fwrite(nums, sizeof(float), 10, fp);
    //二进制文件必须使用fwrite,不能使用fputs

    fclose(fp);
}

void readFloats(const char *fileName){
    FILE *fp;
    fp = fopen(fileName,"rb");
    float nums[10];
    for(int i = 0; i < 10; i++){
        unsigned bin = *(unsigned *)(nums + i);
        //必须将float型转换为unsigned型,否则无法移位运算
        //这种转换必须通过指针实现
        for(int j = 31; j >=0; j--){
            putchar('0'+ ( (bin>>j) &1));
        }
        putchar('\n');
    }  
    fclose(fp);  
}


位运算实现部分功能

两数交换
m=m^n;
n=m^n;
m=n^m;
/*用位运算实现交换m和n,使用异或*/

使用宏定义:

#define swap(x,y) (x ^= y ^= x ^= y)
判断奇偶
while((m<<31)>>31==0&&(n<<31)>>31==0)
/*用位运算判断是否为偶数*/ 

高低字节组合

本关任务:程序填空,写一个表达式,将short类型整数k的高字节作为结果的低字节,将short类型整数p的低字节作为结果的高字节,拼成一个新short类型整数。

 ((k&0xff00)>>8)^(p<<8)   
 //算数右移的时候,由于符号位可能会产生干扰,应和0xff00取与运算

循环移位

任务描述
本关任务:程序填空,写一个表达式,将int整数x向右循环移动n位,即向右移出的位直接放在最高位。例如:
输入x的值:1234(对应二进制为00000000 00000000 00000100 11010010);
输入n的值: 10 ,
表达式完成计算后,x的值改变成:880803841 (对应二进制为00110100 10 000000 00000000 00000001)。

x=((unsigned int)x>>n|x<<(32-n));

注:

  • 移位时,必须限定x为无符号类型。
  • 记住循环右移的算法。

翻转指定位

任务描述
本关任务:程序填空,写一个表达式,将int整数x从第p位开始的向右n位(p从右至左编号为0~31)翻转(即1变为0,0变为1),其余各位保持不变。例如:
输入x的值:1234(对应二进制为00000000 00000000 00000100 11010010);
输入p的值: 15 ,输入n的值: 8 ,
表达式完成计算后,x的值改变成:64466 (对应二进制为00000000 00000000 11111011 11010010)。

x=((unsigned int)((~0)<<32-n)>>31-p)^x;
//左侧在造逻辑尺,需要翻转的位,逻辑尺为1,其余为0

注:

  • 取反:和数字1取异或。
  • 本题的思路是,逻辑尺把中间弄成1,再和x异或。

交换高四位和第四位

任务描述
本关任务:编写完整的程序,从终端输入一个无符号短整数k,输出将k的高4位和低4位交换后的结果。例如,程序执行时:
输入:54321(对应二进制为11010100 00110001);
输出:5181 (对应二进制为00010100 00111101)。

#include <stdio.h>

int main(){
	/**********Begin**********/
	unsigned short k,x;
    scanf("%d",&k);
    x=k&0x0ff0|k<<12|k>>12;
    printf("%d",x);
	/**********End**********/
	return 0;
}

注:

  • 此题不建议对x使用左移和右移来设计逻辑尺,因为可能涉及算术右移的保留符号位,从而引发错误。
  • 推荐直接设计逻辑尺:0x0ff0。

存储年月日

写一个表达式,将表示21世纪日期的日day、月month和年year三个整数压缩存储在1个短整型数x中,存储格式略

x = day<<11 | month<<7 | year;

字段结构


宏定义

宏定义和移位

任务描述:
定义宏setbits(x,p,n,y),取出整数y的最右边n位,将其作为整数x从第p位开始的向右n位(p从右至左编号为0~15),x的其余位保持不变。

#define F(x,p,n) ((unsigned short)((x)<<(16-(n)))>>(15-(p)))
#define setbits(x,p,n,y) ((x)&~F(0xffff,p,n)|F(y,p,n))

注:

  • 注意unsigned short强制类型转换,否则可能带来算术右移复制第一位的后果。
  • 相同类型的操作,可以再定义一个宏,而且这样做就不用把数字再移动到中间了,直接在最右边就做完了。

用宏定义实现不同的返回值

定义一个宏RND8(x),返回一个大于等于x的最接近8的倍数值。例如,RND8(5)的值为8,RND8(12)的值为16.

#define RND8(x)( ( (x)+7 ) /8) *8)

enum枚举

格式: enum 枚举名 {枚举元素1,枚举元素2…}
每一个元素都可以赋初始值,其后元素的值+1
好处:在有多个元素时,比#define简洁

enum sex{male female}c1,c2;
//c1值为1,c2值为2

条件编译

#include<stdio.h>
#define CHANGE 1
//#define CHANGE 0
int main(void)
{
    char ch;
    #if CHANGE
    while( (ch=getchar() )!=EOF)
    {
        {
            if('a'<=ch && ch<='z')
                putchar(ch-32);
            else if('A'<=ch && ch<='Z')
                putchar(ch+32);
            else putchar(ch);
        }
    }
    
    #else
    while( (ch=getchar() )!=EOF)
    {
        putchar(ch);
    }
    #endif
}

assert断言

个人认为,assert有点像if。
在这个例子里,认为用if比较好。

#include <stdio.h>
#include <assert.h>
/*要引入assert.h库*/
#define R
int integer_fraction(float x);
int main(void)
{
    float r,s;
    int s_integer=0;
    scanf("%f",&r);
    #ifdef R
        s=3.14159*r*r;
        s_integer=integer_fraction(s);
        assert((s-s_integer)>0.5);
        /*
        如果取整后小数在5~9之间,继续编译,在整数的基础上再加一,即“五入”。
        但是小数是0~4的时候,无法进行编译,是个问题。
        */
        printf("The integer fraction of area is %d\n",s_integer+1);
    #endif
    return 0;
}
int integer_fraction(float x)
{
    int i=x;
    return i;
}

字符串

将一段文字存入字符数组

在这里插入图片描述
在每一句话的最后,先\n,再\

如果只有\n,下边的字不会变蓝;如果只有\,输出的格式和以前不一样

产生26个大写英文字母的字符串

#include <stdio.h>
void main(void)
{
	char Capital[27];
	int i;
	for(i=0;i<26;i++)
	{
		Capital[i]='A'+i;
	}
	Capital[26]='\0';
	/*必须在末尾加'\0'来构造字符串*/
	puts(Capital);
	/*puts+数组名即可打印*/
 } 

连接字符串(循环)

char *mystrcat(char t[],char s[])
/*星号强调了返回值为指针*/
{
	int j=0,k=0;
	while(t[j++]!='\0') ;
	j--;
	while((t[j++])=s[k++]) ;
	return t;
}
void main(void)
{
	char s1[80]="I like ",s2[]="the C programming";
	mystrcat(s1,s2);
	printf("%s\n",s1);
}

删除指定字符

void strdelc(char s[],char c)
{
	int j,k;
	for(j=k=0;s[i]!='\0';j++)
		if(s[j]!=c) s[k++]=s[j];
		/*循环结束后,在串尾加上'\0'*/
		s[k]='\0';
}

去掉重复字符

输入:
12eerer
输出:
12er

#include<stdio.h>
#include<string.h>

void RemoveDuplicate(char *s);

int main()
{
    char str[200];
    // printf("Input strings, end of Ctrl+z\n");
    while (fgets(str, 200, stdin) != NULL)
    {
        RemoveDuplicate(str);
        printf("%s", str);
    }
    return 0;
}

void RemoveDuplicate(char *s)
{
    int r, w, i, len;
    len = strlen(s);
    for (r = w = 0; r < len; r++) 
    /*循环到字符数组末尾*/
    {
        if (s[r]) 
        {
            s[w++] = s[r];
            for (i = r + 1; i < len; i++)
            {
                if (s[r] == s[i]) s[i] = '\0';
                //赋值'\0',它的值为0,下一次就不会进入if
            }
        }
    }
    s[w] = 0;
}

去掉重复字符(空间换时间)

#include <stdio.h>
#include <string.h>

void RemoveDuplicate(char *str);

int main(void) 
{
    char str[200];
    // printf("Input strings, end of Ctrl+Z\n");
    while (fgets(str, 200, stdin)!=NULL) 
    {
        RemoveDuplicate(str);
        printf("%s", str);
    }
    return 0;
}

void RemoveDuplicate(char *str) 
{
    unsigned char *rt, *lt, visited[256] = { 0 };
    for (lt = rt = str; *rt; ++rt) 
    //lt = rt = str 是关键,所以下面的操作对str数组是产生影响的:
    {
        if (!visited[*rt]) visited[*lt++ = *rt] = 1;
        /*比如第一个字符为0,第二个字符为1,第三个字符为1
        *rt='0'=48,那么visited['0']=1,同时*lt指向下一个元素'1'
        由for循环,*rt指向下一个元素'1'
        *rt='1'=49,那么visited['1']=1,同时*lt指向下一个元素'1'
        由for循环,*rt指向下一个元素'1'
        *rt='1'=49,此时不进入if(已visited过),所以此时*rt内重复的元素就不会给*lt,
        在visited中,lt在前面,说明是由它来改变原数组
        所以可以去掉重复字符
        */
    }
    *lt = 0;
    /*字符数组遇到0结束*/
}

复制字符数组

编写函数strnCpy(t,s,n)。
它将字符数组s中的前n个字符复制到字符数组t中,并形成字符串。

#include"Step4_stu.h"
#define SIZE 100

int main()
{
	int n;
	char arr[SIZE];
	char out[SIZE];
	scanf("%[^\n]",&arr);
	scanf("%d",&n);
	strnCpy(out,arr,n);
	printf("%s",out);
	return 0;
}

void strnCpy(char t[],char s[],int n){
    char *pt = t, *ps = s;
    for (int i = 0; i < n && *ps; ++i) {
        *pt++ = *ps++;
    }
    *pt = 0;
}


回文判断

本关任务:编写一个测试一个串是否为回文的递归函数,是回文,函数返回1;不是回文,返回0。并且在主函数中调用该函数,判断输入的字符串是否为回文串。
回文是正读和反读都一样的串,例如:“abcba” 和“otto”就是回文。主函数中若输入的字符串是回文,则输出“Yes”,否则输出“No”。

#include<stdio.h>
#define SIZE 100

int isPalindrome(char arr[],int len);

int main()
{
    char str[SIZE];
    scanf("%s", str);
    printf(isPalindrome(str, strlen(str)) ? "Yes" : "No");
	return 0;
}
int isPalindrome(char arr[],int len)
{
    char *l = arr, *r = arr + len - 1;
    //一个指针指向字符串开头,一个指向字符串末尾
    while (l < r) if (*l++ != *r--) return 0;
	//直到头指针和尾指针相遇,仍然满足回文
    return 1;
}

递归

递归与静态局部变量

#include<stdio.h>
double mulx(double x,int n);
long fac(int n);

double sum(double x,int n)
/*sum函数是最后输出的部分*/ 
{
	int i;
	static double z=1.0; 
	for(i=1;i<=n;i++)
	{
		z=z+mulx(x,i)/fac(i);
	}
	return z; 
}

double mulx(double x,int n)
/*求x的n次方*/
{
	int i;
	double z=1.0;
	for(i=0;i<n;i++)
	{
		z=z*x;
	}
	return z;
}

long fac(int n)
/*计算阶乘*/ 
{
	static long h=1;
	/*静态局部变量*/
	h *=n;
	return h;
}

int main()
{
	double x;
	int n;
	scanf("%lf%d",&x,&n);
	printf("The result is %lf",sum(x,n));
	return 0;
} 

连接字符串(递归)

#include <stdio.h>
int f1(char *pc)
{
	if(*pc) return 1+f1(pc+1);
	return 0;
}
char *f2(char *pa,char *pb)
//对pa指针继续赋值,让两个字符串的地址连续,从而将两个字符串连在一起
{
	if(*pa = *pb) f2(pa+1,pb+1);
	//注意if内是赋值号,不是等号
	return pa;
}
char *f3(char *pa,char *pb)
//将pa指向第一个字符串的末尾
{
	if(*pa) f3(pa+1,pb);
	else f2(pa,pb);
	return pa;
}
int main()
{
	char a[20] = "0123",b[20] = "abc";
	int c=f1(a);
	printf("%d\n",c);
	//if(c%2)	printf("%s",f2(a,b));
	//else	printf("%s",f3(a,b));
	printf("%s",f3(a,b));
	return 0;
}

递归求字符串长度

strlen()的作用是测出字符串的长度。

int strlen(char s[])
{
	if(s[0]=='\0')
		return 0;
	else
		return(1+strlen(s+1));
		/*s+1等价于&s[1],数组名代表首元素的地址*/
}

递归实现字符串逆序

后进先出,有点像栈

void myrever(void)
{
    char c;
    c=getchar();
    if( c != '\n')
    {
        myrever();
        /*第一个字符输入后,先递归myrever(),然后再打印。越先输入的字符就越后打印,
        以此实现逆序的效果。*/
        putchar(c);
    }
}

数组与指针

一维数组作为函数参数的例子

#include <stdio.h>
void fun(int y[],int n);
void main(void)
{
	int i;
	int k,x[5]={1,2,3,4,5};
	fun(x,5);
	for(i=0;i<=4;i++) printf("%d",x[i]);
	fun(&x[2],3); printf("\n");
	for(i=0;i<=4;i++) printf("%d",x[i]);
}
void fun(int y[],int n)
/*需要同时提供数组和数字*/
{
	int i;
	for(i=0;i<n;i++) y[i]++;
}

传递数组,实际传的是数组首元素地址,需要首地址以及元素的个数。(从首地址开始算,n为包括它自己的后面的元素个数)
形参和实参数组1的存储空间在一起,返回数组后形参消失,但是存储数组的空间还在。


十六进制转换为十进制

输入一个十六进制数字串,将其转换成为对应的整数并输出转换结果,遇到非十六进制数字或字符串结束符(’\0’)结束转换。

#include<stdio.h>
#define SIZE 100
void conversion(char str[]){
    int num = 0, tmp;
    for (char *ptr = str; *ptr; ++ptr) {
        if ('0' <= *ptr && *ptr <= '9')
            tmp = *ptr - '0';
        else if ('a' <= *ptr && *ptr <= 'f')
            tmp = *ptr - 'a' + 10;
        else if ('A' <= *ptr && *ptr <= 'F')
            tmp = *ptr - 'A' + 10;
        else
            break;
        num = num * 16 + tmp;
        //十六进制转换为十进制的方法
    }
    printf("%d", num);
}
int main()
{
	char str[SIZE];
	scanf("%s",str);
	conversion(str);
	return 0; 
}

计数器

编程统计输入的一段文字中每个数字字符、 每个英文字符(不区分大小写)和其他字符出现的次数(要求用数组元素作为每个数字字符、每个英文字符、和其他字符出现的次数的计数器)。

测试输入:hust is my home!2019!
预期输出:
0:1
1:1
2:1
9:1
E:1
H:2
I:1
M:2
O:1
S:2
T:1
U:1
Y:1
others:5

#include<stdio.h>
#include<ctype.h>

int main()
{
	/**********  Begin  **********/
	// [0...9]   digit
    // [10...35] alpha
    // [36]      others
    int counter[37] = {0};

    for (int ch; (ch = getchar()) != '\n'; )
        if (isdigit(ch))
            counter[ch - '0']++;
        else if (isalpha(ch))
            counter[toupper(ch) - 'A' + 10]++;
            //把不同类型的字符的计数器放在一个数组内。
            /*
            toupper函数说明:若参数 c为小写字母则将该对应的大写字母返回。
            返回值:返回转换后的大写字母,若不须转换则将参数c值返回。
            在ctype.h头文件中。
            */
        else
            counter[36]++;
	
    for (int ch = '0'; ch <= '9'; ++ch)
        if (counter[ch - '0'])
            printf("%c:%d\n", ch, counter[ch - '0']);
            /*如果某数没有出现,就不输出它的计数器*/
    for (int ch = 'A'; ch <= 'Z'; ++ch)
        if (counter[ch - 'A' + 10])
            printf("%c:%d\n", ch, counter[ch - 'A' + 10]);
    if (counter[36]) printf("others:%d\n", counter[36]);
	/**********  End  **********/
	return 0; 
}

二分段交换

本关任务:输入n个整数到数组u中,再输入正整数k(0< k < n ),k将数组u的n个元素划分为u[0],……,u[k-1]和u[k],……,u[n-1]两段,将两段元素交换位置但仍然存放在数组u中,输出重新排列后的数组元素。

依次把最前面的数放到最后,把后面的数往前移动。

#include<stdio.h>
#define SIZE 100
void move(int arr[],int n,int k){
    int key, j, i;
    for (i = 0; i < k; ++i) {
        key = arr[0];
        //每次把第一个数放到最后
        for (j = 1; j < n; ++j)
            arr[j - 1] = arr[j];
            //把后面的数往前移动
        arr[n - 1] = key;
        //把第一个数放在最后
    }
}
int main()
{
	int arr[SIZE];
    int n,i,k;
	scanf("%d", &n);
	for (i = 0; i < n; i++) {
		scanf("%d", &arr[i]);
	}
	scanf("%d",&k);
    
	move(arr,n,k);   // 交换
    
	for (i = 0; i < n; i++) {
		printf("%d ", arr[i]);
	}
	return 0;
}

高精度计算(分治算法)

分治算法:将规模较大的问题分解为规模较小的问题进行求解。对这些解进行综合并形成原问题的解。
思路:

  • 声明被加数数组x[N],加数数组y[N],和数组z[N+1]
  • 将和数组全部清零
    //待续

函数实现变量的值的交换

必须把指针传进去,这样才能对原来的数产生影响

#include <stdio.h>
void swap(int *px,int *py)
{
	int t;
	t=*px;*px=*py;*py=t;
}

void main(void)
{
	int a=10,b=20,c=30;
	swap(&a,&b);
	swap(&b,&c);
	printf("a=%d,b=%d,c=%d\n",a,b,c);
}

指针和二维数组

用指针逐行输出数组a的所有元素,2层for循环即可

#include <stdio.h>
#define M 3
#define N 2
int main()
{
	int a[M][N]={{1,3},{4,6},{7,9}};
	int *p;
	int i,j;
	p=a[0]; //等价于p=&a[0][0]
	for(i=0;i<M;i++){
		printf("\n");
		for(j=0;j<N;j++){
			printf("%5d",*(p+i*N+j)); //指针间接访问,相当于p[i][j]
		}
	}
}

指针数组

字符指针数组:
(其它指针数组同理)
char *pstr[2]={“123”,“456”};

pstr是一个由2个元素的字符指针数组,
pstr[0]指向字符串"123",pstr[1]指向字符串"456"。

主要应用于描述二维数组。


常量指针

指向的对象是常量。

const int *p;  //可以不初始化
p=&a; //合法,可以修改指针本身的值
*p=10; //非法,不能通过p修改它所指变量a的值
a=8;//合法,因为a不是常量

指针常量

指向固定的对象。
数组名属于指针常量。

int a=5,b=6;
int *const p=&a;//p是常量指针,必须初始化
*p=10;//合法,可以使用p修改所指对象的值
p=&b;//非法,它不能再指向别的变量


指向常量的指针常量

对象固定,值也为常数

int a=5,b=6;
const int *const p=&a;// *p是指向常量的指针常量,必须初始化
*p=10;//非法,不能通过p修改所指变量a的值
p=&b;//非法,指针本身的值也不能修改

动态存储分配

由输出书名的例子引入——“没有冗余”

#define N 3
#include<stdio.h>
#include<stdlib.h>
//在该头文件中给出函数原型 void *malloc(unsigned size);
int main()
{
	int i;
	char *s[N];
	for(i=0;i<N;i++){
		char t[80];
		fgets(t,80,stdin);
		//输入最多80-1个字符并将它们存储在数组t中
		s[i]=(char *)malloc(strlen(t)+1);
		/*
		malloc根据t中字符的个数来确定分配空间的大小,
		此处的强制类型转换是必须的,因为原型void。
		*/
		strcpy(s[i],t);
		//把t中的字符复制到s[i]中
	}
	......
}

二级指针

字符串排序函数----strsort

strcmp:两个字符串自左向右逐个字符进行比较(ASCII值)。
左边>右边,则返回大于0的值,其它同理。

这里是有很多个字符串X1 X2 X3…每个字符串可以看作一个一维数组,很多个字符串就是二维数组,所以使用二级指针(实际上就是字符指针数组,数组中的每个元素都指向一个字符串的首元素),strsort的作用是对这些字符串进行排序。

void strsort(char *s[],int n)
{
	char *temp;
	int i,j;
	for(i=0;i<n-1;i++)
		for(j=0;j<n-i-1;j++)
		/*这里的两个for循环和冒泡排序中的类似*/
			if(strcmp(s[j],s[j+1])>0){
			/*移动指针变量*/
				temp=s[j];
				s[j]=s[j+1];
				s[j+1]=temp;
			}
}

输入N本书名并排序

综合以上两者,得到如下程序:

#define N 3
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int i;
	char *s[N],t[80];
	for(i=0;i<N;i++){
		fgets(t,80,stdin);
		s[i]=(char *)malloc(strlen(t)+1);
		strcpy(s[i],t);
	}
	strsort(s,N);
	/*直接调用strsort来完成排序*/
	for(i=0;i<N;i++) puts(s[i]);
}

指针函数

  • 格式:类型 *函数名(形参)
  • 返回值类型是指针,可以间接实现返回多值的功能。

函数指针

  • 格式:类型 (*标识符)(形参)
    int (*p)(int,int) //p是指向含两个int型参数的函数的int型指针
  • 使用:
    • 初始化:函数指针名=函数名。
    • 通过函数指针来调用它所指的函数。
#include <stdio.h>
void f1(int x)
{	printf("x=%d\n",x);}
int main(void)
{
	void (*pf1)(int x);
	pf1=f1;
	pf1(5); 
	//等价于(*pf1)(5);
	return 0;
}

通用的排序

void sort(int *v,int n,int(*comp)(int,int))
{
	int i,j;
	for(i=1;i<n;i++)
		for(j=0;j<n-i;j++)
		/*对v[j]和v[j+1]按照comp的规则进行比较*/
			if((*comp)(v[j],v[j+1]))
				swap(v+j,v+j+1); 
				//交换,此处没写swap函数
			
}
int asc(int x,int y)
{
	if(x>y) return 1;
	else return 0;
	//此时按照升序来排,还可以改为降序...
}
...

bsearch和qsort

本关任务:现有如表中所示的4个常用的网站网址信息,包括缩略名、全名、url信息。现设计一个结构体struct web,并编写结构体数组获取表中的网址信息,同时将网站信息按照缩略名排序并按照升序输出,最后获取一个缩略名,输出它的URL信息。
要求:将排序定义成函数。

测试输入:
hkd 华中科技大学 http://www.hust.edu.cn
bd 百度搜索网站 http://www.baidu.com
gg 谷歌搜索网站 http://www.google.com
wy 网易邮箱登录 https://mail.163.com
gg
预期输出:
bd 百度搜索网站 http://www.baidu.com
gg 谷歌搜索网站 http://www.google.com
hkd 华中科技大学 http://www.hust.edu.cn
wy 网易邮箱登录 https://mail.163.com
http://www.google.com

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct web_t  /*  设计表示网址的结构  */
{
    /**********  Begin  **********/
    char sname[8];
    char fname[32];
    char url[32];
	/**********   End   **********/
};

#define N  4      /*  网址表大小  */

void sort(struct web_t webs[],int n);
int cmp(const void *a, const void *b);  

int main()
{
    struct web_t ws[N] = {0},  *p;
    char last_abbr[20]; // 最后输入的缩略名
    for (int i = 0; i < N; ++i)
        scanf("%s%s%s", ws[i].sname, ws[i].fname, ws[i].url);

    sort(ws, N);

    for (int i = 0; i < N; ++i)
        printf("%s %s %s\n", ws[i].sname, ws[i].fname, ws[i].url);
    scanf("%s", last_abbr);
    p = bsearch(&last_abbr, ws, N, sizeof(struct web_t), cmp);
    /*
		bsearch详解:
			第一个参数为,指向要查找的元素的指针
				把要查找的元素放在一个数组内,如果要查找的元素是字符串,第一个参数写&数组名
				可以这样理解:如果直接写数组名,是指向第一个元素的指针;如果是&数组名,是指
				向字符数组,即整个字符串的指针
			第二个参数为,要进行二分查找的数组名(指向进行查找的数组的第一个对象的指针)
			第三个参数为,数组中元素的个数
			第四个参数为,数组中每个元素的大小,以字节为单位
			第五个参数为,用来比较两个元素的函数
	*/
    if (p)
        printf("%s\n", p->url);
    else
        printf("未找到搜寻的网址\n");
	return 0; 
}

void sort(struct web_t webs[],int n)
{
    qsort(webs, n, sizeof(struct web_t), cmp);
    /*
		qsort详解:
			第一个参数为,要排序的数组名(指向要排序的数组的第一个元素的指针)
			第二个参数为,数组中元素的个数
			第三个参数为,数组中每个元素的大小,以字节为单位
			第四个参数为,用来比较两个元素的函数
				该函数怎么写,可以参照下方,或者浏览他人CSDN博客
	*/
}

int cmp(const void *a, const void *b)
// 比较函数,比较缩略名,根据不同情况返回值为正数、0、负数
{
    return strcmp(((struct web_t *)a)->sname, ((struct web_t *)b)->sname);
}

多分支函数处理

本关任务:
设计一个字段结构 struct bits, 它将一个 8 位无符号字节从最低位到最高位声明为 8 个字段,依次为 bit0, bit1,…, bit7,且 bit0 优先级最高。同时设计8个函数,第 i 个函数以 biti(i=0, 1, …, 7)为参数,并且在函数体内输出 biti 的值。将 8 个函数的名字存入一个函数指针数组 p_fun。如果 bit0 为 1, 调用 p_fun[0] 指向的函数。如果 struct bits 中有多位为 1,则根据优先级从高到低顺序依次调用函数指针数组 p_fun 中相应元素指向的函数。

#include<stdio.h>
// 定义字段结构
struct bits{
    unsigned int a : 1;
    unsigned int b : 1;
    unsigned int c : 1;
    unsigned int d : 1;
    unsigned int e : 1;
    unsigned int f : 1;
    unsigned int g : 1;
    unsigned int h : 1;
};

// 联合,使数和字段的存储地址相同
union byte{
    unsigned char n;
    struct bits bit;
}w8;

// 定义8个函数,当char型数的某一位为1时,调用函数
void fun0(unsigned int x) {printf("the function %d is called!\n",x);}
void fun1(unsigned int x) {printf("the function %d is called!\n",x);}
void fun2(unsigned int x) {printf("the function %d is called!\n",x);}
void fun3(unsigned int x) {printf("the function %d is called!\n",x);}
void fun4(unsigned int x) {printf("the function %d is called!\n",x);}
void fun5(unsigned int x) {printf("the function %d is called!\n",x);}
void fun6(unsigned int x) {printf("the function %d is called!\n",x);}
void fun7(unsigned int x) {printf("the function %d is called!\n",x);}

// 创建函数指针数组
void (*p_fun[])(unsigned int x) = {fun0,fun1,fun2,fun3,fun4,fun5,fun6,fun7};

int main()
{
    int n;
    scanf("%d",&n);
    union byte w8={n};
    if(w8.bit.a) p_fun[0](0);
    if(w8.bit.b) p_fun[1](1);
    if(w8.bit.c) p_fun[2](2);
    if(w8.bit.d) p_fun[3](3);
    if(w8.bit.e) p_fun[4](4);
    if(w8.bit.f) p_fun[5](5);
    if(w8.bit.g) p_fun[6](6);
    if(w8.bit.h) p_fun[7](7);
}

函数指针完成四则运算

这种写法可以为逆波兰表达式的main函数部分带来启发

#include <stdio.h>
typedef int (*F)(int,int);
typedef struct funs{
	F fun;		//函数指针
	char op;	//运算符
}funs;

int add(int x,int y){
	return x+y;
}
int sub(int x,int y){
	return x-y;
}
int mul(int x,int y){
	return x*y;
}
int div(int x,int y){
	return x/y;
}
void result(funs fun,int x,int y){
	printf("%d%c%d=%d",x,fun.op,y,fun.fun(x,y));
}
int main(void){
	funs array[4] = {{add,'+'},{sub,'-'},{mul,'*'},{div,'/'}};
	scanf("%d %d %d",&a,&b,&c);
	result(array[a],b,c);
	//result(array[2],6,3);
	return 0;
}

指针表示二维数组

二维数组的数组名,是第0行的地址。
即:二维数组的数组名,是指向一维数组的指针。

int a[3][2]={
				{1,3},
				{4,6},
				{7,9}
			}
(*a)表示一个一维数组,即二维数组的第0(*a)[1]表示 3
(*(a+1))[1]表示 4

二维数组作函数参数

fun(int row,int col,int (*x)[col])
{
	...
}

帮同学debug

#include <stdio.h>
#include <stdlib.h>
int tomalloc(char *pointer[], const int length);//分配内存 
int interact(char * pointer[], const int length);//收集数据 
int Show2User(char *pointer[], const int length);//再现数据 
int freeall(char *pointer[], const int length);//释放分配空间 
void platform(int Re); //对函数运行结果进行评估 

int main(){
	const int length = 3;
	char *words[length];
	platform(tomalloc(words, length));
	platform(interact(words, length));
	platform(Show2User(words, length));
	platform(freeall(words, length));
	puts("have a nice day!");
	getchar();
	return 0;
}
//声明元素为字符指针的数组,控制整体流程
int tomalloc(char *pointer[], const int length){
	int i;
	for (i = 0; i < length; i++){
		pointer[i] = (char*)malloc(20 * sizeof(char));
		if (pointer[i] == NULL)
			return -1;
	}
	return 1;
}
//分配内存
int interact(char *pointer[], const int length){
	printf("hello,ready to input %d sentences?\n\
            Write now:");
	int s;
	for(s=0;s<length;s++){
		scanf("%s", pointer[s]);
		printf("%d more sentences\n",length-s);
	}
	return 2;
}
//收集数据
int Show2User(char *pointer[], const int length){
	puts("now we display what u have entered:\n");
	int s;
	for(s = 0; s < length; s++)
         puts(pointer[s]);
	puts("finished!!");
	return 3;
}
//再现数据
int freeall(char *pointer[], const int length){
	int i;
	for (i = 0; i < length; i++);
	free(pointer[i]);
	return 4;
}
//释放空间
void platform(int Re){
switch(Re){
	case 1:
		puts("malloc() works successfully");
		break;
	case -1:
		puts("malloc() failed");
		exit(10000);
	case 2:
		puts("interact() works successfully");
		break;
	case 3:
		puts("having displayed well");
		break;
	case 4:
		puts("free() finished");
		break;
	default:
		puts("what's fuck?!");
		exit(0);
}
	return;
}

结构

求两点之间长度

#include <stdio.h>
#include <math.h>
struct point{ // 平面上点的结构类型说明
	int x;
	int y;	//x,y是点的坐标
}
int main(void)
{
	struct point start,end; //声明点结构变量
	double dx,dy,len;
	scanf("%d%d%d%d",&start.x,&start.y,&end.x,&end.y);
	dx=(end.x-start.x)*(end.x-start.x);
	dy=(end.y-start.y)*(end.y-start.y);
	len=sqrt(dx+dy);
	printf("the length is %f\n",len);
	return 0;
}

定义为函数:

#include <stdio.h>
#include <math.h>

struct point
{
	int x;
	int y;
}
double linelen(struct point pt1,struct point pt2)
{
	double dx,dy,len;
	dx=(pt2.x-pt1.x)*(pt2.x-pt1.x);
	dy=(pt2.y-pt1.y)*(pt2.y-pt1.y);
	len=sqrt(dx+dy);
	return len;
}
int main(void)
{
	struct point start,ned;
	double len;
	scanf("%d%d%d%d",&start.x,&start.y,&end.x,&end.y);
	len=linelen(start,end);
	printf("the length is %f\n",len);
	return 0;
}

指向结构的指针

依然是求两点间距离:

#include <stdio.h>
#include <math.h>

double linelen(struct point *p1,struct point *p2)
{
	double dx,dy,len;
	dx=(p2->x-p1->x)*(p2->x-p1->x);
	//p2->x相当于(*p2).x
	dy=(p2->y-p1->)*(p2->y-p1->y);
	len=sqrt(dx+dy);
	return len;
}

int main(void)
{
	struct point start,end;
	double len;
	scanf("%d%d%d%d",&start.x,&start.y,&end.x,&end.y);
	len=linelen(&start,&end);
	printf("the length is %f\n",len);
	return 0;
}

结构类型的变量作为函数参数,占内存大,耗时长,适合较小的结构,并且传值,形参结构不影响实参结构的值。
结构类型的指针作为函数参数时,占内存小,耗时短,适合较大的结构,并且传地址,在函数内部对形参指针的间访操作会影响实参指针所指结构变量的值。(适合要修改实参指针所指的结构变量值的情况)


字段结构

字段结构的声明:

本题用字段如何实现:

引用字段:

无名字段:

无名字段会预留空间,防止以后还有想加的内容。


联合

结构变量是占据各自不同空间的各成员变量的集合,而联合变量是占用同一块内存空间的各成员变量的集合。

只能对联合的第一个成员进行初始化:


分离short的高低字节

union{
	short n;	//存放要进行分离的数据
	char a[2];	//n与数组a占的字节数要相同
}test;
test.n=200;
low=test.a[0];	//低字节数据
high=test.a[1];	//高字节数据
...

访问short的高低字节和二进制位

#include<stdio.h>
#define CHAR_BIT 8
struct w16_bytes{
	unsigned short byte0:8;	//低字节
	unsigned short byte1:8;	//高字节
};
struct w16_bits{
	unsigned short	b0:1,b1:1,b2:1,b3:1,b4:1,b5:1,b6:1,b7:1,b8:1,b9:1,b10:1,b11:1,b12:1,b13:1,b14:1,b15:1;
};

union w16{	//短整型变量、结构成员byte、结构成员bit共享存储
	short i;
	struct w16_bytes byte;
	struct w16_bits bit;
}w;
void main(void)	//大体上演示一下怎么操作字段
{
	w={0};	//i为0;byte和byte1也为0;b到b15都为0
	void bit_print(short);	//函数原型,此处略
	w.bit.b9=1;	//相当于byte1为2
	w.bit.b10=1;//相当于byte1为2+4=6
	w.byte.byte0='b';
	printf("w.i=0x%x\n",w.i);	//按整型解释、输出共享存储的内容
	bit_print(w.i);	//从高到低,逐位输出共享存储的各bit值
}

链表

线性数据结构分为:顺序存储结构(数组)、链式存储结构(链表)

链表的相关操作包括:创建链表,遍历链表,在链表中插入一个结点,删除链表中的一个结点,同类型链表的归并,链表的排序…更详细的内容,请移步至数据结构笔记

建立先进先出链表

过程:把数组内的数提取出来,存在链表内。

算法步骤:

  • 声明头指针,尾指针
    struct s_list *loc_head=NULL,*tail
  • 创建第一个节点
    • 给第一个结点动态分配存储并使头指针指向它
      loc_head=(struct s_list *)malloc(sizeof(struct s_list));
    • 给第一个节点的数据域中成员赋值
      loc_head->data=*p++;
    • 使尾指针也指向第一个结点
      tail=loc_head;
#include <stdio.h>
#include <stdlib.h>
struct create_list_vi
{
	int* next;
	int data;
}
/*create_list_v1:用循环方式建立先进先出链表*headp
将指针p所指的整数依次存放到链表的每个节点*/
void create_list_v1(struct s_list **headp,int *p)
{
	struct s_list *loc_head=NULL,*tail;
	if(p[0]==0); /*相当于*p==0 */
	else{
		loc_head=(struct s_list *)malloc(sizeof(struct s_list));
		loc_head->data=*p++;
		/*把数组内的数提取出来,存在链表内*/
		tail=loc_head;
		while(*p){	/*tail所指结点的指针域指向动态创建的结点*/
			tail->next=(struct s_list *)malloc(sizeof(struct s_list));
			tail=tail->next;	/*tail指向新创建的结点*/
			tail->data=*p++;	/*向新创建的结点的数据域赋值*/
		}
		tail->next=NULL; /*对指针域赋NULL值*/
	}
	*headp=loc_head;	/*使头指针headp指向新创建的链表*/
}

void main(void)
{
	struct s_list *head=NULL,*p;
	int s[]={1,2,3,4,5,6,7,8,0}; /*0为结束标记*/
	create_list_vi(&head,s); /*创建新链表*/
	print_list(head); /*输出链表结点中的数据*/
}

文件

读操作

本关任务:编写程序,设置指令type_c与type_c /p来获取.c文件中的内容,获取type_c+文件名指令时,显示文件中的C语言程序,并为每一行开头加上行号(行号从1开始标注);获取type_c /p+文件名指令时,为每一行开头加上行号的同时,每10行显示一屏,再次获取字母q,则显示下一屏。(为简化程序,本次测试使用测试文件超过10行,且不用考虑文件格式错误)

文件路径为:src/step1_1/test1.c。

原题要求用命令行实现,此处这种方法可以实现类似的功能,值得学习

注意读取指令时的strcmp,读取文件并输出文件内容的方法。

#include<stdio.h>
#include<string.h>

int main()
{
    char buf[128];
    int flag = 0, cnt = 0;
    FILE * file;

    if (scanf("%s", buf) && strcmp(buf, "type_c") != 0)
    {
        printf("指令错误\n");
        return 0;
    }

    if (scanf("%s", buf) && strcmp(buf, "/p") == 0)
        flag = 1;

    file = fopen("src/step1_1/test1.c", "r");

    while (fgets(buf, 128, file))
    {
        printf("%d  %s", ++cnt, buf);
        // 加行号的方法
        if (flag && cnt == 10)
        {
            cnt = 0;
            while (scanf("%s", buf) && strcmp(buf, "q"))
                continue;
        }
    }
    fclose(file);
	return 0; 
}

feof

先进行feof的判断,再使用fgetc,会导致到文件末尾时,由于光标后还有EOF,feof会返回0,fgetc再把光标往后移,读到EOF,输出EOF会乱码

feof原理和用法

读操作(命令行)

本关任务:修改2.(1)的程序,在显示文本的过程中对每一行加一个行号,同时,设计一个显示控制参数/p,使得每显示5行(一屏)就暂停,当用户按下任何一个键,例如”q”就继续显示下一屏。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char* argv[])
{
	FILE *fp;
	if(argc<2){
		printf("Arguments error!\n");
		exit(-1);
	}
	if((fp=fopen(argv[1],"r"))==NULL){
		printf("Can't open %s file!\n", argv[1]);
		exit(-1);
	}
    char cmd[1024];
    strcpy(cmd,"/p");
    if(!strcmp(argv[2],cmd)) 
    {
        int x = 4;
        while(!feof(fp))
        {
            char ch[1024];
            int count = 0;
            static int i = 0;
            do{
                fgets(ch,1024,fp);
                if(ch != NULL)
                    printf("%d %s",++i,ch);   
                count++;
            }while(count <5 && !feof(fp));
            while(argv[x] != NULL){
                x++;
            }
        }   
    }    
	fclose(fp); 
	return 0;
}

文件连接

编写相应的函数,将三个文件进行复制拼接。

#include<stdio.h>
/*
  完成函数,连接filename1、filename2 和 filename3 三个文件的内容到 filename 文件中
  先连接file1,再连接file2,最后连接file3
*/
void connect(char *filename,char *filename1,char *filename2,char *filename3)
{
    FILE * in, *out = fopen(filename, "w");
    char buf[256];
    const char * infiles[3] = { filename1, filename2, filename3 };
   
    for (int i = 0; i < 3; ++i)
    {
        in = fopen(infiles[i], "r");
        while (fgets(buf, 256, in))
        //注意此处“先读后打印”的操作
        {
            fputs(buf, out);
        }
        //fputc('\n', out);
        fclose(in);
    }

    fclose(out);   
}

多文件系统

一个简单多文件系统列举如下:
file1.c 写main函数,在这里定义变量。
file2.c 写其它函数,定义局部变量或者用extern引入file1.c中定义过的变量。
file.h 写头文件。

要在file1.c和file2.c中写include “file.h”

//file1.c
#include "file.h"
int x,y;
char ch;
int main(void)
{
    x=10;
    y=20;
    ch=getchar();
    printf("in file1 x=%d,y=%d,ch is %c\n",x,y,ch);
	func1();
	return 0;
}
//file2.c
#include "file.h"
void func1(void)
{
  //extern x,y,ch;
    extern int x,y;
    extern char ch;
  /*以上两种写法都可以*/
	x++;
	y++;
	ch++;
	printf("in file2 x=%d,y=%d,ch is %c\n",x,y,ch);
}
//file.h
#include<stdio.h>


排序与查找

选择排序

在这里插入图片描述
(注意加上输入和输出框)

#include<stdio.h>
#define SIZE 100
void inArray(int arr[],int n);
void selectSort(int arr[],int n);
void outArray(int arr[],int n);

void inArray(int arr[],int n)
{   
    int num;
    for(int i=0;i<n;i++)
    {
        scanf("%d",&num);
        arr[i]=num;
    }
}

void selectSort(int arr[],int n)
{
    for(int i=0;i<n-1;i++)
    {
        int j,k,temp,min;
        for(j=i,k=j+1;k<n; )
        {
            if(arr[j]<=arr[k])
            {
                min=j;
                k++;
            }

            else
            {
                min=k;
                j=k;
                k++;
            }
        }
        temp=arr[i];
        arr[i]=arr[min];
        arr[min]=temp;   
    }
}
void outArray(int arr[],int n)
{
    for(int i=0;i<n;i++)
    {
        printf("%d ",arr[i]);
    }
}
int main()
{
	int arr[SIZE],n,i;
	scanf("%d", &n);   // 输入元素个数
	
    inArray(arr,n);  // 输入数组的n个元素
    
	selectSort(arr,n);  //  对数组元素排序
    
    outArray(arr,n);   // 输出数组的n个元素
	return 0; 
}

冒泡排序

在这里插入图片描述
输出框和结束要分开!

#include<stdio.h>
#define N 10
void BubbleSort(int a[],int n)
{
	int i,j,t;
	for(i=1;i<n;i++)/*共进行n-1轮冒泡*/ 
		for(j=0;j<n-i;j++)/*对两两相邻的元素进行比较*/ 
			if(a[j]>a[j+1])
			{
				t=a[j];
				a[j]=a[j+1];
				a[j+1]=t;
				/*交换两个数*/
			}
} 
int main()
{
	int x[N],i;
	printf("please input %d numbers:\n",N);
	for(i=0;i<N;i++)	scanf("%d",&x[i]);
	BubbleSort(x,N);
	printf("the sorted numbers:\n");
	for(i=0;i<N;i++) printf("%d",x[i]);
	return 0;
}

二分查找

目的:找到某个值为x的元素在数组内的序号。
方法:将已排好序的n个元素(存放于数组a)分成两半,取a[n/2]与x比较。如果x=a[n/2],算法结束;如果x<a[n/2],则在数组a的前半部分继续找x;如果x>a[n/2],则在数组a的后半部分继续查找x。

int BinarySearch(int a[],int x,int n)
{
	int front=0,back=n-1,middle;
	while(front<=back)
	{
		middle=(front+back)/2;/*计算中间单元的下标*/
		if(x<a[middle])
			back=middle-1;/*查找单元变成原来的前半部*/ 
		else if(x>a[middle])
			front=middle+1;/*在后半部查找*/ 
		else return(middle)	/*找到,返回下标*/ 
	}
}
	return -1;/*没有找到,返回-1*/ 

其它算法

调用随机数

本关任务(习题5.1):模拟掷双骰子游戏,游戏规则:每轮投两次骰子,取两次的和,第一轮若和为7或11则获胜,游戏结束。
若和为其他数字,则将第一轮两次骰子点数的和(假定为8)作为自己的点数,继续第二轮,第三轮,……,直到某轮两次骰子点数的和等于该点数(第一轮两次投掷点数和)则获胜,若中间某轮两次投掷骰子点数的和为7,则输掉游戏。

#include<stdlib.h>
#include<stdio.h>

void diceGame(int randSeed)
{   
    int x,y,z,i;
    srand(randSeed);
    x=rand();
    x=x%6+1;
    y=rand();
    y=y%6+1;
    z=x+y;
    if(z==7||z==11) printf("Round 1:  Score:%d  Success!\n",z);
    else if(z==2||z==3||z==12) printf("Round 1:  Score:%d  Failed!\n",z);
    else 
    {   
        printf("Round 1:  Score:%d  Continue!\n",z);
        printf("Next rounds: Score %d:Success, Score 7:Failed, others:Continue\n",z);
        int a,b,c;
        for(i=2;i<100;i++)
        {   a=rand();
            a=a%6+1;
            b=rand();
            b=b%6+1;
            c=a+b;
            if(c!=z&&c!=7) printf("Round %d:  Score:%d  Continue!\n",i,c);
            else if(c==7) 
			{
				printf("Round %d:  Score:7  Failed!\n",i);
				break;	
			}
            else if(c==z) 
			{
				printf("Round %d:  Score:%d  Success!");
				break;	
			}
        }

    }
}
int main()
{
	int randSeed;
    scanf("%d",&randSeed);        //输入整数作为随机数种子
    diceGame(randSeed);
    return 0;
} 

简单来说,伪随机数的生成是需要一个种子来参考的,如果种子不变,则产升的随机数序列将是固定的,因此如果需要生成不同的种子,这是利用time的原因,但是educoder上的做法是在主函数中直接输入种子

即:srand(time(NULL))是从系统时间内随机得到一个随机数种子,这只是得到种子的一种方法。种子也可以直接输入:
scanf("%d",seed);
srand(seed);
rand()


最大公约数和最小公倍数

辗转相除法:

#include<stdio.h>
int main()
{   
    int a,b,c,d,e,m,n;
/*m是最大公因数,n是最小公倍数*/
    scanf("%d %d",&a,&b);
    if(a>b)
    {   
        d=a;
        e=b;
        c=a%b;
        while(c!=0)
        {
            a=b;
            b=c;
            c=a%b;
        }
        m=b;
    }
    if(a==b)
    {
        d=a;
        e=b;
        m=a;
        n=a;
    }
    if(a<b)
    { 
        d=a;
        e=b;
        c=b%a;
        while(c!=0)
        {
            b=a;
            a=c;
            c=b%a;
        }
        m=a;
    }
    n=d*e/m;
    //最小公倍数,等于两数相乘除以最大公约数
    printf("%d %d",m,n);
    return 0;
}

更相减损法:

#include<stdio.h>
int main()
{
	int m,n,k,p,i,d;
	printf("Input m,n\n");
	scanf("%d%d",&m,&n);
	if(m<n)
	{
		int t;
		t=m;
		m=n;
		n=t;
	}
	k=0;
	while(m%2==0&&n%2==0)
	{
		m/=2;
		n/=2;
		k++;
	}
	for(p=1,i=0;i<k;i++) p*=2;
	while((d=m-n)!=n)
	{
		if(d>n) m=d;
		else
		{
			m=n,n=d;
		}
	}
	d*=p;
	printf("the greatest common divisor:%d",d);
	return 0;
} 

求素数

数组:

int prime[100];
int k=0,count=0,x,y;
for(x=2;x<100;x++)
{
	for(y=2;y<100;y++)
	{
		if(x%y==0) count++;
	}
	if(count==1)
		{
			prime[k]=x;
			k++;
		} 
	count=0;
}
/*求出一个范围内所有素数,将它们存在数组内 */

斐波拉契数列

#include<stdio.h>
int main()
{
    int i,y,n,k;
    int x[100];
    scanf("%d",&k);
    /*定义斐波拉契*/
    for(i=0,x[0]=1,x[1]=1;i<k;i++)
    {   
        x[i+2]=x[i]+x[i+1];
        printf("%10d",x[i]);
        y=(i+1)%8;
        /*控制每行输出8个数*/
        if(y==0) printf("\n");
    }
    return 0;
}

递归算法实现斐波拉契:使用静态数组,提高效率。

long Fibonacci(long n)
{
	static long F[1000];
	if(F[n])
	{
		return F[n];
		/*如果F[n]已经算过了,就直接返回值,被静态数组记忆*/
	}
	if(n==1||n==2)
	{
		return (F[n]=1);
	}
	return(F[n] = (Fibonacci(n-1)+Fibonacci(n-2)));
}

计算数组元素的个数

sizeof a/sizeof a[0]

其它应用题

哥德巴赫猜想

本关任务:设计一个函数对其形参验证歌德巴赫猜想,并以“n=n1+n2”的形式输出结果。
哥德巴赫猜想:一个大于等于4的偶数可以表示成两个素数之和
测试输入:6
预期输出:6=3+3
输入的偶数范围为:4<=n<=1000000
如果有多对满足要求的素数,打印差值绝对值最大的一对素数

测试输入:8
预期输出:8=3+5

#include<stdio.h>
void goldbach(int n)
//请完成goldbach函数,将大于等于4的偶数n表示成两个素数的和。
{
    for (int i = 1; i <= n / 2; i++)
    {
        int p, q;
        int a, b;
        /*
        i是等号右边的数,
        需要两个数,
        现要让它们为素数。
        */
        for (a = 2; a < i; a++)
        {
            if ((i % a) == 0)
                break;
        }
        if (a == i) p = 1;
        /*一直循环到最后,那么符合素数*/
        else p = 0;
        for (b = 2; b < n - i; b++)
        {
            if (((n - i) % b) == 0)
                break;
        }
        if (b == n - i) q = 1;
        else q = 0;
        if (p && q)
        {
            printf("%d=%d+%d", n, i, n - i);
            /*很巧妙地打印差值绝对值最大数,并用break跳出循环,只输出一组数*/
            break;
        }
    }
}


筛法求素数——孪生素数

#include<stdio.h>
int main()
{
	static  long long i,j,k,limit,a[10000000],b[10000000];
	/*b[100]存放素数*/
	scanf("%lld",&limit);
	for(a[0]=a[1]=0,i=2;i<limit;i++)
	/*假定都是素数,如果下标为1,标记为素数*/ 
		a[i]=1;
	for(i=2;i<(limit);i++)
		if(a[i])
		{
			for(j=i*i;j<limit;j+=i)
			/*从2开始,筛掉某个素数的倍数*/ 
				a[j]=0;
		}
	for(k=0,i=2,j=0;i<limit;i++)
	{
		if(a[i])
		{
			b[k]=i;
			k++;
		}
	}
		for(k=0;k<limit;k++)1·
		{
			if(b[k+1]-b[k]==2)
				printf("(%lld,%lld) ",b[k],b[k+1]);
		}
		return 0;
} 

用这种方法,可以输入limit值的最大值为1kw。
(注意用long long时要用%lld)

约瑟夫问题

本关任务:完善实验指导书P142的求解约瑟夫问题的源程序:M个人围成一圈,从第一个人开始一次兄1至N报数,每当报数为N时报数人出圈,直到圈中只剩下一个人为止。
本任务改为键盘输入 M 和 N 。

#include<stdio.h>

int main(void)
{
    int M, N;
    int a[100], b[100];
    /*数组a存放圈中人的编号,数组b中存放出圈人的编号*/
    int i, j, k;

    scanf("%d%d", &M, &N);
    for (i = 0; i < M; ++i)
        a[i] = i + 1;
    /*对圈中人按从1至N顺序编号*/
    for (i = M, j = 0; i > 1; --i) 
    /*i表示圈中人的个数,还有1人时结束循环*/
    {
        for (k = 1; k <= N; ++k)
        /*1到N报数*/
            if (++j > i - 1) j = 0;
            /*最后一个人报数后第一个人接着报,形成一个圈*/
            /*这是数组越界的处理*/ 
            /*j可以指示现在是第几个人(不是一开始的编号)在报数*/

        b[M - i] = j ? a[j - 1] : a[i - 1];
        /*
        三目运算符的规则:先判断j是否等于0,
        如果不等于0,将a[j-1]赋值给b[M-i],
        如果等于0,将a[i-1]赋值给b[M-i]。
        */
        /*
        造成这一句不理解的本质原因,应该在于++j 
        想要弄懂这条语句,我们可以举例子:
        比如,此时还剩3个人,N=3。
        第一个人报数 → j=1 
        第二个人报数 → j=2 
        第三个人报数,然后出圈 → j=3 → j=0
        此时j=0,出圈的是a[i-1]=a[2]=3,3号出圈,正确

        j=0的时候,其实是最后一个人出圈。

        另一种情况:
        如果还剩3个人,N=4。
        ...第三个人报数 → j=3 → j=0
        第一个人报数,然后出圈 → j=1
        出圈的是a[j-1]=a[0]=1,1号出圈,也正确
        */
        if (j) 
        for (k = --j; k < i; ++k)
        /*压缩数组a,使报数为N的人出圈*/
        /*感觉就是:这个人后边的人往前“压”*/
            a[k] = a[k + 1];
        /*
        此处依然通过举例来理解:
        如果j不为0:
        接上例,第一个人出圈,j=1,k=0,i=3。
        循环:a[0]=a[1],a[1]=a[2]
        即现在只剩下2号和3号了,正确

        如果j为0:说明是最后一个人出圈
        不需要压缩了,因为i本身也自减,i减了以后,就和最后一个人没什么关系了。
        */
    }
    // for (i = 0; i < M - 1; ++i)
    //     printf("%d", b[i]);
    printf("%d\n", a[0]);
    return 0;
}
  • 30
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
1. 顺序存储结构中数据中数据元素之间逻辑关系是由( )表示的,链接存储结构中的数据元素之间的逻辑关系是由( )表示的。 A.线性结构 B.非线性结构 C.存储位置 D.指针 2. 线性表是( )。 A.一个有限序列,可以为空 B. 一个有限序列,不能为空 C. 一个无限序列,可以为空 D. 一个无限序列,不能为空 3. 已知一维数组A采用顺序存储结构,每个元素占用4个存储单元,第9个元素的地址为144,则第一个元素的地址是( )。 A. 108 B. 180 C. 176 D. 112 4. 在单链表中删除指针p所指结点的后继结点,则执行( )。 A. p->next= p->next->next B. p->next= p->next C. p= p->next->next D. p= p->next; p->next= p->next->next 5. 若某链表最常用的操作是在最后一个结点之后插入一个结点删除最后一个结点,则采用( )存储方式最节省时间。 A. 单链表 B. 双链表 C. 带头结点的双循环链表 D. 单循环链表 6.二维数组A[7][8]以列序为主序的存储, 计算数组元素A[5][3] 的一维存储空间下标 k=( )。 A. 38 B. 43 C. 26 D. 29 二、完成下列填空题(每空3分,共9分)。 1.在顺序表L中第i个位置上插入一个新的元素e: Status ListInsert_Sq(SqList &L , int i , ET e){ if ( iL.length+1) return ERROR; if(L.length >= L.listsize){ p=(ET*)realloc(L.elem,(L.listsize+10)*sizeof(ET)); if (p==NULL) exit(OVERFLOW); L.elem=p; } for( j=L.length ; j>=i ; --j ) L.elem[j]=L.elem[j-1] ; L.elem[j]=e ; ++L.length ; return OK; } 2. 删除双向链表中p所指向的节点算法: status delete(DuLinkList L, DuLinkList p) { if (p= =L) return ERROR; else { p->prior->next=p->next; p->next->prior=p->prior ; } free(p); return OK; } 三、编程题(共27分)。 1. (共12分)用顺序表表示集合,设计算法实现集合的求差集运算,要求不另外开辟空间。 顺序表的存储结构定义如下: #define Maxsize 100 typedef struct { ElemType data[MaxSize]; // ElemType表示不确定的数据类型 int length; // length表示线性表的长度 }SqList; 将如下函数,伪码补充完整(8分),代码前先用文字描述自己的算法思想(4分)。 文字描述算法:略(4分) void Difference(SqList A, SqList B) {//参考代码如下如下(8分) for (i=0;i<A.length;i++) for(j=0;j<B.length;j++) if(A.data[i]==B.data[j]) { A.data[i]=’#’; break; } for (k=0,i=0;inext == L) return; p = L; while (p->next != L)   { if (p->next->data != e) P = p->next; else { q = p->next;p->next = q->next; free(q);} } } 时间复杂度分析:(2分) 时间复杂度为O(n)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值