【问题标题】:C: Queue and Memory Limit ExcedeedC:超出队列和内存限制
【发布时间】:2018-03-09 08:10:46
【问题描述】:

我是C的初学者,为了练习,决定参加一个小型的在线比赛。

在当前问题中,我被要求编写一个带有 struct 的队列,该队列响应命令 PushBackPopFront

输入包括

  • 一个数字n (n <= 1000000) 表示命令输入的数量。
  • n 行。每行包含两个整数ab
    • a2 用于执行 PopFront,在这种情况下,b 是预期的弹出值。
    • a3 for PushBack,在这种情况下,b 是要排队的值。

如果我们尝试从空队列中弹出,则返回值是-1

任务是在程序执行过程中任何PushBack返回的值是否与预期值一致,则在执行最后一条命令后打印YESNO

我实现了这个版本,但在提交我的答案后,在线评委给出了Maximum-Limit-Excedeed(在27的最后一次测试中)。

我正在阅读它,这个问题可能与其中一些有关:

  1. 使用过大的数组或数据结构。
  2. 程序中存在无限(或太大)递归。
  3. 指针使用不正确(诊断为 MLE)。

我不确定是什么问题。在我看来,在某些测试中,添加节点的数量远远大于删除(这意味着 1. 发生在我的代码中),这反过来又导致 @987654346 中的 while 循环@ 太大(2. 也会发生)。我无法发现指针的使用是否不正确。

我的问题是:

  • 我在这里做错了什么?
  • 我应该怎么做才能解决这个问题?

代码:

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

//===================
//Definitions:

typedef int Item;

typedef struct node
{
    Item item;
    struct node * next;
} Node;

typedef struct queue
{
    Node * front;
    Node * rear;
    long counter;
} Queue;

//===================
//Function Prototypes:

void InitializeQueue(Queue * pq);
bool PushBack(Queue * pq, Item item);
int PopFront(Queue * pq);
void EmptyQueue(Queue * pq);


int main(void)
{
    Queue line;
    long n, i;
    int command, expected, received;
    bool check = true;

    scanf("%ld", &n);

    InitializeQueue(&line);

    i = 0;
    while (i < n)
    {
        scanf("%d %d", &command, &expected);

        switch (command)
        {
            case 2:
                received = PopFront(&line);
                if (received != expected)
                    check = false;
                break;
            case 3:
                PushBack(&line, expected);
                break;

        }
        i++;
    }

    if (check == true)
        printf("YES\n");
    else
        printf("NO\n");

    // free memory used by all nodes
    EmptyQueue(&line);

    return 0;

}




void InitializeQueue(Queue * pq)
{
    pq->front = NULL;
    pq->rear = NULL;
    pq->counter = 0;
}

bool PushBack(Queue * pq, Item item)
{
    Node * pnode;

    //Create node
    pnode = (Node *)malloc(sizeof(Node));

    if (pnode == NULL)
    {
        fputs("Impossible to allocate memory", stderr);
        return false;
    }
    else
    {
        pnode->item = item;
        pnode->next = NULL;
    }

    //Connect to Queue
    if (pq->front == NULL)
    {
        pq->front = pnode;
        pq->rear = pnode;
    }
    else
    {
        pq->rear->next = pnode;
        pq->rear = pnode;
    }

    pq->counter++;

    return true;
}


int PopFront(Queue * pq)
{
    int popped;
    Node * temp;

    temp = pq->front;

    if (pq->counter == 0)
        return -1;
    else
    {
        popped = pq->front->item;
        pq->front = pq->front->next;
        free(temp);
        pq->counter--;
        return popped;
    }
}




void EmptyQueue(Queue * pq)
{
    int dummy;

    while (pq->counter != 0)
        dummy = PopFront(pq);
}

谢谢。

