程序设计与算法 | (9) 数组

本专栏主要基于北大郭炜老师的程序设计与算法系列课程进行整理,包括课程笔记和OJ作业。该系列课程有三部分: (一) C语言程序设计;(二) 算法基础;(三) C++面向对象程序设计

(一) C语言程序设计 课程链接

1. 数组的概念

倒序问题
  • 接收键盘输入的100个整数,然后将它们按和原顺序相反的顺序输出。

  • 如何存放这100个整数?

  • 定义100个int型变量,n1, n2, n3 …n100,用来存放这100个整数!!!(不可行)

  • 使用数组!!

  • 可以用来表达类型相同的元素的集合,集合的名字就是数组名。

  • 数组里的元素都有编号,元素的编号叫下标。通过数组名和下标,就能访问
    元素。

  • 一维数组的定义方法: 类型名 数组名[元素个数]

  • 其中**“元素个数”必须是常量或常量表达式**,不能是变量,而且其值必须是正整数。元素个数也称作“数组的长度”。

  • int a[100]; 名字为a的数组,有100个元素,每个元素都是一个int型变量。

  • T a[N] ;T为类型名,如char,double,int等;N为正整数或值为正整数的常量表达式。
    数组a有N个元素,每个元素都是一个类型为T的变量。 N个元素在内存里是一个挨一个连续存放的。 a数组占用大小总共为 N × sizeof(T)字节的存储空间。

  • 表达式“sizeof(a)”的值就是整个数组的体积,即N × sizeof(T)。

  • int a[100]
    在这里插入图片描述
    数组下标从0开始,N个元素的数组,下标从0 至 N-1。数组名a代表数组的地址,假设为p(也是a[0]的地址),则变量a[i]的地址就是 p+i*sizeof(int)

  • 接收键盘输入的100个整数,然后将它们按和 原顺序相反的顺序输出

#include <iostream>
using namespace std;
#define NUM 100 //使用符号常量,便于修改
int a[NUM]; //数组一般不要定义在main里面,尤其是大数组 
int main() 
{
	for(int i = 0;i < NUM; ++i) 
		cin >> a[i];
	for(int i = NUM-1;i >= 0; --i) 
		cout << a[i] << " ";
	return 0; 
}
筛法求素数

筛法求n以内的所有素数:

  • 判断一个数n是不是素数,可以用2到 n \sqrt{n} n 之间的所有整数去除n, 看能否整除。如果都不能整除,那么n是素数(慢)。
  • 筛法求素数:把2到n中所有的数都列出来,然后从2开始,先划掉n内所有2 的倍数,然后每次从下一个剩下的数(必然是素数:若不是素数,前面一定有其因子,在处理其因子的倍数时,已经被划掉了,矛盾。)开始,划掉其n内的所有倍数。最后剩下的数,就都是素数。
  • 空间换时间,加快了计算速度(大多数情况下,空间(内存)都够用,很多程序都用空间换时间,提高速度;当然有些时候,当空间不够时,也可以用时间换空间。)
 
#include <iostream> //筛法求素数 
#include <cmath>
using namespace std;
#define MAX_NUM 10000000   //符号常量 便于修改
bool isPrime[MAX_NUM + 10]; //标志数组,由于标志都是0/1,用char类型就足够了 节省内存。声明后,值默认初始化为0,最终如果isPrime[i]为1,则表示i是素数。
//+10 包含MAX_NUM,其实+1就够了,+10保险,防止越界,多几个元素无所谓
int main()
{
	for( int i = 2;i <= MAX_NUM; ++i) //开始假设所有数都是素数 
		isPrime[i] = true;
	for( int i = 2;i <= MAX_NUM; ++i) { //每次将一个素数的所有倍数标记为非素数 
		if( isPrime[i]) //只标记素数的倍数
			for( int j = 2 * i; j <= MAX_NUM; j += i) 
				isPrime[j] = false; //将素数 i 的倍数标记为非素数
	}
	for( int i = 2;i <= MAX_NUM; ++i)
		if( isPrime[i])
			cout << i << endl;
	return 0;
}

