数组
1、数组的概念:
数组,它是一组具有相同数据类型,并且按照顺序排列的变量的集合。在内存中,数组表现为一块连续的存储区域。
2、数组的定义:
数组:在变量名后多加一个下标号[]来指定元素的个数
下标号[ ]内必须是一个常量表达式而不能是变量!
3、数组元素的调用
#include <stdio.h>
int main(){
const int N = 6;
int a[N];
int i = N;
while (i--)
a[i] = i * 8 + 2;
while (++i < N)
printf("a[%d]=%d\n", i, a[i]);
return 0;}
4、数组元素的初始化
普通变量的初始化,在赋值负号后加上同类型的常量、变量或表达式。
数组是一个变量的集合,在初始化时要使用{}符号对部分或全部数组元素进行集体赋值。
格式:数据类型 数组名[整型常量表达式] ={数据1, 数据2, 数据3, ……};
#include <stdio.h>
int main(){
int array[] = { 9,6,8,5,3 };
int i = -1;
while (++i < sizeof(array) / sizeof(array[0])) {
printf("%d ", array[i]);
}
printf("\n这个数组总共有 %d 个元素\n", i);
return 0;
}
5、数组元素个数的求法
#include <stdio.h>
#include <stdlib.h>
int main(){
double array[] = { 8,6,5 };
printf("这个数组总共有 %d 个元素\n", sizeof(array)/sizeof(array[0]));
return 0;
}
6、数组元素访问越界问题
通过下标可以访问数组内的元素,下标内数值的正确范围是从0到数组元素总数减1之间。
#include <stdlib.h>
#include <stdio.h>
int main(){
int a[5] = { 3,5,2,-8,100 };
for (int i = 0; i < sizeof(a);i++) {
printf("a[%d] = %d\n", i, a[i]);//访问出错
}
return 0;
}
7、素数筛
7.1、初始版素数筛
void InitPrime()
{
int i,j;
for(i=0;i<MAXN;i++) prime[i]=1;
prime[0]=prime[1]=0;//特殊处理
for(i=2;i<MAXN;i++){
if(!prime[i]) continue;
for(j=i*2;j<MAXN;j+=i) prime[j]=0;
}
}
-
对于求区间[0,n]内有多少个素数,我们知道:n以内的合数的质因数一定不会超过n½,所以在一层循环找质因数时,循环条件可以改为:
int i = 2; i*i <= n; i++;
-
在第二层循环提出质因数倍数时,i,2 i,…,(i-1)i已经被前面的质因数剔除,因此:
循环可以从ii开始,循环条件可以改为:int j = ii; j <= n; j += i;
7.2、改进版素数筛
bool prime[maxn];//标记范围内的i是否是素数
vector<int> p;//装范围内所有素数 p.size()=素数个数
void InitPrime()
{
//初始化
for(int i = 0; i < maxn; i++) prime[i] = 1;
prime[0] = prime[1] = 0;
p.clear();
//注意 i*i < maxn 和 j从i*i开始每次+i
for(int i = 2; i * i < maxn; i++)
if(prime[i])
for(int j = i * i; j < maxn; j += i) prime[j] = 0;
for(int i = 0; i < maxn; i++){
if(prime[i]) p.push_back(i);
}
}
7.3、最终版素数筛
#include<stdio.h>
#define MAX_N 100
int prime[MAX_N + 5] = { 0 };
void init() {
for (int i = 2; i <= MAX_N; i++) {
if (prime[i])continue;
prime[++prime[0]] = i;
for (int j = i; j <= MAX_N / i; j++) {
prime[j * i] = 1;
}
}
return;
}
int main() {
init();
for (int i = 1; i <= prime[0]; i++) {
printf("%d\n",prime[i]);
}
return 0;
}
8、线性筛
8.1、通过线性筛求一个数N的因子个数
#include<stdio.h>
#define MAX_N 100
int prime[MAX_N + 5];
int fac[MAX_N + 5];
int cnt[MAX_N + 5];
void init() {
//如果a,b互素,fac(a*b)=fac(a)*fac(b)
for (int i = 2; i <= MAX_N;i++) {
if (!prime[i]) {
prime[++prime[0]] = i;
fac[i] = 2;
cnt[i]= 1;
}
for (int j = 1; j <= prime[0];j++) {
if (prime[j] * i > MAX_N)break;
prime[prime[j] * i] = 1;
if (i % prime[j] == 0) {
fac[i * prime[j]] = fac[i] / (cnt[i] + 1) * (cnt[i]+2);
cnt[i * prime[j]] = cnt[i] + 1;
break;
}
else {
fac[i * prime[j]] = fac[i] * fac[prime[j]];
cnt[i * prime[j]] = 1;
}
}
}
return;
}
int main() {
init();
for (int i = 2; i <= MAX_N;i++) {
printf("fac[%d]=%d,cnt[%d]=%d\n",i,fac[i],i,cnt[i]);
}
return 0;
}
8.2、通过线性筛打表出 1 到 N 的约数和
const int N=1e5+5;
bool mark[N];
int prim[N];
long long sd[N],sp[N];
int cnt;
void initial()
{
cnt=0;
sd[1]=1;
for (int i=2 ; i<N ; ++i)
{
if (!mark[i])
{
prim[cnt++]=i;
sd[i]=i+1;
sp[i]=i+1;
}
for (int j=0 ; j<cnt && i*prim[j]<N ; ++j)
{
mark[i*prim[j]]=1;
if (!(i%prim[j]))
{
sp[i*prim[j]]=sp[i]*prim[j]+1;
sd[i*prim[j]]=sd[i]/sp[i]*sp[i*prim[j]];
break;
}
sd[i*prim[j]]=sd[i]*sd[prim[j]];
sp[i*prim[j]]=1+prim[j];
}
}
}
9、二分查找算法
折半查找,也称二分查找,在某些情况下相比于顺序查找,使用折半查找算法的效率更高。但是该算法的使用的前提是静态查找表中的数据必须是有序的。
在折半查找之前对查找表按照所查的关键字进行排序的意思是:若查找表中存储的数据元素含有多个关键字时,使用哪种关键字做折半查找,就需要提前以该关键字对所有数据进行排序。
int binary_search(int *arr,int n,int x) {
int head = 0, tail = n - 1, mid;
while (head<=tail) {
mid = (head + tail) >> 1;
if (arr[mid] == x)return mid;
if (arr[mid] < x)head = mid + 1;
else tail = mid - 1;
}
return -1;
}
int binary_search(int *arr,int l,int r,int x) {
if (l > r) return -1;
int mid = (l + r) >> 1;
if (arr[mid] == x)return mid;
if (arr[mid] < x)l = mid + 1;
else r = mid - 1;
return binary_search(arr,l,r,x);
}
二分查找算法
对静态查找表{5,13,19,21,37,56,64,75,80,88,92}
采用二分查找算法查找关键字为 21 的过程为:
图 1 折半查找的过程(a)
如上图 1 所示,指针 low 和 high 分别指向查找表的第一个关键字和最后一个关键字,指针 mid 指向处于 low 和 high 指针中间位置的关键字。在查找的过程中每次都同 mid 指向的关键字进行比较,由于整个表中的数据是有序的,因此在比较之后就可以知道要查找的关键字的大致位置。
例如在查找关键字 21 时,首先同 56 作比较,由于21 < 56
,而且这个查找表是按照升序进行排序的,所以可以判定如果静态查找表中有 21 这个关键字,就一定存在于 low 和 mid 指向的区域中间。
因此,再次遍历时需要更新 high 指针和 mid 指针的位置,令 high 指针移动到 mid 指针的左侧一个位置上,同时令 mid 重新指向 low 指针和 high 指针的中间位置。如图 2 所示:
图 2 折半查找的过程(b)
同样,用 21 同 mid 指针指向的 19 作比较,19 < 21
,所以可以判定 21 如果存在,肯定处于 mid 和 high 指向的区域中。所以令 low 指向 mid 右侧一个位置上,同时更新 mid 的位置。
图 3 折半查找的过程(c)
当第三次做判断时,发现 mid 就是关键字 21 ,查找结束。
- 注意:在做查找的过程中,如果 low 指针和 high 指针的中间位置在计算时位于两个关键字中间,即求得 mid 的位置不是整数,需要统一做取整操作。
二分查找算法之链表实例:
#include <stdio.h>
#include <stdlib.h>
#define keyType int
typedef struct {
keyType key;//查找表中每个数据元素的值
//如果需要,还可以添加其他属性
}ElemType;
typedef struct{
ElemType *elem;//存放查找表中数据元素的数组
int length;//记录查找表中数据的总数量
}SSTable;
//创建查找表
void Create(SSTable **st,int length){
(*st)=(SSTable*)malloc(sizeof(SSTable));
(*st)->length=length;
(*st)->elem = (ElemType*)malloc((length+1)*sizeof(ElemType));
printf("输入表中的数据元素:\n");
//根据查找表中数据元素的总长度,在存储时,从数组下标为 1 的空间开始存储数据
for (int i=1; i<=length; i++) {
scanf("%d",&((*st)->elem[i].key));
}
}
//折半查找算法
int Search_Bin(SSTable *ST,keyType key){
int low=1;//初始状态 low 指针指向第一个关键字
int high=ST->length;//high 指向最后一个关键字
int mid;
while (low<=high) {
mid=(low+high)/2;//int 本身为整形,所以,mid 每次为取整的整数
if (ST->elem[mid].key==key)//如果 mid 指向的同要查找的相等,返回 mid 所指向的位置
{
return mid;
}else if(ST->elem[mid].key>key)//如果mid指向的关键字较大,则更新 high 指针的位置
{
high=mid-1;
}
//反之,则更新 low 指针的位置
else{
low=mid+1;
}
}
return 0;
}
int main(int argc, const char * argv[]) {
SSTable *st;
Create(&st, 11);
getchar();
printf("请输入查找数据的关键字:\n");
int key;
scanf("%d",&key);
int location=Search_Bin(st, key);
//如果返回值为 0,则证明查找表中未查到 key 值,
if (location==0) {
printf("查找表中无该元素");
}else{
printf("数据在查找表中的位置为:%d",location);
}
return 0;
}
二分查找算法之寻找值实例:
#include <stdio.h>
#include <stdlib.h>
//二分查找算法,找不到就返回-1,找到了就返回值
int binary_search(int* list, int len, int target) {
int low = 0;
int hight = len - 1;
int middle;
while (low <= hight) {
middle = (low + hight) / 2;
if (list[middle] = target)
{
printf("已找到该值,数组下标为:%d\n", middle);
return list[middle];
}
else if (list[middle] > target)
{
hight = middle - 1;
}
else if (list[middle] < target)
{
low = middle + 1;
}
}
printf("未找到该值");
return -1;
}
int main()
{
int a[] = { 2,4,5,8,9,44 };
int b = binary_search(a, sizeof(a)>>2, 5);
printf("b=%d\n", b);
return 0;
}
二分法 实现 sqrt函数
#include<stdio.h>
#include<math.h>
//x*x
double my_sqrt(double n) {
double head = 0, tail = n+1.0, mid=2.0;
#define EPSL 1e-6
while (tail - head > EPSL) {
mid = (head + tail) / 2.0;
if (mid * mid < n)head = mid;
else tail = mid;
}
return head;
}
int main() {
double n;
while (~scanf_s("%lf",&n)) {
printf("sqrt(%g)=%g\n",n,sqrt(n));
printf("mysqrt(%g)=%g\n",n,my_sqrt(n));
}
}
10、牛顿迭代法
题目:给定方程 f ( x ) = x 3 / 3 − x = 0 f(x)=x^3/3-x=0 f(x)=x3/3−x=0 使用牛顿法解方程的根
#include<stdio.h>
#include<cmath>
double fd(double x)
{
double c = (x * x * x) / 3 - x;
double d = x * x - 1;
return (c / d);
}
void newton(double x0, double e)
{
double a = x0;
double x = x0 - fd(x0);
int i = 0;
while (abs(x - a) > e)
{
printf("%lf", a);
a = x;
i++; //循环次数
x = x - fd(x);
if (i > 50)
{
printf("迭代失败!");
return;
}
}
printf("%d\n", i);
}
int main()
{
double x0, e;
printf("请输入x0的值:");
scanf_s("%lf", &x0);
printf("请输入容许误差:");
scanf_s("%lf", &e);
newton(x0, e);
return 0;
}
第四部分 字符数组、字符串数组
1、字符数组、字符串数组的初始化
用来存放字符的数组称为字符数组,一般使用char类型来定义。
字符串数组
在char类型数组中,如果有一个或多个元素的数值是’\0’的话,这个数组就是字符串数组。
字符串数组的特征
字符数组必须是char类型的数组
在数组的有效空间内,必须含有一个以上数值是‘\0’的字节,作为字符串的结束标志
1.1、一般整型数组的初始化方式
#include <stdio.h>
#include <string.h>
int main(){
char a[20] = { 'h','e','l','l','0' };
printf("%s",a);
return 0;
}
1.2、使用字符串常量来对数组进行初始化
#include <stdio.h>
#include <string.h>
int main(){
char a[20] = "hello";
printf("%s",a);
return 0;
}
1.3、数组空间和字符串长度
#include <stdio.h>
#include <string.h>
int main(){
char a[20] = "hello\0";
printf("%d\n",sizeof(a));
//输出结果为20,对应自己开辟的字符数组空间大小
printf("%s",a);
return 0;
}
2、数组做参数
-
数组做参数时, 可以不指定长度,也可以随意指定任何长度。
-
其实,数组做参数时性质已经改变了,实际参数只传递了数组的地址(指针)给形参。
-
数组做参数时,已经不再是最初的数组变量,会被集成开发环境(我们使用的编译器)在内部转为指针变量。
#include <stdio.h>
#include <stdlib.h>
void Print(int a[10]) {
printf("%d\n", sizeof(a) / (sizeof(a[0])));
//警告 C6384 用另一个值除指针的 sizeof 值。
}
void print_f(int a) {
printf("%d\n", sizeof(a));
}
int main() {
int a[10] = { 1,2,3,8 };
Print(a);
print_f(5);
return 0;
}
3、字符串操作函数
• 字符串操作函数,也叫字符串族函数或str族函数
• 在C语言标准库中,所有以str开头的函数都属于字符串族函数。
*取字符串长度函数:*
size_t strlen( const char *str);
*字符串拷贝函数:*
char *strcpy( char *strDestination, const char *strSource);
*字符串连接函数:*
char *strcat( char *strDestination, const char *strSource);
*字符串比较函数:*
int strcmp( const char *string1, const char *string2);
*单字符查找函数:*
char *strchr( const char *str, int c);
*多字符查找函数:*
char *strpbrk( const char *str, const char *strCharSet );
*字符串分割函数:*
char *strtok( char *strToken, const char *strDelimit);
*字符串查找函数:*
char *strstr(char *str, char *strSearch);