【问题讨论】:

  • 触发问题的确切输入是什么?
  • @MichaelWalz 未显示):。查看测试列表时,前 26 个给出了ok,平均执行时间为 2 毫秒,(我猜)内存使用量为 380.00Kb。最后的测试给出了MLE,执行时间为 98ms,内存使用量为 5.14Mb。
  • 我说你管理这种问题不需要管理链表。你不需要节点。管理指向内存区域的指针并将其复制到“基指针”中就足够了。当你压入一个值时,你在内存中设置一个值并增加内存指针,当你弹出值时,你减少内存指针并读取值。当内存指针等于“基指针”时,堆栈中没有任何内容。
  • 在 PushBack() 中,看起来您正在覆盖值。您需要转到列表的头部/尾部,然后添加新分配的节点。
  • @MayurK,被覆盖的“值”是用于操作列表的指针。我不相信这有什么问题。

标签: c data-structures queue


【解决方案1】:

我认为该代码在功能上实际上没有任何问题, 尽管它可以通过一些格式改进 :-)

提一件事:

任务是检查执行PopFront后的返回值是否与预期一致。如果是,则打印YES。打印NO,否则。

我会将此视为每个 PopFront 的要求。您似乎正在存储故障条件,最后只打印YESNO 一次

我建议先解决这个问题,然后看看在线法官的反馈。


这一切都忽略了一个事实,即除非您可以重现问题,否则调试代码实际上是相当困难的。如果您无法从在线比赛中获得数据集,则可能值得生成自己的(大型)数据集,看看是否可以让您的代码失败。

一旦出现可重复的故障,调试就会变得非常容易。


虽然不太可能,但您可能(正如mch 在评论中指出的那样)与有限的内存发生冲突。我认为这不太可能,因为您自己的 cmets 表明最后只使用了 5meg 的空间,这并不繁重。但是,如果 的情况,那可能是因为每个整数都有一个指针的开销。

如果你想调查那条路,你可以稍微调整结构如下(去掉不必要的counter):

#define ITEMS_PER_NODE 1000

typedef struct node {
    Item item[ITEMS_PER_NODE];  // array of items.
    int startIndex;             // start index (one to pop from).
    int nextIndex;              // next index (one to push at).
    struct node *next;          // next node.
} Node;

typedef struct queue {
    Node *front;                // first multi-item node.
    Node *rear;                 // last multi-item node.
} Queue;

这个想法是为每个节点存储 许多 个项目,这样next 指针的开销就大大减少了(每 个项目一个指针,而不是每个项目一个)。

队列操作的代码会变得稍微复杂一些,但仍然可以理解。首先,一个用于创建新节点的辅助函数,准备将数据添加到:

// Helper to allocate a new node and prep it for appending.
// Returns node or NULL (and prints error) if out of memory.

Node *GetNewNode(void) {
    Node *pnode = malloc (sizeof(Node));
    if (pnode == NULL)
        fputs ("Impossible to allocate memory", stderr);
    else
        pnode->startIndex = pnode->nextIndex = 0;
    return pnode;
}

接下来,大部分不变的队列初始化:

void InitializeQueue (Queue *pq) {
    pq->front = pq->rear = NULL;
}

pushback 稍微复杂一些,如果队列为空或当前最后一个节点已到达末尾,它首先添加一个新的多项目节点。无论是否发生,都会将一个项目添加到最终节点:

bool PushBack (Queue *pq, Item item) {
    // Default to adding to rear node (assuming space for now).

    Node *pnode = pq->rear;

    // Make sure queue has space at end for new item.

    if (pq->front == NULL) {
        // Handle empty queue first, add single node.

        if ((pnode = GetNewNode()) == NULL)
            return false;
        pq->front = pq->rear = pnode;

    } else if (pq->rear->nextItem == ITEMS_PER_NODE) {
        // Handle new node needed in non-empty queue, add to rear of queue.

        if ((pnode = GetNewNode()) == NULL)
            return false;
        pq->rear->next = pnode;
        pq->rear = pnode;
    }

    // Guaranteed space in (possibly new) rear node now, just add item.

    pq->rear->item[pq->rear->nextIndex++] = item;
}

弹出也有点复杂 - 它获取返回的值,然后如果第一个节点现在已经用尽,则删除它。如果它删除的节点是唯一的节点,这也可能需要清除队列:

