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

时间:2008-12-25 00:56:13

标签: arrays data-structures

在编程时,我还没有看到一个数组比其他形式更适合存储信息的实例。我确实认为编程语言中增加的“特性”已经改进了,并且取而代之。我现在看到他们没有被替换,而是被赋予新的生命,可以这么说。

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

这不是为什么我们为什么要从计算机的角度使用数组,而是为什么我们要从编程的角度使用数组(一个细微的差别)。计算机对阵列的作用不是问题所在。

4 个答案:

答案 0 :(得分:761)

及时回顾课程。虽然我们今天在我们的花哨管理语言中没有考虑过这些问题,但它们是建立在相同的基础之上的,所以让我们来看看如何用C语言管理内存。

在我深入研究之前,快速解释一下术语“指针”的含义。指针只是一个“指向”内存中某个位置的变量。它不包含此内存区域的实际值,它包含内存地址。将一块内存想象成一个邮箱。指针将是该邮箱的地址。

在C中,数组只是一个带偏移量的指针,偏移量指定内存的查看距离。这提供了O(1)访问时间。

  MyArray   [5]
     ^       ^
  Pointer  Offset

所有其他数据结构都建立在此基础之上,或者不使用相邻的内存进行存储,导致随机访问查找时间不佳(尽管不使用顺序内存还有其他好处)。

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

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================

在数组中,我们知道每个元素在内存中彼此相邻。 C数组(此处称为MyArray)只是指向第一个元素的指针:

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
   ^
MyArray

如果我们想要查找MyArray [4],在内部就可以这样访问:

   0     1     2     3     4 
=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
                           ^
MyArray + 4 ---------------/
(Pointer + Offset)

因为我们可以通过向指针添加偏移量来直接访问数组中的任何元素,所以无论数组的大小如何,我们都可以在相同的时间内查找任何元素。这意味着获取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

当数据插入二叉树时,它使用几个规则来决定新节点的放置位置。基本概念是,如果新值大于父值,则将其插入左侧,如果值较低,则将其插入右侧。

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

         ==========
         |   100  |         
         ==========
        /          \ 
  =========       =========
  |  200  |       |   50  |
  =========       =========
                  /       \
            =========    =========
            |   75  |    |   25  |
            =========    =========

当搜索二叉树的值为75时,由于这种结构,我们只需要访问3个节点(O(log N)):

  • 75不到100?看看Right Node
  • 75大于50吗?看看左节点
  • 有75!

即使我们的树中有5个节点,我们也不需要查看其余两个节点,因为我们知道他们(和他们的孩子)不可能包含我们正在寻找的值。这给了我们一个搜索时间,在最坏的情况下意味着我们必须访问每个节点,但在最好的情况下,我们只需要访问一小部分节点。

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

这是对内存中数据结构的高度概述,跳过了很多细节,但希望它能说明数组与其他数据结构相比的优缺点。

答案 1 :(得分:73)

对于O(1)随机访问,无法打败。

答案 2 :(得分:21)

并非所有程序都执行相同操作或在同一硬件上运行。

这通常是为什么存在各种语言功能的答案。数组是核心计算机科学概念。用列表/矩阵/向量/任何高级数据结构替换数组会严重影响性能,并且在许多系统中都是不切实际的。有许多情况下,由于有问题的程序,应该使用这些“高级”数据收集对象之一。

在业务编程中(我们大多数人都这样做),我们可以针对相对强大的硬件。在这些情况下,使用C#中的List或Java中的Vector是正确的选择,因为这些结构允许开发人员更快地完成目标,从而使这类软件更具特色。

在编写嵌入式软件或操作系统时,阵列通常是更好的选择。虽然数组提供的功能较少,但占用的RAM较少,编译器可以更有效地优化代码,以便查找数组。

我相信我会遗漏这些案件的一些好处,但我希望你明白这一点。

答案 3 :(得分:0)

查看数组优点的一种方法是查看数组的O(1)访问能力在哪里,因此大写:

  1. 在应用程序的查找表中(用于访问某些分类响应的静态数组)

  2. 记忆(已经计算出复杂的函数结果,因此你不再计算函数值,比如log x)

  3. 需要图像处理的高速计算机视觉应用程序(https://en.wikipedia.org/wiki/Lookup_table#Lookup_tables_in_image_processing