【问题标题】:Which data structure in C allow me to store lines and append lines easily?C 中的哪种数据结构允许我轻松地存储行和追加行?
【发布时间】:2017-03-19 17:01:09
【问题描述】:

我得到了一个字符串数据列表。 10,20,30是行号

10. string 1
20. string 2
30. string 3

如果用户输入“23 字符串数据”。 23 是用户想要插入的行号。数据应该是这样的

10. string 1
20. string 2
23. string data
30. string 3

如果用户输入“40 个字符串数据”。数据应该是这样的

10. string 1
20. string 2
23. string data
30. string 3
40. string data

我对 C 的数据结构比较陌生。 我应该使用哪种数据结构来有效地存储此类数据?我目前的方向是实现动态数组或链表。但是,以下是我遇到的问题列表。

动态数组的问题:

  1. 使用行号作为索引并创建足够的空间 确保数组长度总是大于或等于最高线 数字。为未使用的索引浪费大量内存。
  2. 打印数据会是个问题。 例如。索引 0-9 没有分配内存。访问它会导致错误。需要想办法知道使用了哪些索引?

链表问题:

  1. 我无法跳转索引(不确定)。如何识别后面是哪一行 另一个并在其间轻松插入线?

【问题讨论】:

  • 计算是关于权衡的。在您的情况下,您需要决定要权衡什么 - 内存与速度。只有您可以根据您的全部要求来回答。除了直接数组和链表,您可能还想查看哈希表。
  • 您可以使用几乎任何您选择的结构。您不能使用“行号”对数组进行索引,因为它们的间距不均匀。这意味着您将进行线性搜索 - 链表可以说更好(至少,在中间插入新行涉及更少的数据移动)。您已经在列表的当前节点和下一个节点中获得了所需的所有信息,以判断新行是否位于这两者之间,然后您可以轻松地进行插入。
  • @kaylum:散列可以让你找到一个特定的行号是否存在,但是在平均散列中找到例如 20 之后的下一个数字是相当困难的,不是吗?
  • @sudo:我不同意你的评价。对于必须线性搜索的结构,哈希表是错误的数据结构。鉴于链表的问题之一是无法“跳转索引”并且提到行相继出现,我认为排序很重要。
  • @sudo:如果列表足够大以保证它,“跳过列表”将是一个很好的改进(20 行可能不需要;可能对 1000 行有帮助;并且会有一个盈亏平衡点介于两者之间)。该描述让人想起(喘气)BASIC 中的行号。

标签: c arrays data-structures linked-list


【解决方案1】:

让我们假设您的要求如下:

  1. 没有强大的实时性。(即它不适用于高频交易或控制机器。)

  2. 它在相对现代的 PC 上运行(RAM 以 GB 为单位,CPU 频率以 GHz 为单位)。特别是它不能在嵌入式系统上运行。

  3. 数据不超过几万行。

然后你可以使用几乎任何你喜欢的数据结构;这对于内存或运行时行为无关紧要。

例如,为了在链表中找到插入点,只需迭代该链表即可。 PC 的速度足以在您完成闪烁之前迭代数万次。

或者只是分配一个包含 100,000 行、每行 80 个字符的数组。没有任何问题。或一百万行。仍然没有问题。或者1000万行,还是没问题的。你明白我的意思吗? (在数组中,您需要一个标记来标记未使用的行。我会使用 struct line { bool used; char text[80]; } 或类似的。您还可以通过仅拥有一个 char *text 成员并动态分配来满足任意长的行 - 并节省内存,或将文本定义为块的链表。)

因此,选择归结为对您来说最容易使用。可能是数组。

