数据结构与算法(二):数组
数组是数据结构中最简单,基础的结构,并且也是我们刚接触代码就会学习到的数据结构。那么我们对数组真的足够了解了吗?
定义
数组是一种线性表数据结构,它用一段连续内存空间,来存储一组具有相同类型的数据。
对于上面数组的定义有几个关键点:线性表,连续内存空间,相同类型数据。
线性表,顾名思义,就是数据像一条线一样串联的数据结构,结构中的每条数据最多只有前后两个方向,链表/栈/队列都属于线性表数据结构。而树/图等数据结构因为数据不是简单的前后关系,所以不属于线性表结构。
连续内存空间和相同类型数据则是数组最重要的特征,它帮助我们提升了根据数组下标随机访问数据的速度。可以直接通过寻址公式(arry[i]_address=baseAddress+i*typeSize)访问数组下标所在位置的数据。但有利也有弊,这个特征让数组的插入和删除操作变得非常低效,比如想在数组中插入或者删除一个数据,为了保证数据连续性,就需要做大量的数据搬移工作。
时间复杂度
随机插入数据的时间复杂度:假设要在n长度的数组中第k位插入一条数据,需要将数据插入到第k位置,并将第k位原来的数据向后迁移,这样第k-n位的数据都要向后移动一位。当在数组的末尾插入一条数据时,因为不需要数据移动,所以它的时间复杂度是O(1),而在数组的开头插入数据时,则是所有的数据都需要向后挪动一位,所以它的时间复杂度是O(n),因为我们在每个位置插入数据的概率是一样的,所以平均情况的时间复杂度是(1+2+3+...+n)/n=O(n)。
删除数据与插入数据的操作类似,所以它们的时间复杂度相同,但是在一些特殊场景下,我们并不一定非要追求数组中数据的连续性,当要删除数据时,我们仅将要删除的数据标记为已删除,但不挪动位置,当数组内存空间不足时,我们再触发一次删除操作(将标记已删除数据删除),这样就大大减少了删除操作导致的数据搬移(JVM 标记清除垃圾回收算法的核心思想)。
查询数据的时间复杂度:分为两种情况,第一种是根据数组下标查询数据,因为我们根据寻址公式可以计算出数组下标数据的内存地址,所以时间复杂度为O(1)。第二种情况是根据数据内容查询,这种情况下,如果数组是有序的,通过二分查找时间复杂度是O(logn),如果数组是无序的,只能通过遍历,那么查询的时间复杂度就是O(n)。
访问越界
在C语言中,只要不是访问受限的内存,所有的内存空间都是可以自由访问的,如果数组下标越界,就会访问到不属于数组的内存空间上的数据,这样可能会导致严重的系统错误。而在高级语言中,当数组下标越界时,也会在运行时抛出数组下标访问越界的异常,可能使程序进入不可用的状态。所以我们需要格外注意可能造成数组下标访问越界的场景,例如for循环。
变长数组
我们知道,数组的长度在声明后是无法修改的,但可以通过复制的方式实现数组的扩容,从而实现一个变长数组。
// 全局变量,大小为10的数组array,长度len,下标i。 int array[] = new int[10]; int len = 10; int i = 0; // 往数组中添加一个元素 void add(int element) { if (i >= len) { // 数组空间不够了 // 重新申请一个2倍大小的数组空间 int new_array[] = new int[len*2]; // 把原来array数组中的数据依次copy到new_array for (int j = 0; j < len; ++j) { new_array[j] = array[j]; } // new_array复制给array,array现在大小就是2倍len了 array = new_array; len = 2 * len; } // 将element放到下标为i的位置,下标i加一 array[i] = element; ++i; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?