C语言零基础入门

一、输入输出

(1)scanf

scanf 是C语言中的一个标准库函数,用于从标准输入(通常是键盘)读取数据。scanf 函数定义在 <stdio.h> 头文件中。

#include <stdio.h>

int main(void) {
	//读取整数 
    int num;
    printf("Enter an integer: ");
    scanf("%d", &num);  // 读取一个整数
    printf("You entered: %d\n", num);
    
    //读取浮点数 
    float num2;
    printf("Enter a floating-point number: ");
    scanf("%f", &num2);  // 读取一个浮点数
    printf("You entered: %f\n", num2);
    
	//读取字符    
	char ch;
    printf("Enter a character: ");
    scanf(" %c", &ch);  // 读取一个字符,前面的空格用于忽略空白字符
    printf("You entered: %c\n", ch);

	// 读取字符串
	char str[50];
    printf("Enter a string: ");
    scanf("%s", str);  // 读取一个字符串,直到遇到空白字符
    printf("You entered: %s\n", str);
    
	//  读取多个数据
    int num3;
    float fnum;
    char ch;
    printf("Enter an integer, a floating-point number, and a character: ");
    scanf("%d %f %c", &num3, &fnum, &ch);  // 读取多个数据
    printf("You entered: %d, %f, %c\n", num3, fnum, ch);
    
    char str2[50];
    printf("Enter a multi-word string: ");
    scanf("%[^\n]", str2);  // 读取多单词字符串,直到遇到换行符
    printf("You entered: %s\n", str2);
    
    return 0;
}

(2)getchar

功能:该函数从标准输入读取一个字符,并返回该字符的ASCII码值。

#include <stdio.h>
int main(void) {
	char ch = getchar(); // 定义char 变量 ch,接收 getchar() 函数返回值做为初值。
	printf("ch的数值:%d\n", ch); 
	printf("ch的字符:%c\n", ch);
	return 0;
}

(3)printf

  • printf 函数的第一个参数是一个格式字符串,用于指定如何输出后续的参数。例如%d 是一个格式说明符,用于输出整数。
#include <stdio.h> // 引⼊头⽂件 stdio.h , 因为下⾯使⽤了printf() 必须添加此头⽂件。
#define PI 3.14
int main(void) {
	int a = 10;
	printf("%d\n", a); // %d:格式匹配符,匹配整数。
	printf("a = %d\n", a); // a = 在“”中,代表普通字符串, 原样输出。
	printf("%d\n", 100);
	printf("%f\n", PI); // %f:格式匹配符,匹配小数。
	printf("PI = %f\n", PI); // PI = 在“”中,代表普通字符串, 原样输出。
	printf("%f\n", 3.45678); // %f:格式匹配符,匹配小数。
	int b = 20;
	printf("%d + %d = %d\n", a, b, a+b); // +、= 在 “”中,代表普通字符串, 原样输出。
	printf("%d + %d = %d\n", 3, 7, 3+7);
	return 0; //C程序要求,main 函数要有返回值。借助 return 实现返回。
}

在这里插入图片描述

补充:占位符说明

在这里插入图片描述

(4)putchar

  • putchar 是C语言中的一个标准库函数,用于向标准输出(通常是终端)输出一个字符。

  • 输出字符串:虽然 putchar 一次只能输出一个字符,但可以结合循环来输出整个字符串。

#include <stdio.h>

int main(void) {
    char str[] = "Hello, World!";
    int i;

    for (i = 0; str[i] != '\0'; i++) {
        putchar(str[i]);  // 输出字符串中的每个字符
    }
    putchar('\n');  // 输出换行符

    return 0;
}

二、变量

1、 变量 3 要素

  1. 变量名:用来在程序中使用。
  2. 变量类型:开辟内存空间大小。
  3. 变量值:存储的实际数据。
    在这里插入图片描述

2、变量名命名的主要规则:

  1. 字母数字和下划线 变量名只能包含字母(A-Z, a-z)、数字(0-9)和下划线(_)。变量名不能以数字开头。

  2. 不得使用关键字 变量名不能与C语言的关键字(如 int, char, for, if 等)相同。

3、变量的作用域

变量的作用域决定了变量在哪里可以被访问。主要作用域有:
  1. 局部变量:在函数内部声明的变量,只在该函数内有效。
  2. 全局变量:在所有函数之外声明的变量,在整个文件或多个文件中都可访问。
  3. 形式参数:函数定义中参数列表内的变量,仅在该函数调用期间有效。
  4. 静态局部变量:使用 static关键字声明的局部变量,其生命周期贯穿整个程序运行期,但是作用域仍限制在声明它的函数内。
#include <stdio.h>

// 全局变量
int globalVar = 100;

void func() {
    // 局部变量
    int localVar = 200;
    printf("Inside function: globalVar = %d, localVar = %d\n", globalVar, localVar);
}

int main() {
    func();
    printf("In main: globalVar = %d\n", globalVar);
    // 下面这行会报错,因为 localVar 是 func 函数内的局部变量
    // printf("localVar = %d\n", localVar);
    return 0;
}

在这里插入图片描述

三、常量

  • C语言中,常量是那些在程序执行过程中其值不能被修改的量。

C语言中常量的主要用法:

  1. 使用 #define 预处理器指令
    #define 是一个预处理器指令,用于定义宏常量。宏常量在编译前会被预处理器替换为指定的值。
#include <stdio.h>

#define PI 3.14159
#define MAX 100

int main(void) {
    printf("Value of PI: %f\n", PI);
    printf("Maximum value: %d\n", MAX);
    return 0;
}
  1. 使用 const 关键字
    const 关键字用于声明常量变量。这种常量在内存中分配空间,并且可以在编译时或运行时初始化。