【讨论】:

    【解决方案2】:

    我会给出我能想到的两个解决方案,但这个问题可能是开放式的。

    1. 使用哈希表。键是行号。值为(string, pointer to next line's value)。这使得随机和线性访问都快速。 编辑: 插入仍然是O(n) 这个。它只会有助于访问时间,即O(1)。第二种解决方案有O(1) 插入。

    2. 假设您没有大间隔的行号:使用单链表L 来存储字符串。还要创建一个单独的数组P,其中包含一个指向列表中每个k-th 节点的指针。要访问行i,请检查P[floor(i/k)],跳转到它在L 中指向的节点,然后向前跳转i mod k 次以到达您的字符串。因此访问时间为O(k)。插入时间为O(1)n 字符串的空间使用量为 O(n + max{i}/k)

    使这与 C 相关的一件事...当然,没有内置的哈希表!所以#2 可能更容易实现。

    【讨论】:

    • 问题的 cmets 中提到了这一点,但在您的回答中没有提到:在某些时候,您会希望按顺序提取行(否则,有什么意义行号?),这对于哈希表来说并不简单。
    • 我的回答中描述的哈希表在值中包含指向下一行值的指针,因此按顺序遍历很容易。我现在明白这怎么可能不清楚。当我说“指向下一行的值的指针”时,我指的是下一行的元组,它又指向下一行的元组……不仅仅是下一行的字符串。我已经编辑了我的答案以澄清。
    • 如何维护这些指向下一行的指针?我假设您始终保留指向最后一行的指针,但这对插入中间没有多大帮助。要么你遍历链表来搜索插入点(在这种情况下,使用哈希表有什么意义?)或者你希望行号不会被大量间隔并连续猜测什么上一个行号是。
    • 是的,你是对的。我不知何故误读了OP很多次,并没有看到他想快速插入中间,所以我只考虑访问。正在编辑...#1 的访问速度仍然更快,因此值得一提。
    【解决方案3】:

    我知道您正在寻找一种专门的数据结构,但是使用 simple 数据结构并惰性对其进行排序怎么样?您可以将新行追加到动态数组,然后在需要打印它们时对数组进行排序(使用qsort)。

    我认为这会更好,因为打印所有行的频率可能比添加/插入行的频率要低。因此,您应该使添加行便宜(在这种情况下,O(1) 摊销),并且打印可能会更昂贵(在这种情况下,O(n log n) )。这也使您的数据结构保持简单,并让 C 标准库处理复杂的部分。

    您可以通过保留一个标记来跟踪是否所有数据都已经知道已排序,从而使这一点变得更好一些;这样重复打印(或者,假设你正在尝试编写一个 BASIC 解释器,重复运行)也会很便宜。如果您希望行通常按顺序输入,这样的标志也可能会有所帮助;然后添加每一行:

    alreadySorted = alreadySorted && (new_line_number > last_line_number)
    

    我会注意到,如果添加的行重用现有行号,您没有指定会发生什么。如果您希望替换旧行,则可以通过使用 stable sort 来调整此方法,然后遍历行以删除具有重复数字的行,只保留最后一个。

    (如果你想让qsort在这种情况下稳定,而不是只为每一行存储一个字符串,你可以存储一些额外的元数据(任何单调递增的计数器都可以,比如当前时间,或者只是添加行时的总行数)。然后你给qsort的比较函数只需要使用额外的数据来解决重复行号的关系。)

    这种方法的一个缺点是 删除 行要么不会很快,要么不会立即回收内存。但是,您尚未指定是否需要删除行;即使是这样,它也可能是一种罕见的操作(因此,时间效率低一些或空间效率低一些可能是可以接受的)。

    【讨论】:

      【解决方案4】:

      此任务的最佳解决方案是使用字典数据类型。 当然,根据键的性质(行数),您可以通过适当的哈希表执行优化。

      当然,c 库没有字典的实现。但是你可以创建你自己的,基于红黑树。 Cormen 轻松解释了这样的数据结构https://www.amazon.com/Introduction-Algorithms-3rd-MIT-Press/dp/0262033844

      注意:如果你的集合很小或者你很少修改结构,那么你可以使用链表。

      【讨论】:

      • ... C 没有;你对图书馆有什么建议吗?因为临时实施它会是矫枉过正。 (我用谷歌搜索并找到了 glib。)
      • 我认为红黑树(或其他平衡二叉搜索树)会很好,但是“适当的哈希表”会存在按顺序提取数据很烦人的问题。
      【解决方案5】:

      我的建议是使用链表和插入排序在需要的时候插入,

      这是修改后的代码,最初取自 geeksforgeeks.org,

      我没有测试过代码,这只是取自网站的修改代码。

       /* C program for insertion sort on a linked list */
      #include<stdio.h>
      #include<stdlib.h>
      
      /* Link list node */
      struct node
      {
          int lineNumber;
          char *str;
          struct node* next;
      }node;
      
      // Function to insert a given node in a sorted linked list
      void sortedInsert(struct node**, struct node*);
      
      // function to sort a singly linked list using insertion sort
      void insertionSort(struct node **head_ref)
      {
          // Initialize sorted linked list
          struct node *sorted = NULL;
      
          // Traverse the given linked list and insert every
          // node to sorted
          struct node *current = *head_ref;
          while (current != NULL)
          {
              // Store next for next iteration
              struct node *next = current->next;
      
              // insert current in sorted linked list
              sortedInsert(&sorted, current);
      
              // Update current
              current = next;
          }
      
          // Update head_ref to point to sorted linked list
          *head_ref = sorted;
      }
      
      
      /* function to insert a new_node in a list. Note that this
      function expects a pointer to head_ref as this can modify the
      head of the input linked list (similar to push())*/
      void sortedInsert(struct node** head_ref, struct node* new_node)
      {
          struct node* current;
          /* Special case for the head end */
          if (*head_ref == NULL || (*head_ref)->lineNumber >= new_node->lineNumber)
          {
              new_node->next = *head_ref;
              *head_ref = new_node;
          }
          else
          {
              /* Locate the node before the point of insertion */
              current = *head_ref;
              while (current->next!=NULL &&
                  current->next->lineNumber < new_node->lineNumber)
              {
                  current = current->next;
              }
              new_node->next = current->next;
              current->next = new_node;
          }
      }
      
      /* BELOW FUNCTIONS ARE JUST UTILITY TO TEST sortedInsert */
      
      /* Function to print linked list */
      void printList(struct node *head)
      {
          struct node *temp = head;
          while(temp != NULL)
          {
              printf("%d  %s \n", temp->lineNumber,temp->str);
      
              temp = temp->next;
          }
      }
      
      /* A utility function to insert a node at the beginning of linked list */
      void push(struct node** head_ref, int new_data, char *line)
      {
          /* allocate node */
          struct node* new_node = (struct node *)malloc(sizeof(struct node));
          int len = strlen(line)+1;
          /* put in the data */
          new_node->lineNumber = new_data;
      
          new_node->str = malloc(len);
          strcpy(new_node->str,line);
          new_node->str[len]  = '\0';
      
          /* link the old list off the new node */
          new_node->next = (*head_ref);
      
          /* move the head to point to the new node */
          (*head_ref) = new_node;
      }
      
      
      
      // Driver program to test above functions
      int main(int argc,char *argv[])
      {
          struct node *a = NULL;
          push(&a, 5 , "TestLine");
          push(&a, 1 , "SecondTest");
          push(&a, 1 , "SecondTest");
          push(&a, 3 , "SecondTest");
          insertionSort(&a);
      
      
          printf("\nLinked List after sorting \n");
          printList(a);
      
          return 0;
      }
      

      【讨论】:

        【解决方案6】:

        我建议你使用链表。

        // Define your list like this
        typedef struct node {
            int line; // To hold the line number
            char * data;
            struct node * next;
        } node_t;
        
        // To insert
        node_t* insert(node_t *head, const char * data, int line) // n is line from beginning
        {
            // Node to be inserted in given line
            node_t *newNode;
        
            // Allocating Memory
            newNode = malloc(sizeof(node_t));
        
            // Filling the Data to New Node
            newNode->data = malloc(strlen(data)+1); // Allocate memory to store data
            strcpy(newNode->data, data);
            newNode->line = line;
            newNode->next = NULL;
        
            // It might be our First Node in Linked List
            if(head == NULL) {
        
                //Address of New Node Becomes our head
                return (head = newNode);
            } 
        
            // Node Might be inserted At Head
            else if(line == 0) {
                // Joining previous Linked List After new Node
                newNode->next = head;
        
               // Address of New Node Becomes our head
                return (head = newNode);
            } 
        
            // Inserting At the line next to line
            else {
        
                // Pointer to store intermediate address of node
                // To be used in Traversing
                node_t * current = head;
        
                // Go through to insert at Nth line
                while(current != NULL) {
        
                    node_t * next = current->next; //The next Node
        
                    if((line >= current->line && line < next->line) || (line >= current->line && NULL == next->line)) { // Test if we are at some point between current line and next line or if there is no next
        
                        // If we are, point newNode to the next node of current
                        newNode->next = current->next;
        
                        // Now point current towards our New Node
                        current->next = newNode;
        
                        // Return Head as soon as we have inserted our new node
                        return head;
                    }
                    current = next; // Point current to the next node to continue
                }
            }
        }
        

        如果保证行号总是更大,您还可以在每个节点中存储一个指向具有最大行号的节点的指针。这将增加空间,但在 n(0) 时间内达到结果。

        【讨论】:

          猜你喜欢
          • 2015-04-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-26
          • 2012-04-24
          • 2015-04-18
          • 1970-01-01
          • 2012-02-18
          相关资源
          最近更新 更多