【问题标题】:Why would I use a data structure(e.g. hash table) instead of an array?为什么我要使用数据结构(例如哈希表)而不是数组?
【发布时间】:2020-11-17 00:41:04
【问题描述】:

这可能听起来很愚蠢,但我正在尝试制作一个高效的程序(时间/内存方面)并研究哈希表我已经看到它基本上是一个链表数组,当表中的所有点都开始填满时被采取。这开始需要内存和时间,因为它们需要 malloc 来存储数据和时间来搜索元素,这与数组不同;这都是因为数组不是动态的并且有限制

所以我只是想知道,为什么我不能制作一个 200 亿长的数组,这样我就可以使用索引在 O(1) 中访问并且不需要 malloc?就像,主要是一个巨大的数组,仅此而已

我需要将文本保存为一堆行,所以我知道每一行在哪里(第 1 行将是第一行,呃),我似乎没有必要使用哈希表,但问题是我没有不知道它将有多少行,所以如果我制作一个 50 的数组可能还不够,我想知道使用列表/哈希表/其他一些结构或只是一个 char 数组的数组是否更好

【问题讨论】:

  • 如果您已经有了要查找的项目的索引,则不需要哈希表。假设您没有索引。
  • 在没有索引的情况下使用哈希表,因为您使用字符串之类的东西作为键。
  • 另外,如果使用的索引在整个数组中分布稀疏,那么分配一个包含 200 亿个元素的数组是非常浪费内存的。
  • 另外,由于缓存利用率低,一个巨大的稀疏数组可能会比哈希表
  • 每个文本都应该是一个动态分配的字符串。哈希表或数组的元素将是指向这些字符串的指针。

标签: arrays c data-structures hashtable


【解决方案1】:

如果您已经知道要搜索的每个元素的索引,则使用数组很有用,可以减少所有操作的时间。

在内存部分,因为数组有固定的维度,如果你知道你必须保存的总数据量,它就会全部下降;因为如果“只是为了确定”你创建一个包含 200 亿个索引的数组,然后最终只使用前 100 个索引,那么与使用动态内存相比,这是一个非常糟糕的选择,动态内存只能在需要更多空间的情况下自行扩展。