#include <stdio.h>

int main(void) {
    const int MAX = 100;
    const float PI = 3.14159;
    const char GRADE = 'A';

    printf("Maximum value: %d\n", MAX);
    printf("Value of PI: %f\n", PI);
    printf("Grade: %c\n", GRADE);

    // 下面这行代码会导致编译错误,因为 MAX 是常量
    // MAX = 200;

    return 0;
}
  1. 枚举常量

枚举(enum)是一种特殊的整型常量集合,用于定义一组具名的整数值。

说明:枚举类型 Day 包含七个枚举常量:MON, TUE, WED, THU, FRI, SAT,
SUN。默认情况下,这些枚举常量会被赋予从 0 开始的连续整数值。

#include <stdio.h>

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN };

int main(void) {
    enum Day today = WED;
    printf("Today is day number: %d\n", today);//Today is day number: 2

    return 0;
}

练习:

#include <stdio.h>
#define PI 3.1415926 // 定义常量
int main(void) {
// 圆的面积 :s = PI * r * r
// 圆的周长 :L = 2 * PI * r
	int r = 3; // 变量的定义。
	float s = PI * r * r; // 表达式。作为变量值。
	float l = 2 * PI * r;
//printf("圆的面积:%f\n", s);// 28.274334 默认显示 6 位小数。
//printf("圆的周长:%f\n", l);// 18.849556
//printf("圆的面积:%.2f\n", s);// 28.27
//printf("圆的周长:%.2f\n", l);// 18.85指定保留小数点后两位,对第3位四舍五入
	float m = 3.4567891;
	printf("m=%.2f\n", m);
	printf("m=%6.2f\n", m);
//共显示6位数,包含小数点,保留小数点后两位,对第3位四舍五入,不足6位用空格补齐。
	printf("m=%06.2f\n", m);
//共显示6位数,包含小数点,保留小数点后两位,对第3位四舍五入,不足6位用0补齐。
	return 0;
}

四、sizeof

在这里插入图片描述

1、有符号整型
在这里插入图片描述

#include <stdio.h>
#include <limits.h>
int main(void) {
	printf("int大小 = %u\n", sizeof(int));
	printf("int最小值:%d, 最大值:%d\n", INT_MIN, INT_MAX);
	printf("short大小 = %u\n", sizeof(short));
	printf("short最小值:%hd, 最大值:%hd\n", SHRT_MIN, SHRT_MAX);
	printf("long大小 = %u\n", sizeof(long));
	printf("long最小值:%ld, 最大值:%ld\n", LONG_MIN, LONG_MAX);
	printf("long long大小 = %u\n", sizeof(long long));
	printf("long long最小值:%lld, 最大值:%lld\n", LLONG_MIN, LLONG_MAX);
}

在这里插入图片描述
2、无符号整型
在这里插入图片描述

【扩展】:C语言中引入无符号整型有几个重要的原因,这些原因涉及性能、表示范围、内存利用率以及特定应用场景的需求。以扩大表示范围为例子,无符号整型可以表示更大的正数范围。

在这里插入图片描述

五、char

在C语言中,char 类型用于表示字符。char 类型是一个基本的数据类型,通常占用1个字节的内存。以下是关于 char 类型的详细总结:

  1. 基本属性
    在这里插入图片描述

  2. 定义和初始化

#include <stdio.h>
#include <limits.h>
int main(void) {
	char ch1 = 'A';  // 单引号表示单个字符
	char ch2 = 65;   // 使用ASCII码值初始化
	printf("%c",ch2); //结果:A 
	return 0;
}
  1. 字符常量
    在这里插入图片描述

  2. 字符串
    在这里插入图片描述

  3. 输入和输出

#include <stdio.h>

int main(void) {
    char ch;
    printf("Enter a character: ");
    scanf("%c", &ch);  // 读取一个字符
    printf("You entered: %c\n", ch);  // 打印字符

    return 0;
}
  1. ASCII 码
    在这里插入图片描述
    在这里插入图片描述

  2. 字符操作

  • 比较字符:可以使用关系运算符比较字符。
  • 字符运算:可以对字符进行算术运算,实际上是对其ASCII码值进行运算。
#include <stdio.h>

int main(void) {
    char ch1 = 'A';
    char ch2 = 'B';

    if (ch1 < ch2) {
        printf("'%c' comes before '%c'\n", ch1, ch2);//'A' comes before 'B'
    }

    char ch3 = ch1 + 1;  // 'A' + 1 -> 'B'
    printf("ch1 + 1 = %c\n", ch3);//ch1 + 1 = B

    return 0;
}
  1. 字符串处理函数
  • C标准库提供了许多用于字符串处理的函数,例如 strlen、strcpy、strcat、strcmp 等。
#include <stdio.h>
#include <string.h>

int main(void) {
    char str1[20] = "Hello";
    char str2[20] = "World";

    strcpy(str1, str2);  // 复制 str2 到 str1
    strcat(str1, "!");   // 连接字符串
    printf("str1: %s\n", str1);

    int len = strlen(str1);  // 获取字符串长度
    printf("Length of str1: %d\n", len);

    return 0;
}
  1. 无符号字符
  • 如果需要确保 char 类型的值是非负的,可以使用 unsigned char。
#include <stdio.h>

int main(void) {
    unsigned char ch = 255;  // 无符号字符
    printf("Value of ch: %u\n", ch);

    return 0;
}

【 练习1 】:将大写字母,转换成 小写字母

