上一篇,咱们讲完算法复杂度,接下来咱们来见一见咱们很是熟悉的朋友--数组。html
咱们平时使用的数组是数据类型,可是数组不只仅是数据类型更是一种基础的数据结构。算法
咱们来看看数组定义:分类连续的内存空间来存储相同类型集合的线性表数据结构。数组
线性表+连续内存+相同类型 着三个特性合并出了数组的必杀技:随机访问。数据结构
那么数组是怎么实现下标随机访问的呢?架构
根据头节点和固定类型具体长度,就能够实现随机访问。工具
search[i]_address = base_address + i*data_type_sizespa
虽然对于访问来说,下标随机访问的复杂度为O(1)3d
可是对于插入和删除来讲日子就不是那么好过了。调试
由于数组要保证内存的连续性这个特性,因此插入和删除都是比较低效的,咱们具体来看看插入和删除。htm
插入:咱们在任意节点前插入,那么后面的元素必须后移一位来保证连续性。
假设咱们有一个数组为 int[n] =[1,2,3,4,5,......,n]
咱们在左侧顶头插入呢?后面的所有都移动,因此算个复杂度为O(n),咱们上面一篇提到过的最差状况时间复杂度
与此对应的是末尾插入,完美,啥都不用干,这就不用说了就是 最好状况时间复杂度。
那么若是在中间任意位置出现呢?多种状况,因此就是平均状况时间复杂度
概括一下:
数组左侧顶头插入元素,最坏状况时间复杂度 O(n)
数组末尾追加插入元素:最好状况时间复杂度 O(1)
数组中间状况插入元素,平均状况时间复杂度 O(n)
插入平均时间复杂度
咱们这里来验证一下,数组下标随机访问的平均时间复杂度为何是O(n):
对于一个 int[n] = [1,2,3,4,5,......,n]
我从头依次进行插入,那么对应的移动数组次数是
n
(n-1)
(n-2)
...
1
0
对于平均时间复杂度来说,能够插入的空先后两端2个+中间n-1,即 n+1 种状况
这里的每一个插入点发生的状况几率是同样的都是 1 / (n+1)
因此,平均时间复杂度为移动次数*发生的几率
n * [1/(n+1)]
(n-1) * [1/(n+1)]
...
1 * [1/(n+1)]
0 * [1/(n+1)]
简化一下:(0+1+2+3...n) / (n+1) => [(n-1)/2] * n + n / (n+1)
算法复杂度去除系数、常量、低阶,这里的平均状况时间复杂度是O(n)
平常开发中,若是咱们遇到有序数组,那咱们必须在插入的同时,后移后面的全部位置。
可是若是数组对排序不敏感,那么咱们的插入能够在后面追加,这样避免了移动数组,复杂度就是O(1)
删除:若是咱们删除数组中的元素,为了保持数组的连续性,依然须要搬运数组元素。
因此删除操做和插入操做的最好、最坏、平均复杂度是对应的。
其实咱们也能够加一个删除标记,在空闲的时候进行删除重排序。
对于以上这段C程序来讲,上面这段代码,咱们在书写的时候,没有仔细检查,使得<写成了<=,这样就会产生了越界问题。
前提条件:
一、由于不一样的CPU架构不一样的编译器会有不一样的内存分配策略:从高地址向低地址增加,或者从低地址向高地址增加。也就是大小端问题。
二、首先压栈的是 i ,以后是数组arr。因此 i 的内存地址比arr中的元素高,而且 i 和arr中的元素相邻,而且类型相同,也就是子节是对齐的。
因此当 arr [ 3 ] 这个地址越界访问到了 相邻的同类型的 i 这个变量的内存地址。arr[3] =0,其实也就是 i = 0,好吧,又从头开始了,无限循环。
那么为何咱们平时使用的语言即使是越界也会程序异常终止。其实这是编译器作的工做,编译器不一样,内存申请方式也不一样。
咱们平时的编译器已经帮咱们作好了代码检查等工做。因此避免了越界的状况。
首先 [] ,就是咱们声明简单的数组,由于数组是连续的线性数据结构,因此不少操做微软又给作了封装。
Array ,就是微软对数组进行的封装类。下面又进一步衍生出了动态数组。
ArrayList,就是动态数组,里面封装了数组不少的操做。尤为是动态扩容。泛型以后,又出了泛型列表。
List<T>是动态数组的泛型版本,避免了频繁的装箱拆箱,效率较高,也是咱们平时使用最频繁的一种。
其实微软已经开源了.NET Framework 源码,详细可查看此地址。
还有一种方式,可使用远程调试源码,远程下载源码到本地,进行源码调试:
工具 -》 调试 -》勾选 启用源代码单步调试
固然速度上确定会比本地代码慢。
以上就是今天的内容。