2. 数组的初始化

  • 在定义一个一维数组的同时,就可以给数组中的元素赋初值:
    类型名 数组名[常量表达式] = {值,值…值};
    { }中的各数据值即为各元素的初值,值之间用逗号间隔.
int a[10] = {0,1,2,3,4,5,6,7,8,9};

效果:a[0]=0;a[1]=1…a[9]=9;

  • 数组初始化时,{ }中值的个数可以少于元素个数。相当于只给前面部分元素赋值,而后面的元素,其存储空间里的每个字节都被写入二进制数0:
int a[10] = {0,1,2,3,4};

只给a[0]~a[4]5个元素赋值,而后5个元素自动赋0值。

用数组取代复杂分支结构
  • 有时会用一个数组存放一些固定不变的值,以取代复杂的程序分支结构。
  • 例:接受一个整数作为输入,如果输入1,则输出“Monday”,输入2,则 输出“Tuesday”…输入7,则输出“Sunday”,输入其他数,则输出 “Illegal”。(之前我们曾用switch语句做过)
#include <iostream>
#include <string> //使用string须包含此“头文件”
using namespace std;
string weekdays[] = { //string是字符串类型。可存放字符串常量 
"Monday","Tuesday","Wednesday","Thursday",
"Friday","Saturday","Sunday" }; //字符串数组
int main()
{
	int n;
	cin >> n;
	if( n > 7 || n < 1 )
	       cout << "Illegal";
	else
		cout << weekdays[n-1]; 
	return 0;
}
  • 例题: 已知2012年1月25日是星期三,编写一个程序, 输入用“年 月 日”表示的一个2012年1月25日以后的日期, 输出该日期是星期几(星期天输出0)。
    在这里插入图片描述
    思路:可以推出2012年1月22日是星期天。由于星期天用0表示,算出给定日期是从该天起过了x天,然后直接输出x%7(就得到给定日期是星期几)。
#include <iostream>
using namespace std;
int monthDays[13] = {-1,31,28,31,30,31,30,31,31,30,31,30,31}; //0位置不用 从1开始
int main()
{
	int year,month,date;
	int days = 0; //从2012-01-22开始过了多少天 
	cin >> year >> month >> date;
	for(int y = 2012; y < year; ++y) { //遍历整年的天数
		if( y%4 ==0 && y%100!= 0 || y%400 == 0) 
			days += 366;
	    else
	        days += 365;
	}
	if( year%4 ==0 && year%100!= 0 || year%400 == 0)
		monthDays[2] = 29; 
	for(int m = 1; m < month; ++m) //计算整月的天数
		days += monthDays[m];
	days += date; //加上最后一个月的天数
	days -= 22; //2012年1月22日是星期天   减去2012已经过去的天数
	cout << days % 7 << endl;
	return 0;
}

3. 数组越界

  • 数组元素的下标,可以是任何整数,可以是负数,也可以大于等于数组的元素个数。不会导致编译错误:
    在这里插入图片描述
    但运行时很可能会出错!!!

  • int a[10];
    在这里插入图片描述
    其中0-9是合法下标。a[-2] = 10; a[11] = 100; 均可能导致程序运行出错!!! 因为可能写入了别的变量的内存空间,或者写入指令的内存空间

  • 一般情况下,C/C++数组越界,编译不会报错,但可能导致运行出错。

  • 用变量作为数组下标时(访问数组时),不小心会导致数组越界(变量下标值变为负数,或者太大)。

  • 可能引起意外修改其他变量的值,导致程序运行结果不正确

  • 可能试图访问不该访问的内存区域,导致程序崩溃

  • 数组越界的程序,用某些编译器编译后可能可以正确运行,换一个编译器编译后就运行错误。

  • 数组声明的时候,可以多开点(多几个元素),可以在一定程度上防止越界,根本上还得自己写程序时多注意。