#include <stdio.h>
int main(void) {
	char ch = 'R'; // char 变量定义
	printf("R 转换的小写为:%c\n", ch+32); //ch+32 表达式,对应格式匹配符 %c
	char ch2 = 'h'; // char 变量定义
	printf("h 转换的大写为:%c\n", ch2-32); //ch2-32 表达式,对应格式匹配符 %c
	char ch3 = '5';
// 借助字符5, 利用 ASCII特性,打印出 字符9
	printf(" 打印字符9 = %c\n", ch3+4);
	return 0;
}

在这里插入图片描述
【 练习 2】:在一个printf函数中, 打印输出 hello 换行 world 换行 。

#include <stdio.h>
/*
 方法1
int main(void) {
	printf("hello\nworld\n");
	return 0;
}
*/
//方法2
int main(void) {
	char ch = '\n'; //定义ch变量,初值为'\n'
	printf("hello%cworld%c", ch, ch); // 等价于 printf("hello\nworld\n");
	return 0;
}


六、bool

  • 在C语言中,布尔类型(bool)并不是内置类型,而是通过包含 <stdbool.h> 头文件来引入的。bool 类型用于表示真(true)和假(false)两种逻辑值。

1.输出布尔值

可以使用 %d 格式说明符来输出布尔值。true 输出为 1,false 输出为 0。

#include <stdio.h>
#include <stdbool.h>

int main(void) {
    bool isTrue = true;
    bool isFalse = false;

    printf("isTrue: %d\n", isTrue);  // 输出 1
    printf("isFalse: %d\n", isFalse);  // 输出 0

    return 0;
}

2.布尔值的整数表示
在C语言中,true 实际上表示整数 1,false 表示整数 0。因此,任何非零值都可以被视为 true,零值被视为 false。

#include <stdio.h>
#include <stdbool.h>

int main(void) {
    bool a = 1;  // true
    bool b = 0;  // false
    bool c = 10; // true

    printf("a: %d\n", a);  // 输出 1
    printf("b: %d\n", b);  // 输出 0
    printf("c: %d\n", c);  // 输出 1

    return 0;
}

3.布尔运算
布尔运算符(如 &&、||、!)可以用于布尔表达式的组合和否定。

#include <stdio.h>
#include <stdbool.h>

int main(void) {
    bool a = true;
    bool b = false;

    bool andResult = a && b;  // false
    bool orResult = a || b;   // true
    bool notResult = !a;      // false

    printf("a && b: %d\n", andResult);
    printf("a || b: %d\n", orResult);
    printf("!a: %d\n", notResult);

    return 0;
}

在这里插入图片描述

七、运算符

(1)算数运算符

在这里插入图片描述

(2)自增自减运算符

#include <stdio.h>
// ++ / -- 运算符
int main(void) {
// 前缀
	int a = 10;
	printf("%d\n", ++a); // ++a 等价于 a = a+1;
// 后缀
	int b = 10;
	printf("%d\n", b++); // b++ 等价于 b = b+1;
// 不管前缀、后缀、含有变量 ++/-- 表达式执行完后,变量均发生了自增、自减。
	printf("b = %d\n", b);
	
	return 0;
}

在这里插入图片描述

(3)赋值运算符

在这里插入图片描述

(4)比较运算符

在这里插入图片描述

(5)逻辑运算符

  • 0 为假, 非0 为真。(非0: 1、 27、-9)
  1. 逻辑非(!)
  • 规律:非真为假,非假为真。
#include<stdio.h>
// 逻辑运算符
int main(void) {
// 逻辑非
	int a = 34; // 34 是非0, 默认 a 为真。
	int b = 0;
	printf("a = %d\n", !a); // a为真,非a 为假!!---> 0
	printf("b = %d\n", !b); // b=0, b为假,非b, 为真。 ---> 1
	return 0;
}

  1. 逻辑与(&&)
  • 规律:同真为真,其余为假。
#include<stdio.h>
// 逻辑运算符
int main(void) {
	int a = 34; // 34 是非0, 默认 a 为真。
	int b = 0;
	printf("%d\n", a && !b); // a为真,!b为真, 真&&真 -- 真。 --->1	
}
  1. 逻辑或(||)
  • 规律:有真为真,同假为假。
#include<stdio.h>
// 逻辑运算符
int main(void) {
	int a = 34; // 34 是非0, 默认 a 为真。
	int b = 0;
	printf("------%d\n", a || !b); // a为真,!b为真,真||真 -- 真。---> 1
	printf("------%d\n", a || b); // a为真,b为假,真||假 --真。---> 1
	printf("------%d\n", !a || b); // !a为假,b为假,假||假 --假。---> 0
}

(6)逗号运算符

在这里插入图片描述

(7)三目运算符

在这里插入图片描述


#include<stdio.h>
// 三目运算
int main(void) {
	int a = 40;
	int b = 4;
	//int m = a > b ? 69 : 10;
	//printf("m = %d\n", m); // m = 69;
	//int m = a < b ? 69 : 10;
	//printf("m = %d\n", m); // m = 10;
	
	// 三目运算支持嵌套
	int m = a < b ? 69 : (a<b?3:5); // 先算表达式3,取5,整个三目运算,取表达式3-- >5
	printf("m = %d\n", m); // m = 5;
	int m2 = a < b ? 69 : a < b ? 3 : 5;
	printf("m2 = %d\n", m2); // m = 5;
	return 0;
}

【说明】:如果不使用(),三目运算默认的结合性,自右向左。

(8)运算符的优先级

在这里插入图片描述

八、if 条件判断

(1)if…else 分支

#include <stdio.h>

int main(void) {
	int a;
	printf("请输入一个数:");
	int ret = scanf("%d", &a);
	if (a > 0) {
		printf("a > 0\n");
	} else {
		printf("a <= 0\n");
	}
	return 0;
}

(2)多个分支逻辑

