数组

官方信息技术老师·12 页·深入(追求细节与边界)·0 次浏览·3 天前
内存布局时间复杂度边界场景

数组

看清 O(1) 访问的本质,掌握插入删除的真实代价

按 空格/→ 演示下一步

1 / 12 页

全部页面点击任意一页,跳回舞台从这页播放

内存布局时间复杂度边界场景

数组

看清 O(1) 访问的本质,掌握插入删除的真实代价

1第 1 页 · 数组

一维数组的定义及引用

上一节我们认识了数组是「同类型数据的集合」。这一节聚焦最基础的形态——一维数组:它怎么定义、下标从几开始、内存里长什么样、以及如何读写某个元素。

定义三要素
类型、名字、长度三部分,例 int a[10]; 长度须为编译期常量
下标从 0 起
有效范围 0~n-1;越界 C/C++ 不检查,后果未定义
连续内存布局
元素紧挨排列,总字节数 = 单元素大小 × 长度
元素引用语法
用 a[i] 读写第 i+1 个元素,i 可为变量或表达式
宿舍一排编号储物柜对应 →一维数组存储结构

柜子紧挨排列并编号,要取某件按编号直接定位;数组下标就是这编号(只是从 0 起算)

2第 2 页 · 一维数组的定义及引用

一维数组应用--冒泡排序

前页我们已经能用下标访问一维数组。现在把[5,2,4,1]排成升序:让较大的数一次次越过相邻的小数,最终移动到末端,这就是冒泡排序。

核心规则
从左到右扫描,只比较相邻元素;若前者大于后者就交换,重复多轮完成升序排序。
扫描上界
数组长度为 n 时,第 i 轮只需比较到下标 n-i;每轮结束后,末端 i 个位置已有序。
提前结束
若某一轮一次交换也没有,数组已有序,可立即停止,不必再跑满 n-1 轮。
性质与场景
相邻交换使它稳定且原地;平均、最坏为 O(n²),适合教学、近乎有序或很小的数组。
整理一串数字对应 →冒泡排序

每轮从左向右比较,较大数像气泡一样,经相邻交换逐步移到尚未排好的末端。

3第 3 页 · 一维数组应用--冒泡排序

选择法排序

冒泡排序靠相邻元素反复互换,交换次数动辄几十次。换个思路:每趟只在未排序区里挑出最小值,只换一次——这就是选择法排序。

分区扫描
数组分左右两区:左为已排序、右为未排序,每趟扫描右区找最小元素下标
一次交换到位
一趟只把极值与右区首元素换一次,左侧已排序区扩一格
趟数 n-1
n 个元素只需 n-1 趟,最后一个元素自然归位
交换远少于冒泡
总交换次数 ≤ n-1,但比较次数仍是 n(n-1)/2
不稳定排序
相等元素的原始相对顺序可能被破坏,是深入时该注意的边界问题
体育课按身高排队对应 →选择法排序

从队伍里扫一遍找出最矮的,拎到已排好队形的末尾;反复此动作直到所有人就位

比较次数=n(n1)2,交换次数n1\text{比较次数}=\frac{n(n-1)}{2},\quad \text{交换次数}\le n-1
4第 4 页 · 选择法排序

一维数组的典型应用--查找

上一页我们把数组排好序了,本页就用排好的数组讲查找。手机通讯录找'张三',两种思路:直接从头翻,或者先翻到'Z'开头。这两种思路,对应数组里最经典的两种查找方法。

查找的本质
在数据集合中找出满足条件的关键字及其位置
顺序查找
从首元素逐个比较至尾,无须预处理,平均比较 n/2 次
二分查找
前提:数组有序。每次取中点比较,范围折半
复杂度对比
顺序 O(n),二分 O(log n);n 越大二分优势越明显
边界与哨兵
查找失败须返回 -1;可设哨兵省去越界判断
在书架或字典中找一本书对应 →顺序查找与二分查找

顺序=一本本翻;二分=字典先按首字母缩范围,再二分定位

5第 5 页 · 一维数组的典型应用--查找

矩阵

前几页我们把数据排成一排就够了——成绩单、温度记录。但只要问题里同时出现两个维度,比如「第几行第几列」「第几周第几节课」,一根线就装不下。该请出矩阵了。

矩阵 = 二维数组
用 m×n 表示 m 行 n 列,元素由两个下标 [i][j] 唯一定位
内存按行连续
C/C++/Java/Python 默认行优先,逻辑上是二维、物理上仍是一段连续空间
边界陷阱
行下标 0~m-1,列下标 0~n-1,越界访问是最高频的运行时错误
典型应用
图像像素、图的邻接矩阵、线性方程组系数、Excel 式表格数据
Excel 表格对应 →二维数组

行号对应第一维下标,列号对应第二维下标,每个单元格就是一个元素

aij, 0i<m, 0j<na_{ij},\ 0 \le i < m,\ 0 \le j < n
6第 6 页 · 矩阵

二维数组的应用

上一页我们看了矩阵的概念——矩阵在程序里怎么存、怎么操作?这就轮到二维数组出场了。