4. 二维数组

矩阵乘法

编程求两个矩阵相乘的结果。输入第一行是整数m,n, 表示第一个矩阵是m行n列的。接下来时一个m×n的矩阵。 再下一行的输入是整数p,q,表示下一个矩阵是p行q列(n=p) 再接下来就是一个p行q列的矩阵。 要求输出两个矩阵相乘的结果矩阵(1 < m,n,p,q <= 8)。
在这里插入图片描述
在这里插入图片描述
用什么存放矩阵??

二维数组
  • 定义N行M列的二维数组:
    T a[N][M]; T :类型名,如char , double, int等。M、N : 正整数,或值为正整数的常量表达式
  • 每个元素都是一个类型为T的变量
  • N×M个元素在内存里是一个挨一个连续存放的
  • 数组占用了一片连续的、大小总共为 N×M×sizeof(T)字节的存储空间。
  • 表达式“sizeof(a)”的值就是整个数组的体积,即N×M×sizeof(T)。
  • 访问数组元素的方法:
    数组名[行下标][列下标]; 例如:a[i][j];行下标和列下标都从0开始
存放方式
  • 数组T a[N][M] 每一行都有M个元素
  • 第i行的元素就是a[i][0]、a[i][1]…a[i][M-1]。同一行的元素,在内存中是连续存放的。
  • 第j列的元素的元素,就是a[0][ j]、a[1][ j]…a[N-1][ j]。
  • a[0][0]是数组中地址最小的元素。如果a[0][0]存放在地址n,则a[i][ j]存放的地址就是
    n + iMsizeof(T)+j*sizeof(T) 下标i代表的是第i+1行,首地址加上前i(0~i-1)行
    的元素内存,再加上第i+1行元素内存。
  • int a[2][3] 在内存中的存放方式:
    在这里插入图片描述
  • 二维数组的每一行,实际上都是一个一维数组。
    a[0],a[1]都可以看作是一个一维数组的名字,可以直接当一维数组 使用。
初始化
int a[5][3]={{80,75,92},{61,65},{59,63,70},{85,90},{76,77,85}};

每个内层的{},初始化2维数组中的一行。

  • 二维数组初始化时,如果对每行都进行了初始化,则也可以不给出行数:
int a[][3]={ {80,75,92},{61,65} };

a 是一个2行3列的数组,a[1][2]被(默认)初始化成0。

遍历

遍历一个二维数组,将其所有元素逐行依次输出:

#define ROW 20
#define COL 30
int a[ROW][COL];
for( int i = 0; i < ROW ; ++i) {
	for( int j = 0; j < COL ; ++j ) 
		cout << a[i][j] << " ";
	cout << endl;
}
矩阵乘法
#include <iostream> 
using namespace std;
//符号常量 便于修改 
#define ROWS 8 
#define COLS 8
//可以多开一些空间 
int a[ROWS][COLS]; 
int b[ROWS][COLS];
int c[ROWS][COLS]; //结果
int main()
{
	int m,n,p,q;
	cin >> m >> n;
	for(int i = 0;i<m; ++i) //读入a矩阵
		for(int j = 0; j < n; ++j) 
			cin >> a[i][j];
	cin >> p >> q;
	for(int i = 0;i<p; ++i) //读入b矩阵
		for(int j = 0; j < q; ++j) 
			cin >> b[i][j];
	//矩阵乘法
	for(int i = 0; i < m; ++i) { 
		for(int	j=0;j<q;++j) {
			c[i][j] = 0;
			for(int k = 0; k < n; ++k)
        		c[i][j] += a[i][k] * b[k][j]; //结果矩阵第i行第j列的元素=a矩阵第i行和b矩阵的第j列对应相乘再想加。计算m*q*n次
		} 
	}
	//输出结果矩阵
	for(int i = 0;i<m;++i){
        for(int j = 0; j < q; ++j) {
			cout << c[i][j] << " "; 
		}
		cout << endl;
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值