int PopFront (Queue * pq) {
    // Capture empty queue.

    if (pq->first == NULL)
        return -1;

    // Get value to pop.

    Node *currFront = pq->front;
    int valuePopped = currFront->item[currFront->startIndex++];

    // Detect current node now empty, delete it.

    if (currFront->startItem == currFront->endIndex) {
        // Detect last node in queue, just free and empty entire queue.

        if (currFront == pq->rear) {
            free (currFront);
            pq->front = pq->rear = NULL;
        } else {
            // Otherwise remove front node, leaving others.

            pq->front = currFront->next;
            free (currFront);
        }
    }

    // Regardless of queue manipulation, return popped value.

    return valuePopped;
}

除了我们清除节点而不是项目之外,清空队列基本上没有改变:

void EmptyQueue (Queue * pq) {
    // Can empty node at a time rather than item at a time.

    while (pq->front != NULL) {
        Node *currentFront = pq->front;
        pq->front = pq->front->next;
        free (currentFront);
    }
}

【讨论】:

  • 哎哟。我的意思是,如果在程序执行期间任何PushBack 返回的值与预期值不一致,它会在执行最后一个命令后打印YESNO。很抱歉造成混乱。
  • @Jazz 在这种情况下,一旦发现不匹配,您应该打印NO,然后退出程序。
  • @user3386109 感谢您的建议。如果我在与PushBack 的输入编号mm &lt; n 发生不匹配的情况下退出程序,则无法输入带有命令的剩余n - m 行。我会想办法在第一次不匹配时停止创建节点,但无论如何都要处理以下n - m 输入,看看会发生什么。
【解决方案2】:

我认为最好在我在这里发布的代码中使用更简单的方法。

以下行中的代码与比赛所需的输入/输出不匹配,但包含解决问题的实用且简单的方法:简单的堆栈管理器! (如果我理解正确的话)。

#include <stdio.h>
#include <malloc.h>

int * stack;
int * base;
int cnt;

/* To emulate input file */
struct stFile {
    int n;
    struct stCmd {
        int a;
        int b;
    } cmd[200]; // 200 is an arbitrary value.
} fdata = {
    20,
    {
        {2,0},
        {2,0},
        {2,0},
        {3,35},
        {2,0},
        {3,4},
        {2,0},
        {2,0},
        {2,0},
        {3,12},
        {3,15},
        {3,8},{3,18},
        {2,0},
        {2,0},
        {3,111},
        {2,0},
        {2,0},
        {2,0},
        {2,0},
        {3,8},{3,18},{3,8},{3,18},{3,8},{3,18},{3,8},{3,18},{3,8},{3,18},
        {3,11},{3,13},{3,11},{3,11},{3,11},{3,11},{3,11},{3,11},
        {3,11},{3,13},{3,11},{3,11},{3,11},{3,11},{3,11},{3,11},
        {2,0},
        {2,0},
        {2,0},
        {2,0},
        {2,0},
        {2,0},
        {2,0},
        {2,0},
        {0,0}
    }
};

int push(int item)
{
    if (cnt) {
        *stack = item;
        stack++;
        cnt--;
        return 0;
    } else {
        return 1;
    }
}

int pop(int *empty)
{
    if (stack!=base) {
        stack--;
        cnt++;
        if (empty)
            *empty = 0;
    } else {
        if (empty)
            *empty = 1;
    }

    return *stack;
}

int main(void)
{
    int i=0,e=0;

    cnt = fdata.n;
    base = stack = malloc(cnt*sizeof(int));

    if (!base) {
        puts("Not enough memory!");
        return 1;
    }

    while(fdata.cmd[i].a!=0) {
        switch(fdata.cmd[i].a) {
        case 2:
            printf("popping ...: %d ",pop(&e));
            printf("empty: %d\n",e);
            break;

        case 3:
            e = push(fdata.cmd[i].b);
            printf("pushing ...: %d %s\n",fdata.cmd[i].b,(e)?"not pushed":"pushed");
            break;

        default:
            break;

        };

        i++;
    }

    if (base)
        free(base);

    return 0;
}

【讨论】:

    猜你喜欢
    • 2014-02-09
    • 2018-02-24
    • 2013-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多