本质是数组的数组
每个元素本身又是一个一维数组,行数与列数共同决定规模
按行连续存储
物理上是线性一段内存,先放满第一行再放第二行
双下标定位
arr[i][j] 中 i 为行号、j 为列号,访问时间复杂度 O(1)
典型应用场景
矩阵运算、图像像素、游戏棋盘、表格数据等
电影院座位表对应 →二维数组结构

几排几座对应 arr[i][j],行号=排、列号=座

addr(a[i][j])=base+(i×n+j)×size\text{addr}(a[i][j]) = \text{base} + (i \times n + j) \times \text{size}
7第 7 页 · 二维数组的应用

字符数组与字符串

前页我们用二维数组处理了矩阵数据。如果只存一行字符——比如一个人的名字——一维数组就够了;但要存一个「可识别长度」的文本,C 语言会把它定义为特殊的字符数组,这就是字符串。

字符数组定义
char 类型元素组成的一维数组,逐个存放 ASCII 字符
字符串本质
C 中没有独立 string 类型,字符串就是末尾带 '\0' 的字符数组
'\0' 终止符
ASCII 码为 0,标记字符串结束;strlen、printf(%s) 都靠它判定边界
两种初始化的本质差异
char s[]="ab" 在栈/全局区可改;char *p="ab" 指向只读常量区,写入会段错误
典型应用
文本读写、命令行参数 argv[]、缓冲区处理、strcpy/strcmp 等库函数的基础
队伍末尾的空椅子对应 →'\0' 终止符

字符依次坐进椅子排队,最后那张空椅就是「队伍到此为止」的标记

sizeof(s)strlen(s)+1\text{sizeof}(s) \geq \text{strlen}(s) + 1
8第 8 页 · 字符数组与字符串

二级C考点解析之数组的定

前几页我们已经用一维数组做冒泡排序、用二维数组处理矩阵。但考试最爱考的,往往不是怎么用,而是定义和引用的几条硬规则——这页我们把这些规矩一次性说透。

定义三要素
类型说明符、数组名、[常量表达式]三者齐全,缺一不可
长度必须是常量
[ ]里只能放正整型常量或常量表达式,不能是变量
数组名是地址常量
代表首元素地址,值不可改,不能对它赋值
下标从0开始
第1个元素是a[0],最后一个是a[n-1],不是a[n]
越界是未定义行为
a[n]语法合法但读写它属于越界,结果不可预料
公寓楼对应 →数组

楼名=数组名(地址常量);房号1~n对应下标0~n-1;楼只有n层就没有n+1号房

9第 9 页 · 二级C考点解析之数组的定

字符串与字符数组

前面我们认识了字符数组——它就是装字符的容器。但 C 里说「字符串」时,有一条隐形约定必须先讲清楚:末尾那个看不见的 '\0'。今天就拆开这条暗线。

字符串本质
以'\0'结尾的一维字符数组,'\0'既是结束标记也是长度的唯一依据
字符串常量
双引号包起的内容,编译器自动补'\0',存于只读数据段
与字符数组差异
字符数组可不含'\0';字符串必须有,否则按字符串处理会越界
常用库函数
strlen求长、strcpy拷贝、strcmp比较、strcat拼接、strstr找子串
典型应用
扫描用户输入、解析命令行参数、文本切割与格式化输出
珍珠项链对应 →字符串

每颗珠子是一个字符,末尾那颗挂坠就是'\0'——没有它就找不到项链的终点

10第 10 页 · 字符串与字符数组

本节要点

  • 数组名是首地址常量,不能被赋值(sizeof/& 除外)
  • 下标从 0 起,遍历边界常见错在 < n 与 <= n-1
  • 冒泡与选择同为 O(n²),差别在交换频率与稳定性
  • 二维数组按行优先连续存储,可映射为一维
  • 字符串以 '\0' 结尾,所有处理函数都依赖此约定
延伸主题:指针与数组的等价关系动态数组(malloc)结构体数组与综合排序
11第 11 页 · 本节要点

课后思考

先合上书想清楚再翻答案——三个问题分别对应回顾、应用、迁移三个层次。

1为什么 C 语言的数组下标要从 0 开始?把 a[i] 写成 *(a+i),这个翻译揭示了什么本质?

参考答案数组名是首元素地址。a[i] = *(a+i) 意味着下标就是偏移量,a+0 自然等于 a 本身。这也解释了 a 与 &a 数值相同、类型不同——一个指向元素,一个指向整个数组。

2冒泡排序和选择法排序的比较次数与交换次数,各自在最好/最坏/平均情况下是多少?数据基本有序时该选谁?

参考答案比较次数:冒泡 O(n²) 最坏 / O(n) 最好(加标志位后);选择始终 O(n²)。交换次数:选择恒为 O(n),冒泡最坏 O(n²)。基本有序时改进版冒泡更优。

3对 char s[10]="hello";,s 与 &s 数值相同,为什么 s+1 步长 1 字节而 &s+1 步长 10 字节?

参考答案s 是 char*(指向首元素),&s 是 char(*)[10](指向整个数组)。数值相同(都指向首字节),但 +1 的偏移量由指针类型决定——这是数组与指针关系最经典的边界题。

12第 12 页 · 课后思考