指针是如此令人困惑:在C中使用单链表堆栈

时间:2018-12-11 09:44:20

标签: c stack

我是一名学生,遵循一本名为 C语言中的数据结构基础的书,使用C使用单链表实现堆栈。

问题是,我希望这段代码的结果为<5,3>和<9,6> 但是控制台仅显示<5,3>和<9,3>。

实际上,我不确定我使用的指针是否是问题的根源。

有人可以向我解释为什么我得到这个结果吗?

完整代码如下:

typedef struct {
int edge[1]; //edge array for saving 2 node's data
} element;

typedef struct stack *stackPointer;
typedef struct stack {
element data;
stackPointer link;
} stack;
stackPointer top[MAX_STACKS];

void initStack();
void push(int i, element item);
element pop(int i);

int top_counter = -1;

int main()
{
    int x, y;

    initStack();

    element *data = malloc(sizeof(element));


    top_counter++;
    data->edge[0] = 9;
    data->edge[1] = 6;

    push(top_counter, *data);


    top_counter++;
    data->edge[0] = 5;
    data->edge[1] = 3;

    push(top_counter, *data);

    *data = pop(top_counter);
    x = data->edge[0];
    y = data->edge[1];
    printf("<%d,%d>\n", x, y);
    top_counter--;

    *data = pop(top_counter);
    x = data->edge[0];
    y = data->edge[1];
    printf("<%d,%d>\n", x, y);
    top_counter--;

    // result of this code
    // <5, 3>
    // <9, 3>
    // WHY?!?!?!?!??!

}


void initStack() {
    for (int i = 0; i < MAX_STACKS; i++) {
        top[i] = NULL;
    }

}

void push(int i, element item) {
    // push item to top[i]
    stackPointer temp;
    temp = (stackPointer)malloc(sizeof(*temp));
    temp->data = item;
    temp->link = top[i];
    top[i] = temp;

}

element pop(int i) {
    stackPointer temp = top[i];
    element item;
    if (!temp) printf("\nStack Empty\n");
    item = temp->data;
    top[i] = temp->link;
    free(temp);
    return item;
}

2 个答案:

答案 0 :(得分:2)

正如Jaybird在对该问题的评论中指出的那样,问题在于元素类型仅包含一个int,而不是两个。

但是,该代码对于一个示例来说过于复杂,因为它不是实现单个堆栈,而是实现MAX_STACKS堆栈。这就是为什么代码看起来如此奇怪的原因,而不是您在现实生活中可能看到的东西。

考虑以下反例:

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

struct node {
    struct node *next;
    int          data[2];
};

struct node *new_node(const int data0, const int data1)
{
    struct node *n;

    n = malloc(sizeof *n);
    if (!n) {
        fprintf(stderr, "new_node(): Out of memory.\n");
        exit(EXIT_FAILURE);
    }

    n->next = NULL;
    n->data[0] = data0;
    n->data[1] = data1;

    return n;
}

void free_node(struct node *n)
{
    if (n) {
        /* Poison the node, so we can more easily
           detect use-after-free bugs. */
        n->next    = NULL;
        n->data[0] = -1;
        n->data[1] = -1;

        free(n);
    }
}

要将单个节点推送(放在)链表中,

void push_node(struct node **listptr, struct node *n)
{
    if (!listptr) {
        fprintf(stderr, "push_node(): No linked list (listptr is NULL).\n");
        exit(EXIT_FAILURE);
    } else
    if (!n) {
        fprintf(stderr, "push_node(): No node to push (n is NULL).\n");
        exit(EXIT_FAILURE);
    }

    n->next = *listptr;
    *listptr = n;
}

要从链接列表中弹出第一个节点,

struct node *pop_node(struct node **listptr)
{
    if (!listptr) {
        fprintf(stderr, "pop_node(): No linked list specified (listptr is NULL).\n");
        exit(EXIT_FAILURE);

    } else
    if (!*listptr) {
        /* Linked list is empty, return NULL. */
        return NULL;

    } else {
        struct node *n;

        n = *listptr;
        *listptr = n->next;
        n->next = NULL;

        return n;
    }
}

对于调试,我们可以使用一个例程来打印堆栈的内容:

void show_list(struct node *first, FILE *out)
{
    if (out) {
        fputs("[", out);
        while (first) {
           fprintf(out, " <%d,%d>", first->data[0], first->data[1]);
           first = first->next;
        }
        fputs(" ]\n", out);
    }
}

要测试,我们写一个小的main()

