目录
一、数组
存放数据的一个组,所有的数据都统一存放在这一个组中,一个数组可以同时存放多个数据。保存在数组中的数据,称为“元素”
1.1、创建数组
创建数组的方式有很多种
int a[10]; //直接声明int数据类型,容量为10
int b[10] = {1, 2, 3}; //声明后,可以赋值初始值,使用{}囊括,不一定需要让10个位置都有初始值,比如这里仅仅是为前三个设定了初始值,注意,跟变量一样,如果不设定初始值,数组内的数据并不一定都是0
int c[10] = {1, 2, [4] = 777, [9] = 666}; //我们也可以通过[下标] = 的形式来指定某一位的初始值。注意下标是从0开始的,第一个元素就是第0个下标位置,比如这里数组容量为10,那么最多到9
int c[] = {1, 2, 3}; //也可以根据后面的赋值来决定数组长度
基本类型都可以声明数组:
#include <stdio.h>
int main() {
char str[] = {'A', 'B', 'C'}; //多个字符
char str2[] = "ABC"; //实际上字符串就是多个字符的数组形式
}
1.2、打印12个月的天数
比如现在需要打印12个月的天数
# include <stdio.h>
int main(){
int arr[12] = {31,28,31, 30, 31, 30, 31, 30, 31, 30, 31, 30};
for(int i=0; i< 12; i++){
int days = arr[i];
printf("%d月的天数是%d天\n", (i+1), days);
}
}
同样的,我们也可以对数组中的值进行修改
# include <stdio.h>
int main() {
int arr[] = {666, 777, 888};
arr[1] = 999; //让第二个元素的值变为999
printf("%d", arr[1]);
}
现在要求将低于31天的月份修改为0
# include <stdio.h>
int main(){
int arr[12] = {31,28,31, 30, 31, 30, 31, 30, 31, 30, 31, 30};
for(int i=0; i< 12; i++){
if(arr[i] < 31){
arr[i] = 0;
};
printf("%d月的天数是%d天 \n", (i+1), arr[i]);
// printf("%d月的天数是%d天\n", (i+1), days);
}
}
1.3、多维数组
数组不仅仅只可以有一个维度,我们可以创建二维甚至多维的数组,简单来说就是,存放数组的数组。
int main(){
int arr[3][12] = {
{31,28,31, 30, 31, 30, 31, 30, 31, 30, 31, 30},
{31,28,31, 30, 31, 30, 31, 30, 31, 30, 31, 30},
{31,28,31, 30, 31, 30, 31, 30, 31, 30, 31, 30}};
printf("%d", arr[1][2]);
};
1.4、冒泡排序
现在有一个int数组,但是数组内的数据是打乱的,现在请你通过C语言,实现将数组中的数据按从小到大的顺序进行排序:
冒泡排序的核心思想是:
假设数组长度为N;
进行N轮循环,每次循环都选出一个最大的数放到后面;
每次循环中,从第一个数开始,让其与后面的数两两比较,如果更大,就交换位置,如果更小,就不动。
# include<stdio.h>
int main(){
int arr[10] = {3,5,7,2,9,0,6,1,8,4};
int mid;
for (int i = 0; i < 10; ++i) {
for (int j = i+1; j < 10; ++j) {
if(arr[i] > arr[j]){
mid = arr[i];
arr[i] = arr[j];
arr[j] = mid;
} else continue;
}
}
for (int i = 0; i < 10; ++i) {
printf("数组中第%d个数为%d \n", i, arr[i]);
}
};
1.5、斐波那契数列解法其二
学习过数组,我们就可以利用数组来计算斐波那契数列,这里可以采用动态规划的思想
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优解的值。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
我们可以在一开始创建一个数组,然后从最开始的条件不断向后推导
# include<stdio.h>
int main() {
int target = 8;
int arr[target];
arr[0] = 1;
arr[1] = 1;
for (int i = 2; i < target; ++i) {
arr[i] = arr[i-1] + arr[i-2];
}
printf("%d", arr[target-1]);
}
1.6、打家劫舍(力扣)
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
实例1:
输入:[1, 2, 3, 1]
输出:4
解释:偷窃1号房屋(金额 = 1),然后偷窃3号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4。
实例2:
输入:[2, 7, 9, 3, 1]
输出:12
解释:偷窃1号房屋(金额=2),偷窃3号房屋(金额=9),接着偷窃5号房屋(金额=1)。偷窃到的最高金额 = 2 + 9 + 1 = 12
# include<stdio.h>
int main() {
int target = 5;
int result;
int dp[target];
int arr[] = {2, 7, 9, 3, 1};
dp[0] = arr[0];
dp[1] = arr[1] > arr[0] ? arr[1] : arr[0];
for (int i = 2; i < target; ++i) {
dp[i] = dp[i-1] > dp[i-2]+arr[i] ? dp[i-1]:dp[i-2]+arr[i];
}
result = dp[target-1];
printf("%d", result);
printf("\n");
for (int i = 0; i < target; ++i) {
printf("%d \t", dp[i]);
}
}
第一步:假设现在只有 [2] 这一个房屋,那么我们只能去偷一号房。
第二步:假设现在有 [2, 7] 两个房屋,我们需要比较 2 和 7 ,去选择偷钱多的那一间房。
第三步:假设现在有 [2, 7, 9] 三间房屋,我们需要比较是先偷一号房再偷三号房,还是直接偷二号房。
所以有如下递推关系
dp[i] = max(dp[i - 1], dp[i - 2] + arr[i])
二、字符串
对于字符类型的数组,比较特殊,它实际上可以作为一个字符串(String)表示,字符串就是一个或多个字符的序列,比如我们在一开始认识的 “hello world”,像这样的多个字符形成的一连串数据,就是一个字符串,而printf函数接收的第一个参数也是字符串
2.1、字符串的创建与使用
在c语言中并没有直接提供存储字符串的类型,我们熟知的能够存储字符的只有char类型,但是它只能存储单个字符,而一连串的字符想要通过变量进行保存,就只能依靠数组。
//直接保存单个字符,字符串末尾必须添加一个'\0'表示结束
char str[] = {'H','e','l','l','o','\0'};
//用%s来作为一个字符串输出
printf("%s", str);
char str[] = "Hello";
printf("%s", str);
2.2、scanf、gets、puts函数
前面我们认识了printf函数,实际上这个函数就是用于打印字符串到控制台,我们只需要填入一个字符串和后续的参数即可。
#include<stdio.h>
int main() {
const char str[] = "hello world";
printf(str);
}
现在我们知道该如何输出,那么输入该如何实现呢,比如我们现在希望将我们想要说的话告诉程序,让程序从控制台读取我们输入的内容,这时候我们就需要用到scanf函数
# include<stdio.h>
int main() {
char str[10];
// 用scanf函数来接受控制台输入,并将输入的结果按照格式,分配给后续的变量
scanf("%s", str);
printf("%s", str);
}
除了扫描字符串之外,我们也可以直接扫描数字
# include<stdio.h>
int main() {
int a,b;
scanf("%d", &a);
scanf("%d", &b); //对于不是数组类型的数,在填写变量时要在前面添加一个&符号
printf("a + b = %d", a + b);
}
除了使用scanf之外,我们也可以使用字符串专用的函数来接受字符串类型的输入和输出:
# include<stdio.h>
int main() {
char str[10];
gets(str);
puts(str);
}
当然也有专门用于字符输入输出的函数:
# include<stdio.h>
int main() {
int c = getchar();
putchar(c);
}
2.3、回文串判断
“回文串”是一个正读和反读都一样的字符串,请你实现一个C语言程序,判断用户输入的字符串(仅出现英文字符)是否为“回文”串。
ABA就是一个回文串,因为正读反读都是一样的
ABCA就不是一个回文串,因为反着读不一样
# include<stdio.h>
# include<string.h>
int main() {
char str[64];
scanf("%s", str);
int len = strlen(str); //字符串长度
int left=0, right=len-1;
_Bool flag = 1;
while (left < right) {
if(str[left] != str[right]){
flag = 0;
break;
}
left++;
right--;
}
flag ? printf("yes") : printf("no");
}
2.4、字符串匹配(暴力解法)
现在有两个字符串:
str1 = "abcdabbc"
str2 = "cda"
现在请你设计一个C语言程序,判断第一个字符串中是否包含了第二个字符串,比如上面的例子中,很明显第一个字符串包含了第二个字符串
# include<stdio.h>
# include<string.h>
int main() {
char str1[64];
char str2[64];
_Bool flag = 0;
gets(str1);
gets(str2);
int len1 = strlen(str1);
int len2 = strlen(str2);
for (int i = 0; i < len1; ++i) {
for (int j = 0; j < len2; ++j) {
if(str1[i + j] != str2[j]){
flag = 1;
break;
}
}
if(!flag) break;
}
puts(flag?"不包含":"包含");
}
2.5、字符串匹配(KMP算法)
暴力解法虽然比较好理解,但可能会做一些无意义的比较
如果不匹配的位置发生在第三个字符,而前面是a,b两个字符都匹配,显然完全没有必要再继续挨着去比较a和b,因为很明显不可能匹配。
当拿到子串时,就需要根据子串来计算一个叫做next的数组,与子串的长度相同,它存储了当不匹配发生在对应的位置上时,应该在哪一个位置开始继续比较。
KMP算法:
从第一位开始依次推导。
next数组的第一位一定是0。
从第二位开始(用i表示),将第i-1个字符(也就是前一个)与其对应的 next[i-1]-1 位上的字符进行比较。
如果相等,那么next[i]位置的值就是next[i - 1] + 1
如果不相等,则继续向前计算一次next[next[i-1] - 1] - 1 位置上的字符和第 i - 1 个字符是否相同,直到找到相等的为止,并且这个位置对应的值加上1就是next[i]的值了,如果都已经到头了都没遇到相等的,那么next[i] 直接等于 1
# include<stdio.h>
# include<string.h>
int main() {
char str1[] = "abababcabb";
char str2[] = "ababc";
int len1 = strlen(str1), len2 = strlen(str2);
int next[len2];
next[0] = 0;
for (int i = 1; i < len2; ++i) {
int j = i - 1;
while (1) {
if (next[j] == 0 || str2[i - 1] == str2[next[j] - 1]) {
next[i] = next[j] + 1;
break;
}
j = next[j] - 1;
}
}
for (int i = 0; i < len2; ++i) {
printf("%d", next[i]);
}
int i = 0, j = 0;
while (i < len1) {
if (str1[i] == str2[j]) {
i++;
j++;
} else {
if (j == 0) {
i++;
j++;
} else {
j = next[j] - 1;
}
}
if(j == len2) break;
}
printf(j == len2 ? "yes" : "no");
}