#include <stdio.h>

int main(void) {
	int score;
	printf("请输入学生成绩:");
	scanf("%d", &score);
	if (score >= 90) { // 优秀
		printf("优秀\n");
	} else if (score >=70 && score < 90) { // 70 < score < 90 错误写法。
		printf("良好\n");
	} else if (score >= 60 && score < 70) {
		printf("及格\n");
	} else {
		printf("差劲\n");
	}
}

九、switch 语句

在这里插入图片描述

#include <stdio.h>
int main(void) {
	int score;
	printf("请输入学生成绩:");
	scanf("%d", &score);
	// 将 学生成绩,通过运算,得出可以放入 switch case 分支的 表达式。
	switch (score/10) {
		case 10:
			printf("优秀\n");
			break; // 表示当前分支结束。
		case 9:
			printf("优秀\n");
			break;
		case 8:
			printf("良好\n");
			break;
		case 7:
			printf("良好\n");
			break;
		case 6:
			printf("及格\n");
			break;
		default: // 所有case都不满足的其他情况。
			printf("不及格");
			break;
	}
	return 0;
}

备注:case穿透
在这里插入图片描述

十、while 循环

在这里插入图片描述
在这里插入图片描述

#include<stdio.h>
int main(void) {
	//7的倍数: num % 7 == 0
	//个位含7: num % 10 == 7
	//十位含7: num / 10 == 7
	int num = 1;
	while (num <= 100) {
		if (num % 7 == 0 || num % 10 == 7 || num / 10 == 7) {
			printf("敲桌子!\n");
		} else {
			printf("%d\n", num);
		}
		num++;
	}
	return 0;
}

十一、do while 循环

在这里插入图片描述

练习:
在这里插入图片描述

#include<stdio.h>
int main(void) {
	//-个位数:int a = num % 10;
	//-十位数:int b = num / 10 % 10;
	//-百位数:int c = num / 100;
	int num = 100; // 数数从 100 开始。
	int a, b, c; // 定义存储个位、十位、百位数 的变量。
	do {
		a = num % 10; // 求个位数。
		b = num / 10 % 10;
		c = num / 100;
		// 判断 这个数字是否是“水仙花数”
		if (a * a * a + b * b * b + c * c * c == num){
			printf("水仙花数:%d\n", num);
		}
		num++; // 不断向后数数。
	} while (num <= 999);
	return 0;
}

在这里插入图片描述

十二、for 循环

在这里插入图片描述

  • 使用 for 循环 求 1~100 的 和
#include <stdio.h>

int main(void) {
    int sum = 0;  // 初始化累加和为0

    // 使用 for 循环从1到100求和
    for (int i = 1; i <= 100; i++) {
        sum += i;  // 累加当前的值
    }

    // 输出结果
    printf("The sum of numbers from 1 to 100 is: %d\n", sum);

    return 0;
}
  • 特殊语法:
    -在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

十三、嵌套 for 循环

#include <stdio.h>

int main(void) {
    for (int i = 0; i < 2; i++) { // 外层循环
        for (int j = 0; j < 3; j++) { // 内层循环
            printf("Hello World!\n");
        }
    }
    return 0;
}

分析代码:拆解以上循环嵌套的执行过程

第一次外层循环:
	i = 0; //i的初始值为0
	i < 2  //条件成立,进入内层循环
		第一次进入内层循环:
			【内层循环第一次:】
			j = 0; //j的初始值为0
			j < 3  //条件成立
				printf("Hello World!\n");//此时输出一次 Hello World!
			j++; //j的值变为1
			【内层循环第二次:】
			j = 1; 
			j < 3  //条件成立
				printf("Hello World!\n");//此时输出一次 Hello World!
			j++; //j的值变为2
			【内层循环第三次:】
			j = 2; 
			j < 3  //条件成立
				printf("Hello World!\n"); //此时输出一次 Hello World!
			j++; //j的值变为3
			【内层循环第四次:】
			j = 3; 
			j < 3  //条件不成立,内层循环结束
	i++; //i的值变为1
第二次外层循环:
	i = 1; 
	i < 2  //条件成立,进入内层循环
		第二次进入内层循环:
			【内层循环第一次:】
			j = 0; //j的初始值为0
			j < 3  //条件成立
				printf("Hello World!\n"); //此时输出一次 Hello World!
			j++; //j的值变为1
			【内层循环第二次:】
			j = 1; 
			j < 3  //条件成立
				printf("Hello World!\n"); //此时输出一次 Hello World!
			j++; //j的值变为2
			【内层循环第三次:】
			j = 2; 
			j < 3  //条件成立
				printf("Hello World!\n"); //此时输出一次 Hello World!
			j++; //j的值变为3
			【内层循环第四次:】
			j = 3; 
			j < 3  //条件不成立,内层循环结束
	i++; //i的值变为2
第三次外层循环:
	i = 2; 
	i < 2  //条件不成立,外层循环结束,整个循环都终止了

练习:打印乘法口诀表

#include <stdio.h>

int main(void) {
    // 外层循环控制行数
    for (int i = 1; i <= 9; i++) {
        // 内层循环控制列数
        for (int j = 1; j <= i; j++) {
            // 打印乘法口诀表的一个项
            printf("%d*%d=%d ", j, i, i * j);
        }
        // 每一行结束后换行
        printf("\n");
    }

    return 0;
}

在这里插入图片描述

十四、语句跳转

  • 在流程控制语句中还有一类“跳转语句”,主要用来中断当前的执行过程。

(1)break

  • 当代码中遇到break时,会直接中断距离最近的循环,跳转到外部继续执行。
#include <stdio.h>

