Arrays 为什么我们使用数组而不是其他数据结构?

Arrays 为什么我们使用数组而不是其他数据结构?,arrays,data-structures,Arrays,Data Structures,在我编程时,我还没有看到一个实例,其中数组比另一种形式的数组更适合存储信息。我确实认为编程语言中添加的“特性”在此基础上有所改进,并由此取代了它们。我现在看到他们没有被取代,而是被赋予了新的生命 那么,基本上,使用数组有什么意义呢 这不是为什么我们从计算机的角度使用数组,而是为什么我们从编程的角度使用数组(一个微妙的区别)。计算机对阵列的处理不是问题的关键。对于O(1)随机访问,这是不可击败的。是时候回去上课了。虽然我们在当今的高级管理语言中没有考虑到这些东西,但它们是建立在同一基础上的,所以我

在我编程时,我还没有看到一个实例,其中数组比另一种形式的数组更适合存储信息。我确实认为编程语言中添加的“特性”在此基础上有所改进,并由此取代了它们。我现在看到他们没有被取代,而是被赋予了新的生命

那么,基本上,使用数组有什么意义呢


这不是为什么我们从计算机的角度使用数组,而是为什么我们从编程的角度使用数组(一个微妙的区别)。计算机对阵列的处理不是问题的关键。

对于O(1)随机访问,这是不可击败的。

是时候回去上课了。虽然我们在当今的高级管理语言中没有考虑到这些东西,但它们是建立在同一基础上的,所以我们来看看C.< /P>内存是如何管理的。 在我开始之前,请快速解释一下“指针”一词的含义。指针只是“指向”内存中某个位置的变量。它不包含这个内存区域的实际值,它包含它的内存地址。把一块内存想象成一个邮箱。指针将是指向该邮箱的地址

在C语言中,数组只是一个带有偏移量的指针,偏移量指定要在内存中查找的距离。这提供了访问时间

  MyArray   [5]
     ^       ^
  Pointer  Offset
所有其他数据结构要么以此为基础构建,要么不使用相邻内存进行存储,导致随机访问查找时间差(尽管不使用顺序内存还有其他好处)

例如,假设我们有一个包含6个数字(6,4,2,3,1,5)的数组,在内存中它看起来是这样的:

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
   0     1     2     3     4 
=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
                           ^
MyArray + 4 ---------------/
(Pointer + Offset)
         ==========
         |   100  |         
         ==========
        /          \ 
  =========       =========
  |  200  |       |   50  |
  =========       =========
                  /       \
            =========    =========
            |   75  |    |   25  |
            =========    =========
在数组中,我们知道每个元素在内存中相邻。C数组(此处称为
MyArray
)只是指向第一个元素的指针:

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
   ^
MyArray
如果我们想查找
MyArray[4]
,在内部,它的访问方式如下:

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
   0     1     2     3     4 
=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
                           ^
MyArray + 4 ---------------/
(Pointer + Offset)
         ==========
         |   100  |         
         ==========
        /          \ 
  =========       =========
  |  200  |       |   50  |
  =========       =========
                  /       \
            =========    =========
            |   75  |    |   25  |
            =========    =========
因为我们可以通过向指针添加偏移量直接访问数组中的任何元素,所以无论数组大小如何,我们都可以在相同的时间内查找任何元素。这意味着获取
MyArray[1000]
所需的时间与获取
MyArray[5]
所需的时间相同

另一种数据结构是链表。这是指针的线性列表,每个指针指向下一个节点

========    ========    ========    ========    ========
| Data |    | Data |    | Data |    | Data |    | Data |
|      | -> |      | -> |      | -> |      | -> |      | 
|  P1  |    |  P2  |    |  P3  |    |  P4  |    |  P5  |        
========    ========    ========    ========    ========

P(X) stands for Pointer to next node.
请注意,我将每个“节点”制作成它自己的块。这是因为它们不能保证(而且很可能不会)在内存中相邻

如果我想访问P3,我不能直接访问它,因为我不知道它在内存中的什么位置。我只知道根(P1)在哪里,因此我必须从P1开始,并跟随每个指针指向所需的节点

这是一个O(N)查找时间(查找成本随着每个元素的添加而增加)。与P4相比,P1000要贵得多

更高级的数据结构,如哈希表、堆栈和队列,都可以在内部使用一个数组(或多个数组),而链表和二叉树通常使用节点和指针

您可能想知道为什么任何人都会使用需要线性遍历来查找值的数据结构,而不仅仅是使用数组,但它们有自己的用途

再来一次我们的阵法。这一次,我想找到保存值“5”的数组元素

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
   ^     ^     ^     ^     ^   FOUND!
在这种情况下,我不知道要将什么偏移量添加到指针上才能找到它,所以我必须从0开始,然后逐步找到它。这意味着我必须进行6次检查

因此,在数组中搜索值被认为是O(N)。随着阵列变大,搜索成本也会增加

还记得上面我说过,有时使用非顺序数据结构会有好处吗?搜索数据是这些优点之一,二叉树就是最好的例子之一

二叉树是一种类似于链表的数据结构,但是每个节点都可以链接到两个子节点,而不是链接到单个节点

         ==========
         |  Root  |         
         ==========
        /          \ 
  =========       =========
  | Child |       | Child |
  =========       =========
                  /       \
            =========    =========
            | Child |    | Child |
            =========    =========

 Assume that each connector is really a Pointer
当数据插入到二叉树中时,它使用若干规则来决定新节点的位置。基本概念是,如果新值大于父值,则将其插入左侧;如果值较低,则将其插入右侧

这意味着二叉树中的值可以如下所示:

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
   0     1     2     3     4 
=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
                           ^
MyArray + 4 ---------------/
(Pointer + Offset)
         ==========
         |   100  |         
         ==========
        /          \ 
  =========       =========
  |  200  |       |   50  |
  =========       =========
                  /       \
            =========    =========
            |   75  |    |   25  |
            =========    =========
在二叉树中搜索值75时,我们只需要访问3个节点(O(logn)),因为这种结构:

  • 75比100小吗?看右边的节点
  • 75大于50吗?看左边的节点
  • 有75个
即使我们的树中有5个节点,我们也不需要查看其余的两个,因为我们知道它们(及其子节点)不可能包含我们要查找的值。这给了我们一个搜索时间,在最坏的情况下意味着我们必须访问每个节点,但在最好的情况下,我们只需要访问一小部分节点

这就是数组被击败的地方,它们提供了一个线性的O(N)搜索时间,尽管有O(1)个访问时间


这是对内存中数据结构的一个令人难以置信的高级概述,跳过了很多细节,但希望它能说明阵列与其他数据结构相比的优势和劣势。

并非所有程序都做相同的事情或在同一硬件上运行。

这就是为什么存在各种语言特征的原因。阵列是计算机科学的核心概念。用列表/矩阵/向量/任何高级数据结构替换阵列都会严重影响性能,在许多系统中根本不可行。在许多情况下,由于所讨论的程序,应该使用这些“高级”数据收集对象之一

在商业编程中(我们大多数人都是这样做的),我们可以瞄准相对独立的硬件