【讨论】:

    【解决方案2】:

    这可能听起来很愚蠢,但我正在尝试制作一个高效的程序(时间/内存方面)

    高效的程序来做什么?你从来没有真正说出你想要做什么。

    研究哈希表我发现它基本上是一个链表数组

    这是一个常见的实现,但它并没有说明你为什么首先要使用哈希表。

    当您根据非数字键(即字符串)搜索记录时,您会使用哈希表。您将该键输入到一个散列函数中,该函数会输出一个整数值,然后使用该值对表进行索引。所以如果f("foo") 吐出3,那就是你用来存储数据的表索引,键为"foo"

    没有一个实用的散列函数是完美的,不同的字符串会产生相同的散列值,称为冲突。使用链表是解决冲突的一种方法,其他方法是计算表中的二级索引或只是将返回的索引加 1。

    相对于线性或二进制搜索,从键计算哈希,与 O(n) 的线性搜索时间和二进制搜索相比,时间复杂度为 O(1) O(log2 n) 的时间。权衡是您的表没有以任何方式排序 - 线性遍历将出现随机排序。

    编辑

    来自评论:

    我需要将文本保存为一堆行,所以我知道每一行在哪里(第 1 行将是第一行,呃),我似乎没有必要使用哈希表,但问题是我没有不知道它将有多少行,所以如果我制作一个 50 的数组可能还不够,我想知道使用列表/哈希表/其他一些结构还是只是一个 char 数组的数组更好(在帖子中添加也)

    如果您只需要存储一个字符串序列,您可以动态分配一个数组,然后根据需要扩展该数组。假设所有行都是已知的固定长度,您可以执行以下操作(将文件读入内存,将内容转储到标准输出):

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAX_LENGTH 80
    
    size_t read_arr( FILE *in, char (**arr)[MAX_LENGTH+1] ) 
    {
      size_t size = 1;
      size_t read = 0;
    
      *arr = malloc( sizeof **arr * size );
      if ( !*arr )
      {
        return 0;
      }
    
      char buf[MAX_LENGTH+1];
      while( fgets( buf, sizeof buf, in ) )
      {
        if ( read == size )
        {
          char (*tmp)[MAX_LENGTH+1] = realloc( *arr, sizeof **arr * (size * 2) );
          if ( tmp )
          {
            *arr = tmp;
            size *= 2;
          }
          else
          {
            fprintf( stderr, "Unable to extend array past %zu entries, returning what we have so far.\n", read );
            return read;
          }
        }
        strcpy( (*arr)[read++], buf );
      }
      return read;
    }
    
    int main( int argc, char **argv )
    {
      if ( argc < 2 )
      {
        fprintf( stderr, "USAGE: %s <file name>\n", argv[0] );
        return EXIT_FAILURE;
      }
    
      FILE *in = fopen( argv[1], "r" );
      if ( !in )
      {
        fprintf( stderr, "File %s not found\n", argv[0] );
        return EXIT_FAILURE;
      }
    
      char (*arr)[MAX_LENGTH+1];
      size_t n = read_arr( in, &arr );
    
      for ( size_t i = 0; i < n; i++ )
        printf( "%s", arr[i] );
    
      free( arr );
      return EXIT_SUCCESS;
    }
    

    realloc 是一项相对昂贵的操作,因此您不想对文件中的每一行都执行此操作。每次将数组加倍可以最大限度地减少调用次数,尽管权衡是内部碎片的可能性(例如,需要 256 行来存储 129 行)。但平均而言,这应该不是问题。

    你能告诉我char (**arr)[MAX_LENGTH+1]是什么吗,我从未见过这种结构;是二维数组吗?

    是的,我想我应该解释一下。

    T (*a)[N];
    

    a 声明为一个指针,指向T 的N 元素数组。 T [M][N] 类型的表达式将“衰减”为 T (*)[N] 类型(不是 T **)。

    我想动态分配足够的空间来存储 T [N] 类型的 M 个对象。所以我们从常见的成语开始

    P *p = malloc( sizeof *p * M );
    

    sizeof *p 等价于sizeof (P),因此我们分配了足够的空间来存储P 类型的M 个对象。现在我们用 array 类型 T [N] 替换类型 P,这给了我们

    T (*p)[N] = malloc( sizeof *p * M );
    

    在这种情况下,sizeof *p 等价于 sizeof (T [N]),因此我们分配了足够的空间来存储 T 的 M 个 N 元素数组。

    由于a[i] 被定义为*(a + i),以下是正确的:

    (*p)[i] == (*(p + 0))[i] == (p[0])[i] == p[0][i]
    

    所以我们可以像任何其他二维数组一样索引到p

    所以在上面的main 函数中,我将arr 声明为指向MAX_LENGTH+1 数组char 的指针。因为我希望read_arr 更新存储在arr 本身中的值(分配内存的地址),所以我需要将指针 传递给arr。请记住,如果您希望函数更新其参数之一,则必须将 指针 传递给该参数1,即使该参数已经是指针类型。如果arr的类型是char (*)[MAX_LENGTH+1],那么&amp;arr的类型就是char (**)[MAX_LENGTH+1],或者“指向MAX_LENGTH+1的指针-char的元素数组”。

    再一次,这假设文件中的所有行都接近相同的长度,并且它们都小于某个已知的最大长度。如果您有一个文件,其中行的长度差别很大,或者 99% 的行长度为 20,而一两行的长度为 200,那么您将想做其他事情。


    1. 数组很奇怪,但在这种情况下我们处理的不是数组类型,而是指针类型。

    【讨论】:

    • 我需要将文本保存为一堆行,所以我知道每一行在哪里(第 1 行将是第一行,duh),我似乎没有必要使用哈希表,但是问题是我不知道它将有多少行所以如果我制作一个 50 的数组可能还不够,我想知道使用列表/哈希表/其他一些结构或只是一个字符数组更好数组(也在帖子中添加)
    • 你能告诉我char (**arr)[MAX_LENGTH+1]是什么吗,我从未见过这种结构;是二维数组吗?
    【解决方案3】:

    哈希表是一种数据结构,允许在给定键的情况下快速检索数据项。

    使用键的散列来索引散列表。它将键映射到用于索引表的整数上。散列函数通常非常快。

    哈希表通常比可能的键数小得多。因此,散列函数可以为多个不同的键生成相同的索引。这称为碰撞。为了处理冲突,哈希表条目通常有一个链表(但平衡二叉树也可以)。该列表存储了哈希表条目的所有冲突。

    因此,给定一个键,哈希函数确定表中的索引,然后在该条目的列表中搜索实际键,然后检索与该键关联的数据。如您所见,这比搜索链表要快得多,并且比每个可能的键都有一个条目的数组使用的内存要少得多。

    维护表和列表有一些开销,但主要的好处是快速的数据检索。

    设计哈希函数本身就是一门科学。

    注意:所以哈希表由两种数据结构组成:哈希表本身及其大小和哈希函数,哈希表条目可以是列表、排序列表、树或其他任何东西。

    【讨论】:

      【解决方案4】:

      你可以。如果你有那么大的记忆力。但从统计的角度来看,哈希表也一样好。他们在平均上有 O(1) 的查找。这可能很难理解如何以及为什么,所以我的建议是尝试实现你自己的只是为了学习。此外,在好的操作系统上,malloc 调用不应该很慢。

      【讨论】:

        【解决方案5】:

        其实很简单。

        您不能使用 200 亿长的数组的原因是它需要 80 GB 的 RAM。如果是 64 位,则为 160 GB。

        你也不能用字符串索引它们。 myArray["hello"] = "world"; 永远不会工作。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-10-31
          • 1970-01-01
          • 2010-09-28
          • 2019-08-05
          • 2015-03-05
          • 2011-07-30
          • 2017-12-26
          • 2017-08-27
          相关资源
          最近更新 更多