int main(void)
{
    struct node *odds = NULL;
    struct node *evens = NULL;
    struct node *n;

    /* Push <1,3> and <5,7> to odds. */
    push_node(&odds, new_node(1, 3));
    push_node(&odds, new_node(5, 7));

    /* Push <2,2>, <4,2>, and <6,8> to evens. */
    push_node(&evens, new_node(2,2));
    push_node(&evens, new_node(4,2));
    push_node(&evens, new_node(6,8));

    /* Push <3,3> to odds. */
    push_node(&odds, new_node(3,3));

    /* Show the two stacks. */
    printf("odds stack after the pushes: ");
    show_list(odds, stdout);
    printf("evens stack after the pushes: ");
    show_list(evens, stdout);

    /* Pop each node off from the odds stack,
       and push them into the evens stack. */
    while ((n = pop_node(&odds)))
        push_node(&evens, n);

    /* Show the stacks again. */
    printf("odds stack after the while loop: ");
    show_list(odds, stdout);
    printf("evens stack after the while loop: ");
    show_list(evens, stdout);

    /* Pop each node from evens stack, and display it. */
    while ((n = pop_node(&evens))) {
        printf("<%d, %d>\n", n->data[0], n->data[1]);
        free_node(n);
    }

    printf("All done.\n");
    return EXIT_SUCCESS;
}

如果您编译并运行以上命令,它将输出

odds stack after the pushes: [ <3,3> <5,7> <1,3> ]
evens stack after the pushes: [ <6,8> <4,2> <2,2> ]
odds stack after the while loop: [ ]
evens stack after the while loop: [ <1,3> <5,7> <3,3> <6,8> <4,2> <2,2> ]
<1, 3>
<5, 7>
<3, 3>
<6, 8>
<4, 2>
<2, 2>
All done.

一些语法细节:

  • push和pop操作会修改指向列表中第一个节点的指针。如果仅传递指针本身,则该修改将对调用者不可见。这就是为什么将指针**listptr的指针用作参数,将*listptr用作访问列表中第一个节点的指针的原因。

  • if (out)是指针时,
  • if (out != NULL)等效于out

  • while ((n = pop_node(&odds))) { ... }等效于while ((n = pop_node(&odds)) != NULL) { ... }。编写循环的另一种方法是

        while (1) {
    
            n = pop_node(&odds);
            if (n == NULL)
                break;
    
            ...
    
        }
    

    即首先分配n,然后将其与NULL进行比较。这是一种非常常见的速记形式。

    • if (!listptr)等同于if (listptr == NULL)

    记住这一区别的一种方法是大声读“ not”或“ no”的not运算符!。因此,if (!listptr)读为“如果没有listptr”。

请考虑当您从一个堆栈中弹出项目并将它们推到另一个堆栈时会发生什么。他们的顺序被颠倒了。使Tower of Hanoi / Tower of Brahma / Lucas' Tower如此有趣的原因是,它可以与三个堆栈配合使用。


在此示例中,根本没有“堆栈”抽象数据类型。用于推入和弹出操作的堆栈句柄只是指向第一项的指针。如果要使用链接列表的相同句柄来实现堆栈和队列,则可以使用

typedef struct {
    struct node *newest; /* or first in list */
    struct node *oldest; /* or last in list */
} storq;
#define  STORQ_INIT  { NULL, NULL }

以便您可以使用声明空的堆栈或队列

storq  stuff = STORQ_INIT;

无需调用某些storq_init(&stuff);函数即可将其初始化为可以使用的已知(空)状态。

上述内容并非完全对称,因为它允许恒定时间( O (1)时间复杂度)storq_push()(推送或前置),storq_pop()和{ {1}}(类似于推,但位于队列/堆栈的另一端),而storq_unshift()(类似于pop,但位于队列/堆栈的另一端)将是“慢速”( O (( N )时间复杂度,其中 N 是队列/堆栈中的节点数)。

为完整起见,省略错误检查的操作为

storq_shift()

要了解它们是如何工作的,我建议绘制图形,每个节点用一个椭圆表示,箭头表示void storq_push(storq *sq, struct node *n) { n->next = sq->newest; sq->newest = n; if (!sq->oldest) sq->oldest = n; } void storq_unshift(storq *sq, struct node *n) { n->next = NULL; if (sq->oldest) { sq->oldest->next = n; } else { sq->newest = n; sq->oldest = n; } } struct node *storq_pop(storq *sq) { if (sq->newest) { struct node *n; n = sq->newest; if (n->next) { sq->newest = n->next; } else { sq->newest = NULL; sq->oldest = NULL; } n->next = NULL; return n; } else { return NULL; } } 成员指向的位置。 next句柄本身是一个带有两个箭头的框,一个箭头指向列表中的第一个/最新成员,另一个指向列表中的最后一个/最早的成员。

对于每种操作(推,松开,弹出),需要考虑以下三种情况:当列表为空时,列表只有一项时以及列表中有很多项时。如果找出全部九个,您将了解上述功能的工作原理,以及为什么它们要这样做。

答案 1 :(得分:0)

int edge[1]更改为int edge[2],它应该可以工作。