int main(void) {
    int i = 1;

    while (1) {  // 使用1表示无限循环
        printf(" 这是第%d次输出\n", i);
        i++;

        if (i > 5) {
            break;  // 当 i 大于 5 时退出循环
        }
    }

    return 0;
}

结果:
在这里插入图片描述

(2)continue

  • continue语句表示“继续”执行循环,也就是中断循环中的本次迭代,并开始执行下一次迭代。
//需求:输出1到10之间所有的整数,5不输出

#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 10; i++) {
        if (i == 5) {
            continue;  // 当 i 等于 5 时跳过本次循环
        } else {
            printf("这是第%d次输出\n", i);
        }
    }

    return 0;
}

结果:
在这里插入图片描述

十五、数组

在这里插入图片描述
在这里插入图片描述

  • 基本特性
    -
  • 数组初始化
    在这里插入图片描述
  • 练习:数组元素逆序
#include <stdio.h>

// 数组元素逆序
int main(void) {
	int arr[] = {1, 6, 8, 0, 4, 3, 9, 2}; // 变为:{2, 9, 3, 4, 0, 8, 6, 1}
// 获取数组的元素个数
	int n = sizeof(arr) / sizeof(arr[0]);
	int i = 0; // 从前向后
	int j = n-1; // 从后向前
	int tmp = 0; // 定义临时变量。
// 交换数组元素之前,打印数组的所有元素。
	for (int i = 0; i < n; i++) {
		printf("%d ", arr[i]);
	}
	putchar('\n');
// 循环交换数组元素。
	while (i < j) {
		tmp = arr[i]; // 三杯水变量交换法
		arr[i] = arr[j];
		arr[j] = tmp;
		i++; // 不断后移
		j--; // 不断前移
	}
// 交换数组元素之后,打印数组的所有元素。
	for (int i = 0; i < n; i++) {
		printf("%d ", arr[i]);
	}
	putchar('\n');
	return 0;
}

1.查找算法

(1)顺序查找

思路:

  • 遍历列表。
  • 找到跟目标值相等的元素,就返回他的下标。
  • 遍历结束后,如果没有搜索到目标值,就返回-1。

参考代码:

#include <stdio.h>

int main(void) {
    int a[5] = {4, 5, 2, 3, 1};
    int target = 1;
    int len = sizeof(a) / sizeof(a[0]);
    bool b = false;

    for (int i = 0; i < len; i++) {
        if (target == a[i]) {
            printf("%d\n", i);
            b = true;
        }
    }

    if (!b) {
        printf("-1\n");
    }

    return 0;
}

时间复杂度:O(n)

(2)二分查找

【注意】:二分查找的前提是数字是排序好的。

思路:

  • 从数组的中间元素开始,如果中间元素正好是目标值,则搜索结束。
  • 如果目标值大于或者小于中间元素,则在大于或小于中间元素的那一半数组中搜索。

动画演示:

在这里插入图片描述

#include <stdio.h>

int main(void) {
    int a[5] = {4, 5, 8, 9, 15};
    int target = 5;
    int len = sizeof(a) / sizeof(a[0]);
    int left = 0;
    int right = len - 1;
    bool b = false;

    while (left <= right) {
        int mid = (left + right) / 2;
        if (a[mid] < target) {
            left = mid + 1;
        } else if (a[mid] > target) {
            right = mid - 1;
        } else {
            printf("%d\n", mid);
            b = true;
            break;
        }
    }

    if (!b) {
        printf("找不到该数字\n");
    }

    return 0;
}

时间复杂度:O(logn)

3. 排序算法

菜鸡三人组:

  • 冒泡排序
  • 选择排序
  • 插入排序

牛逼三人组:

  • 快速排序
  • 堆排序(难)
  • 归并排序(较难)

(1)冒泡排序

思路:

  • 1.比较所有相邻的元素,如果第一个比第二个大,则交换他们。
  • 2.一轮下来,可以保证最后一个数是最大的。
  • 3.以此类推,执行n-1轮,就可以完成排序。
  • 动画演示:

在这里插入图片描述
代码参考:

#include <stdio.h>

