计算机内存

在正式介绍数组之前,我们先来了解一下计算机内存。
通俗一点来说,计算机内存就是有一堆0和1组成的一连串的二进制,比如1101010111001。这一连串的数据中每一位我们叫比特位(bit),一个bit表示一个0或者1。但是我们的计算机CPU并不是针对每个bit位进行操作,cpu调度和操作的内存最小单位是字节(byte),一个字节由8bit组成。后续我们涉及到的内存相关的单位也都是基于字节的。
总结: 内存就是由无数个格子组成的连续集合,每个格子中有8个bit位,格子的地址从0开始,依次为0,1,2,3......

计算机内存有一系列的单位转换
1Byte=8Bit
1KB=1024Byte
1MB=1024KB=1024*1024Byte
1GB=1024MB=102410241024Byte
所以说,对于一个4GB的内存条来说,它的内存是0到2^32Byte,说的再简单一点,4GB的内存有2^32个格子,每个格子中有八个bit位。大概示意图如下:

介绍完内存,接下来介绍一下数组是什么,数组就是在内存中申请一片连续的空间用于存放数据。数组在内存中的地址是连续的,它的索引下标从0开始。
假设我们申请了一个长度为5的int数组,每个int类型占四个字节,那么这个数组在内存中的模型是下面这样的,一个格子占4个字节,如果第一个格子的地址为1000,那么第二个格子、第三个格子的地址依次在第一个格子的地址上加上4和8,其地址就是1004、1008。

一维数组的声明

C#中,一维数组的声明格式如下:
数据类型[] 数组名=new 数据类型[元素个数]
如:申请一个长度为10的int数组,写法就是:int[] _array = new int[10]

一维数组的具体算法

首先我们声明一个名为Array的类,里面定义一个int数组,用于存放线性表的数据,再定义一个 _length 字段,表示线性表的实际长度。代码如下:

internal class Array
{
    private int[] _array;//声明数组 用于存放数据
    int _length;//线性表的实际长度
}

这里大家对数组的长度和线性表的长度 _length 要有一个正确的认识,数组只是存放数据元素的一个容器,而实际存放了多少个数据元素,需要有一个单独的变量 _length 来表示。

算法1:查询线性表中是否存在某一个值

查询一个值的算法比较简单,我们只需要从线性表的第一个元素开始,将数组中的每一个值和要查询的值一一比对,如果存在,就返回这个值的下标.如果查到最后一个元素还是找到对应的值,则直接返回-1;

public int Get(int data)
{
    for(int  i = 0; i < _length; i++)
    {
        if(_array[i] == data)
        {
            return i;
        }
    }
    return -1;
}

算法2:修改线性表中某一个值

数组值的修改在C#中也是非常简单的,因为数组在内存中是连续的一块内存,可以通过首地址计算出后续任何一个索引的地址并进行修改。基本格式如下:
数组名[数组下标]=要修改的值。比如我要把一个名为array的数组的第3个元素的元素值改为4,这里注意,数组的索引下标是从0开始的,所以第3个元素的下标为2,语法为:array[2]=4
下面这个方法,index代表要修改的下标,data表示要修改的值

public bool Update(int index, int data)
{
    if(index<0 || index >= _length)
    {
        return false;
    }
    _array[index] = data;
    return true;
}

算法3:插入一个值 在线性表的指定索引处插入一个元素。

数组在内存中因为是连续的一块内存,当我们想在中间某个位置插入一个数据的话,那么这个位置后面的所有元素(包括这个位置的元素)都要向后移动。同时要将线性表的实际长度 _length 加上1。
这里有一点要注意,如果存放线性表的数组已经满了,我们就需要扩容。比如声明了一个长度为10的数组存放线性表元素,但是线性表的实际长度也是10,如果这个时候再插入一个元素的话,数组就放不下了,我们就需要扩容。扩容的意思就是声明一个长度更大的新数组来存放数据,同时把原来数组的元素复制到新数组中。我们这里的扩容后方案是:每一次都将数组的长度增加一倍。

public bool Insert(int index, int data)
{
    if (_array.Length == _length)
    {
        int[] newArray = new int[_length * 2];
        for (int i = 0; i < _length; i++)
        {
            newArray[i] = _array[i];
        }
        _array = newArray;
    }
    for (int i = _length - 1; i >= index; i--)
    {
        _array[i + 1] = _array[i];
    }
    _array[index] = data;
    _length++;
    return true;
}

算法4:删除数组中指定下标的元素

数组中删除元素和新增元素恰好相反,新增元素是把新增元素所在下标后面的所有元素后移动,删除元素则是把要删除下标后面的所有元素向前移动一个位置。

public bool Delete(int index)
{
    for (int i = index; i < _length - 1; i++)
    {
        _array[i] = _array[i + 1];
    }
    _array[_length - 1] = 0;
    _length--;
    return true;
}

当果然这里的示例代码的参数是要删除的数组下标,当然你也可以直接传递一个要删除的数据进来。这样的话,你就需要先找到这个数据对应的下标,然后再执行删除操作。

算法5:输出线性表

使用一个for循环输出数组中的所有元素,这里就直接给大家看一下代码

public void Print()
{
    Console.Write($"总共{_length}个元素:");
    for (int i = 0; i < _length; i++)
    {
        Console.Write(_array[i] + " ");
    }
    Console.WriteLine();
}

示例代码地址

https://gitee.com/jiamingyuanjin_admin/data_struct/blob/master/DataStruct/01Array.cs

总结

通过以上的代码我们可以发现,对于数组的随机访问效率非常高,查询和更新的效率都很快。但是新增元素和删除元素的效率却很低,因为新增和删除都需要大量移动数组中的元素。当数组长度很长的时候,这个操作明显就不合适。不过没关系,后面的链表会帮助我们解决这个新增和删除效率低的问题。