int main(void) {
    int a[] = {6, 5, 4, 1, 3, 2};
    int len = sizeof(a) / sizeof(a[0]);

    // 冒泡排序
    for (int i = 0; i < len - 1; i++) {
        for (int j = 0; j < len - 1; j++) {
            if (a[j] > a[j + 1]) {
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }

    // 输出排序后的数组
    for (int i = 0; i < len; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");  // 输出换行符

    return 0;
}
  • 思考:以上代码能否进行优化呢?内层循环需要每次都遍历0~len-1次吗??
  • 回答:不需要,因为随着外层循环次数的增加,数组末尾的排序会依次确定好位置,例如,第一轮会确定6的位置,第二轮会确定5的位置。所以,内层循环不需要每次都遍历0~len-1次,只需要遍历len-1-i 次就够了。

优化后的代码:

#include <iostream>
using namespace std;
int main() {
	int a[] = {6,5,4,1,3,2};
	int len = sizeof(a) / sizeof(a[0]);
	for(int i = 0; i < len-1; i++) {
		for(int j = 0; j < len-1-i; j++) {
			if(a[j] > a[j+1]) {
				int temp = a[j];
				a[j] = a[j+1];
				a[j+1] = temp;
			}
		}
	}
	for (int i = 0; i < len; i++) {
		cout << a[i] << " ";
	}
}

冒泡排序时间复杂度:O(n2)

(2)选择排序

思路:

  • 1.找到数组中的最小值,把他更换到列表中的第一位。(具体做法:先假设第一数为最小值,记录它的索引值,将第一数和第二个数作比较,如果第一个数大于第二个数,将最小索引值记录为第二个数,依次循环比较,一轮比较下来,最小值所在的索引位置就会被找到,并且把他更换到最开头的位置。
  • 2.接着找到第二小的值,把他更换到数组中的第二位。
  • 3.以此类推,执行n-1轮,就可以完成排序。
  • 动画演示:

在这里插入图片描述

参考代码:

#include <stdio.h>

int main(void) {
    int a[] = {6, 5, 4, 1, 3, 2};
    int len = sizeof(a) / sizeof(a[0]);

    // 冒泡排序
    for (int i = 0; i < len - 1; i++) {
        for (int j = 0; j < len - 1 - i; j++) {
            if (a[j] > a[j + 1]) {
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }
    }

    // 输出排序后的数组
    for (int i = 0; i < len; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");  // 输出换行符

    return 0;
}

选择排序时间复杂度:O(n2)

(3)插入排序

插入排序(insertion sort)是一种简单的排序算法,它的工作原理与手动整理一副牌的过程非常相似。

基本思路:

  • 在未排序区间选择一个基准元素,将该元素与其左侧已排序区间的元素逐一比较大小,并将该元素插入到正确的位置。

具体步骤:

  1. 从第二个元素开始,依次将元素插入已经排序好的部分。
  2. 遍历已排序好的部分,找到合适的插入位置。
  3. 将待排序的元素插入合适的位置。
  4. 将大于待排序元素的元素向后移动一个位置。
  5. 依次类推,完成插入排序。

例子:

  • 插入排序的总体过程,类似我们打牌,摸牌后进行依次插入的过程:
    在这里插入图片描述

  • 举例:假如已经进行到31这个数了,31前面的数我们已经插入排序完毕了;那么对于31这个数,我们需要先将其与93比较,31<93,交换位置;接着比较31<77,交换位置;接着比较31<54,交换位置;接着比较31>26,不需要交换位置了,此时内层循环可以结束了。
    在这里插入图片描述

动画演示:

在这里插入图片描述

代码参考:

#include <stdio.h>

int main(void) {
    int a[] = {5, 3, 4, 7, 2};
    int len = sizeof(a) / sizeof(a[0]);

    for (int i = 1; i < len; i++) {
        int key = a[i];
        int j = i - 1;
        while (j >= 0 && key < a[j]) {
            a[j + 1] = a[j];
            j -= 1;
        }
        a[j + 1] = key;

        // 输出当前排序状态
        for (int z = 0; z < len; z++) {
            printf("%d ", a[z]);
        }
        printf("\n");  // 输出换行符
    }

    return 0;
}

插入排序的外层循环为for循环 + 内层循环为while循环,所以时间复杂度为 O(n2)

(4)快速排序

在这里插入图片描述

  • 第一轮排序的代码:
#include <iostream>
#include <vector>
using namespace std; 

// 遍历数组 
void printVector(const vector<int>& vec) {
    for (int num : vec) {
        cout << num << " ";
    }
    cout << endl;
}

// 归位函数 
void partition(vector<int>& vec, int left, int right) {
    int tmp = vec[left];
    while (left < right) {
        while (left < right && vec[right] >= tmp) { // 从右边找出比tmp小的数
            --right;  // 往左移动一步
        }
        vec[left] = vec[right];  // 把右边的值给到左边的空缺位置
        printVector(vec);
        while (left < right && vec[left] <= tmp) {
            ++left;
        }
        vec[right] = vec[left];
        printVector(vec);
    }
    vec[left] = tmp; // 将tmp进行归位
}

int main() {
    vector<int> vec = {5, 7, 4, 6, 3, 1, 2, 9, 8};
    printVector(vec);
    partition(vec, 0, vec.size() - 1);
    printVector(vec);
}

结果:
在这里插入图片描述

问题解析:: 为何是先从右往左进行查找?
答: 因为左边空出了位置, 从右往左找出比tmp小的数字, 可以放到left指针所在的位; 如果一开始从左往右查找, 右边并没有空余位置。

代码解析1: 
while (left < right && vec[right] >= tmp): 以上这行代码为何添加条件判断 left < right
答: 因为如果不添加, right可能会跑到left的左边, 比如5 6 7,right指针最终会移动到5所在位置的左边,vec[right]就取不到元素了

# 代码解析2: 
while (left < right && vec[right] >= tmp): 以上这行代码为何是 vec[right] >= tmp, 不能是vec[right] > tmp:: 因为如果是vec[right] > tmp, 假如列表为5 6 5 7, right指针移动到5所在位置就会停止了, 此时
列表的顺序为: 5 6 5 7, left指针也会一直停留在左边5的位置, 循环无法跳出来.
  • 添加递归函数,实现左右两边也进行快速排序最终版代码:
#include <iostream>
#include <vector>
using namespace std;

int partition(vector<int>& vec, int left, int right) {
	int tmp = vec[left];
	while (left < right) {
		while (left < right && vec[right] >= tmp) {  // 从右边找出比tmp小的数
			--right;  // 往左移动一步
		}
		vec[left] = vec[right];  // 把右边的值给到左边的空缺位置
		while (left < right && vec[left] <= tmp) {
			++left;
		}
		vec[right] = vec[left];
	}
	vec[left] = tmp;  // 将tmp进行归位
	return left;
}

void quick_sort(vector<int>& vec, int left, int right) {
	if (left < right) {  // 至少存在两个元素
		int mid = partition(vec, left, right);
		quick_sort(vec, left, mid - 1);
		quick_sort(vec, mid + 1, right);
	}
}

int main() {
	vector<int> vec = {5, 7, 4, 6, 3, 1, 2, 9, 8};
	quick_sort(vec, 0, vec.size() - 1);
	for (int num : vec) {
		cout << num << " ";
	}
	cout << endl;
}

结果:

在这里插入图片描述

快速排序时间复杂度:O(nlogn)

(5)归并排序

  • 理解一次归并
    在这里插入图片描述

  • 一次归并动画演示:点我观看

  • 一次归并的代码:

#include <iostream>
#include <vector>

void mergeOne(std::vector<int>& arr, int low, int mid, int high) {
    int i = low;
    int j = mid + 1;
    std::vector<int> temp;  // 临时存储排序后的元素

    // 合并两个有序子数组
    while (i <= mid && j <= high) {
        if (arr[i] < arr[j]) {
            temp.push_back(arr[i]);
            i++;
        } else {
            temp.push_back(arr[j]);
            j++;
        }
    }

    // 如果左边还有元素,全部加入临时数组
    while (i <= mid) {
        temp.push_back(arr[i]);
        i++;
    }

    // 如果右边还有元素,全部加入临时数组
    while (j <= high) {
        temp.push_back(arr[j]);
        j++;
    }

    // 将临时数组中的元素拷贝回原数组
    for (int k = low; k <= high; ++k) {
        arr[k] = temp[k - low];
    }
}

int main() {
    std::vector<int> li = {2, 5, 7, 8, 9, 1, 3, 4, 6};
    mergeOne(li, 0, 4, 8);

    // 输出排序后的数组
    for (int num : li) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}
  • 归并排序整体思路:【递归解决:先分解,再合并】
    在这里插入图片描述

  • 归并排序图解:

在这里插入图片描述

  • 归并排序最终代码:
#include <iostream>
#include <vector>

void mergeOne(std::vector<int>& arr, int low, int mid, int high) {
    int i = low;
    int j = mid + 1;
    std::vector<int> temp;  // 临时存储排序后的元素

    // 合并两个有序子数组
    while (i <= mid && j <= high) {
        if (arr[i] < arr[j]) {
            temp.push_back(arr[i]);
            i++;
        } else {
            temp.push_back(arr[j]);
            j++;
        }
    }

    // 如果左边还有元素,全部加入临时数组
    while (i <= mid) {
        temp.push_back(arr[i]);
        i++;
    }

    // 如果右边还有元素,全部加入临时数组
    while (j <= high) {
        temp.push_back(arr[j]);
        j++;
    }

    // 将临时数组中的元素拷贝回原数组
    for (int k = low; k <= high; ++k) {
        arr[k] = temp[k - low];
    }
}

void mergeSort(std::vector<int>& arr, int low, int high) {
    if (low < high) {
        int mid = (low + high) / 2;
        mergeSort(arr, low, mid);
        mergeSort(arr, mid + 1, high);
        mergeOne(arr, low, mid, high);
    }
}

int main() {
    std::vector<int> li = {5, 2, 8, 7, 9, 3, 4, 6, 1};
    mergeSort(li, 0, li.size() - 1);

    // 输出排序后的数组
    for (int num : li) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

归并排序时间复杂度:O(nlogn)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/919409.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

应聘美容师要注意什么?博弈美业收银系统/管理系统/拓客系统分享建议

随着美容行业的不断发展&#xff0c;成为一名优秀的美容师需要具备一系列重要的技能和品质。无论是在面试过程中还是在实际工作中&#xff0c;以下建议将帮助你在应聘美容师职位时脱颖而出&#xff1a; ▶ 专业技能和资格 首先&#xff0c;确保你具备所需的专业技能和资格。这…

JVM性能分析工具JProfiler的使用

一、基本概念 JProfiler&#xff1a;即“Java Profiler”&#xff0c;即“Java分析器”或“Java性能分析工具”。它是一款用于Java应用程序的性能分析和调试工具&#xff0c;主要帮助开发人员识别和解决性能瓶颈问题。 JVM&#xff1a;即“Java Virtual Machine”&#xff0c…

css鼠标移动效果高亮追随效果

如图所示&#xff0c;鼠标移动有一块高亮随着鼠标移动。代码如下&#xff1a;(vue3篇) <div class"container"><span class"use-hover-hglh-element trail" :style"isShow ? dyStyle : { opacity: 0 }"></span></div>…

PHP屏蔽海外IP的访问页面(源代码实例)

PHP屏蔽海外IP的访问页面&#xff08;源代码实例&#xff09;&#xff0c;页面禁用境外IP地址访问 <?php/*** 屏蔽海外ip访问* 使用ip2long函数得到ip转为整数的值&#xff0c;判断值是否在任一一个区间中* 以下是所有国内ip段* 调用方法&#xff1a;IschinaIp($ALLIPS)* …

现代分布式系统新法宝:基于单元的架构

- 前言 - 数十年来&#xff0c;IT 业界一直在努力掌握分布式系统。然而&#xff0c;随着系统日益复杂&#xff0c;给开发数字产品的组织带来巨大挑战。可以说&#xff0c;分布式系统最棘手的方面之一是面对故障时的可靠性&#xff0c;特别是现代分布式系统使用大量物理与虚拟资…

2.8 群辉 黑群晖 意味断电 抱歉,您所指定的页面不存在。

实验室组装的黑群晖施工时不小心被意味断电&#xff0c;然后出现了如下图&#xff1a; 对于7.1.1的系统来说&#xff0c;这个是由于libsynopkg.so.1和libsynoshare.so.7这两个文件出问题所致。 因此&#xff0c;解决方法也比较简单就是把好的文件恢复到/lib文件夹下即可。 这…

【视频讲解】Python深度神经网络DNNs-K-Means(K-均值)聚类方法在MNIST等数据可视化对比分析...

全文链接&#xff1a;https://tecdat.cn/?p38289 分析师&#xff1a;Cucu Sun 近年来&#xff0c;由于诸如自动编码器等深度神经网络&#xff08;DNN&#xff09;的高表示能力&#xff0c;深度聚类方法发展迅速。其核心思想是表示学习和聚类可以相互促进&#xff1a;好的表示会…

K8S资源限制之ResourceQuota

ResourceQuota介绍 在K8S中&#xff0c;大部分资源都可以指定到一个名称空间下&#xff0c;因此可以对一个名称空间的计算资源&#xff0c;存储资源&#xff0c;资源数量等维度做资源限制。 如限制pod数量、svc数量&#xff0c;控制器数量&#xff0c;限制PVC请求的存储量 注…

【Android原生问题分析】夸克、抖音划动无响应问题【Android14】

1 问题描述 偶现问题&#xff0c;用户打开夸克、抖音后&#xff0c;在界面上划动无响应&#xff0c;但是没有ANR。回到Launcher后再次打开夸克/抖音&#xff0c;发现App的界面发生了变化&#xff0c;但是仍然是划不动的。 2 log初分析 复现问题附近的log为&#xff1a; 用户…

[JavaWeb]微头条项目

完整笔记和项目代码&#xff1a; https://pan.baidu.com/s/1PZBO0mfpwDPic4Ezsk8orA?pwdwwp5 提取码: wwp5 JavaWeb-微头条项目开发 1 项目简介 1.1 业务介绍 微头条新闻发布和浏览平台,主要包含业务如下 用户功能 注册功能登录功能 头条新闻 新闻的分页浏览通过标题关键字搜…

AJAX学习(24.11.1-24.11.14)(包含HTTP协议)

AJAX学习&#xff08;24.11.1-11.14) 来源&#xff1a; 传智 | 高校学习平台-首页 传智播课&#xff1a;黑马程序员 1.服务器和客户端 1.服务器&#xff1a;存放和对外提供资源的电脑。 2.客户端&#xff08;用户&#xff09;&#xff1a;获取和消费资源的电脑。&#xff0…

9.《滑动窗口篇》---①长度最小的子数组(中等)

滑动窗口推导过程 我们不能说一上来就知道这个题目用滑动窗口&#xff0c;然后就使用滑动窗口的方法来做这个题目。 首先我们想到的应该是暴力解法。 接着再优化为滑动窗口 由于数字都是 ≥ 0 的数。因此累加的数越多。和越大。 因此right往后遍历的时候。当发现sum > targe…

《Python网络安全项目实战》项目5 编写网站扫描程序

《Python网络安全项目实战》项目5 编写网站扫描程序 项目目标&#xff1a;任务5.1 暴力破解网站目录和文件位置任务描述任务分析任务实施相关知识任务评价 任务5.2 制作网页JPG爬虫任务分析任务实施相关知识任务评价任务拓展 WEB网站安全渗透测试过程中需要进行目录扫描和网站爬…

React(二)

文章目录 项目地址七、数据流7.1 子组件传递数据给父组件7.1.1 方式一:給父设置回调函数,传递给子7.1.2 方式二:直接将父的setState传递给子7.2 给props传递jsx7.2.1 方式一:直接传递组件给子类7.2.2 方式二:传递函数给子组件7.3 props类型验证7.4 props的多层传递7.5 cla…

Python学习29天

二分查找 # 定义函数冒泡排序法从大到小排列 def bbble_sort(list):# i控制排序次数for i in range(len(list) - 1):# j控制每次排序比较次数for j in range(len(list) - 1 - i):if list[j] < list[j 1]:list[j], list[j 1] list[j 1], list[j] # 定义二分查找函数 def…

【工控】线扫相机小结 第三篇

海康软件更新 目前使用的是 MVS_STD_4.3.2_240705.exe &#xff0c;最新的已经到4.4了。 一个大的变动 在上一篇中我们提到一个问题&#xff1a; 需要注意的是&#xff0c;我们必须先设置 TriggerSelector 是 “FrameBurstStart” 还是 “LineStart” 再设置TriggerMode 是 …

K8S资源限制之LimitRange

LimitRange介绍 LimitRange也是一种资源&#xff0c;在名称空间内有效&#xff1b;限制同一个名称空间下pod容器的申请资源的最大值&#xff0c;最小值pod的resources中requests和limits必须在这个范围内&#xff0c;否则pod无法创建。当然pod也可以不使用resources进行创建ty…

Maven maven项目构建的生命周期 Maven安装配置 IDEA 配置 Maven

一&#xff0c;Maven的概述 Maven的作用&#xff1a;专门用于管理和构建Java项目的工具&#xff0c;它的主要功能有&#xff1a; 提供了一套标准化的项目结构提供了一套标准化的构建流程&#xff08;编译&#xff0c;测试&#xff0c;打包&#xff0c;发布……&#xff09;提…

Rust “xxx“.to_string()和Rust String::from(“xxx“)区别(将字符串字面量(str类型)转换为String类型)

文章目录 Rust "xxx".to_string()和Rust String::from("xxx")区别1. .to_string()&#xff08;能够将任何可以显示的类型&#xff08;如数字、结构体等&#xff09;转为字符串&#xff09;2. String::from()区别总结&#xff1a;性能&#xff1a;示例对比&…

Windows仿macOS?看这一篇就够了

如果你有任何关于Windows仿macOS的问题&#xff0c;可加入942644281 &#xff08;QQ群&#xff09; Date9.20更新&#xff1a;增加功能按键左移部分Date9.16更新&#xff1a;增加了大多数资源的网盘链接Date9.15更新&#xff1a;增加StartAllBack&#xff0c;资源管理器调整部…