Pushing to a stack containing ONLY unique values in C - c

I've implemented a stack with pointers, that works like it's suppose too. Now, I need it push to the stack, without it pushing a duplicate. For example, if I push '2' into the stack, pushing another '2' will still result with only one '2' in the stack because it already exists.
Below is how I went about trying to create the new push function. I know that I'm suppose to traverse the stack and check it for the element I'm adding, but I guess I'm doing that wrong? Can anyone help me out?
typedef struct Node {
void *content;
struct Node *next;
} Node;
typedef struct Stack {
Node *head;
int count;
} Stack;
void push(Stack *stack, void *newElem) {
Node *newNode = (Node*) malloc(sizeof(Node));
if (stack->count > 0) {
int i;
for (i = 0, newNode = stack->head; i < stack->count; i++, newNode =
newNode->next) {
if (newNode->content == newElem) return;
}
} else {
newNode->next = stack->head;
newNode->content = newElem;
stack->head = newNode;
stack->count++;
}
}

if (newNode->content == newElem)
You are comparing two pointers. I guess you want to check whether their contents are equal:
#include <string.h>
if (memcmp(newNode->content, newElem, size) == 0)
The value size may be indicated by the caller. In your case, it should be sizeof(int).
Moreover, once you have traversed the stack, you don't add the element to your data structure.

The problem is that if your stack is non-empty, and you don't find the element already in the stack, you don't do anything. You need to get rid of the else keyword and make that code unconditional. Then, you allocate space for the new Node before you know if you need it or not, and, even worse, overwrite the newly allocated pointer with your iteration over the stack to see if you need to push it or not. So move the malloc down after the } ending the if

You already have a working
void push(Stack *stack, void *newElem);
right?
So, why not write a new function
int push_unique(Stack *stack, void *newElem) {
if (find_value(stack, newElem) != NULL) {
return 1; // indicate a collision
}
push(stack, newElem); // re-use old function
return 0; // indicate success
}
Now you've reduced the problem to writing
Node *find_value(Stack *stack, void *value);
... can you do that?

I'm not sure you realized it, but your proposed implementation is performing a linear search over a linked list. If you're pushing 2,000 elements on a stack with an average of 2 duplicates of each element value, that's 2,000 searches of a linked list averaging between 500-750 links(it depends on when, IE:what order, the duplicates are presented to the search function in. This requires 1 million+ compares. Not pretty.
A MUCH more efficient duplicate detection in find_value() above could use a hash table, with search time O(1), or a tree, with search time O(log N). The former if you know how many values you're potentially pushing onto the stack, and the latter if the number is unknown, like when receiving data from a socket in real-time. (if the former you could implement your stack in an array instead of a much slower, and more verbose linked-list)
In either case, to properly maintain the hashtable, your pop() function would need to be paired with a hashtable hashpop() function, which would remove the matching value from the hashtable.
With a Hashtable, your stack could just point to the element's value sitting in it's hash location - returned from find_value(). With a self-balancing tree however, the location of the node, and thus the element value, would be changing all the time, so you'd need to store the element's value in the stack, and the tree. Unless you're writing in a very tight memory environment, the performance the 2nd data structure would afford would be well worth the modest cost in memory.

Related

Linked stacks and queues

Came across Linked Stacks and Queues in a book(and it is not stack/queue implementation using linked list).
it says that stack/queue can be represented sequentially if we had only one stack/queue.However, when several stacks/queues coexisted ,then there is no efficient way to represent them sequentially.
Below is the code given
#define MAX_STACKS 10 //maximum number of stacks;
typedef struct {
int key;
//other fields.
}element;
typedef struct stack *stackpointer;
typedef struct {
element data;
stackpointer link;
}stack;
stackpointer top[MAX_STACKS];
void push(int i ,element item) {
stackpointer temp;
malloc(temp,sizeof(*temp));
temp->data = item;
temp->link = top[i];
top[i] = temp;
}
Am newbie to data structures. Can I get the brief explantion of above concept i.e Linked Stacks/Queues.
So I checked out your book and I kind of understand what your problem is.
Such a representation proved efficient if we had only one stack or one queue. However, when several stacks and queues co−exist, there was noefficient way to represent them sequentially
So, by sequential, you must understand that it means using arrays to represent stacks and not a linked list. Now just assume that you have a matrix comprising of 10 arrays to represent each of size 100, and you push some data into each. Say you push only a few elements in each stack, what happens is that you end up wasting a lot of data as there are a 1000 elements in the matrix. This problem was there while using a single array but it becomes more pronounced when you have multiple arrays for multiple stacks.
Now as you might have understood, using the linked list representation of a stack uses as much memory as needed, with only a slight overhead of keeping track of the next element, in this case stackpointer link.
stackpointer top[MAX_STACKS]
So what we have done here is create an array of type stackpointer to keep track of the top position of each individual stack. So now whenever the user wishes to enter an element, they must pass the index(int i) as well as the data (element item).
void push(int i ,element item)
{
stackpointer temp;
malloc(temp,sizeof(*temp));
temp->data = item;
temp->link = top[i];
top[i] = temp;
}
So what we do is create a temp variable to store our data, which will now become the top of our stack but before doing so we must point it to the previous top of stack, (that is done in line 5) and in line 6, we just point the top[i] to temp.
However, you might want to correct your code with this
stackpointer temp = (stackpointer)malloc(sizeof(element));
If you have doubts, on malloc, just refer to this.
If you have a doubt, let me know and I will clarify anything you need.

Why do I override my struct? Implementing queues but it doesn'work

I need to program queue structs in C for an assignment. The nodes have a pointer to the next node and the value (so far, so normal). But, as I need to use it with threads, I shall malloc all of the capacity on the heap.
However, the nodes and queues are defined like this:
//Element of a queue
struct queue_node {
// Pointer to next element in the queue
struct queue_node* next;
// Value/Data of the queue element
int value;
};
// Queue data structure
struct queue {
// Head of the linked list
struct queue_node* head;
// Max capacity of the queue
int capacity;
// Current size of the queue. size <= capacity, always
int size;
};
The problem I got with this, was to push elements, as I don't have any information on which is the start or the end of the allocated memory. So I decided to make the head node always the first one in the space, so I could work with the capacity
And I programmed the basic functions like this:
struct queue* queue_new(int capacity){
struct queue_node* head1 = malloc(sizeof(struct queue_node)*capacity);
struct queue* ret = malloc(sizeof(struct queue));
/*
struct queue_node head2;
head2.next = NULL;
(*head1) = head2;
*/
(*head1).next = NULL;
(*ret).head = head1;
(*ret).size = 0;
(*ret).capacity = capacity;
return ret;
}
void queue_delete(struct queue* queue){
free((*queue).head);
free(queue);
}
So. But, I get trouble, when I want to push something into the queue. Obviously, the first thing to do, is to fill the head. And that seems to work. But appending an element to the head node doesn't:
int queue_push_back(struct queue* queue, int value){
if((*queue).size >= (*queue).capacity){
return -1;
}else if((*queue).size == 0){
(*(*queue).head).value = value;
(*queue).size++;
return (*queue).size;
} else{
if((*(*queue).head).next == NULL){ ////((*queue).head + sizeof(struct queue_node))
printf("Intern queue size 1: %d\n", (*queue).size);
(*(*queue).head).next = ((*queue).head + sizeof(struct queue_node));
printf("Error here?\n");
(*(*(*queue).head).next).value = value;
printf("Error here 2?\n");
(*(*(*queue).head).next).next = NULL;
printf("Error here 3?\n");
printf("Intern queue size 2: %d\n", (*queue).size);
printf("Intern queue capacity: %d\n", (*queue).capacity);
(*queue).size++;
return (*queue).size;
}
}
I skipped the code for the common case, because this doesn't even works. For some reason, if I want to push a second element, it overrides my queue struct. And I have no idea why.
Can someone help me and tell me, where I did something wrong?
You seem to be trying to mix two different approaches to the problem:
Maintaining the queue as an array, and
Maintaining the queue as a linked list.
Allocating space for the full complement of nodes in one block, keeping the queue head at the beginning of the block, and indeed having a fixed queue capacity in the first place, are all characteristic of array-like use. On the other hand, having element node structures with 'next' pointers is the form of a linked list.
If you manage the queue as an array, then the next pointers are redundant, and indeed they are constricting if you actually use them. Instead, you can always identify and navigate to a node by means of the pointer to the start of the block of nodes and a node index: my_queue_ptr->head[node_num]. You can also identify the next available node based on the queue's current size: my_queue_ptr->head[my_queue_ptr->size].
But then whenever you dequeue a node, you have to move all the other nodes -- or at least their data -- one position forward. If you move the whole nodes, then you screw up their next pointers, because the thing at each pointed-to location is different, and has different significance, from what was there before.
On the other hand, if you manage the queue as a linked list then it does not make sense to allocate all the nodes in one block. It would instead be conventional to allocate a new node for each value you enqueue, and to deallocate the node of each value that you dequeue. In that case you will modify the queue's head pointer upon enqueueing the first element and upon dequeuing any element. If you do not maintain a pointer to the current tail as well (as presently you don't), then every time you enqueue an element you'll need to walk the list to find the tail node, and append the new node there.
Update:
In the event that you nevertheless proceed with what you describe, the only way forward that makes sense to me is to adopt the array-based approach, and ignore altogether the linked-list aspects of the data structures. The queue_new() and queue_delete() functions you presented are reasonable for this. Your queue_push_back(), on the other hand, isn't even internally consistent, much less appropriate for the array-like approach.
Before, I go into details, however, I want to point out that your code is unnecessarily hard to read. Surely you have been introduced to the -> operator by this point; it is specifically designed to ease use of pointers to structures, and especially to easy use of chains of pointers to structures. Here is the first part of your queue_push_back() function, rewritten to use ->; the part presented is exactly equivalent to the corresponding part of your original:
int queue_push_back(struct queue* queue, int value){
if (queue->size >= queue->capacity) {
return -1;
} else if (queue->size == 0) {
queue->head->value = value;
queue->size++;
return queue->size;
} else {
// ...
}
That's much easier to read, at least for me. Now, with fewer distractions, it's easier to see that the only attribute of the head node that you set is its value. You do not set its next pointer. If you've understood my recommendation then you'll recognize that that's in fact just fine -- you'll be using indices into the array to access elements, not links, which would be at best redundant.
But now consider what you try to do when you push the next element. The very first thing is to test the value of the head node's next pointer, which you never set. Undefined behavior results. Now you could manage the links and use them (though you'd need something more sophisticated than what you now have if you want to support queues with capacity greater than 2), but as I said, my recommendation is to ignore the links altogether.
Having already verified that the queue has room for another element, you can access that element directly as queue->head[queue->size]:
queue->head[queue->size].value = value;
queue->size++;
And Lo, that's all there is to it. But wait, it gets better! If you look carefully, you'll see that there's very little difference between the case of the first node and the case of the others. In fact, the difference is purely syntactic; the first node (when queue->size == 0) would be equally well served by the code I've just presented; it doesn't need to be a special case at all:
int queue_push_back(struct queue* queue, int value){
if (queue->size >= queue->capacity) {
return -1;
} else {
queue->head[queue->size].value = value;
return ++queue->size;
}
// That's all, folks!
}

Why create heap when creating a linked list when we can simply do this?

I'm studying linked lists from this lesson.
The writer (and all other coders on every single tutorial) goes through creating node type pointer variables, then allocates memory to them using typecasting and malloc. It seems kinda unnecessary to me (Offourse I know I'm missing something), why can't we implement the same using this?
struct node
{
int data;
struct node *next;
};
int main()
{
struct node head;
struct node second;
struct node third;
head.data = 1;
head.next = &second;
second.data = 2;
second.next = &third;
third.data = 3;
third.next = NULL;
getchar();
return 0;
}
I've created nodes and the next pointers points towards the addresses of the next nodes...
Let's say you create a variable of type node called my_node:
struct node my_node;
You can access its members as my_node.data and my_node.next because it is not a pointer. Your code, however, will only be able to create 3 nodes. Let's say you have a loop that asks the user for a number and stores that number in the linked list, stopping only when the user types in 0. You don't know when the user will type in 0, so you have to have a way of creating variables while the program is running. "Creating a variable" at runtime is called dynamic memory allocation and is done by calling malloc, which always returns a pointer. Don't forget to free the dynamically allocated data after it is no longer needed, to do so call the free function with the pointer returned by malloc. The tutorial you mentioned is just explaining the fundamental concepts of linked lists, in an actual program you're not going to limit yourself to a fixed number of nodes but will instead make the linked list resizable depending on information you only have at runtime (unless a fixed-sized linked list is all you need).
Edit:
"Creating a variable at runtime" was just a highly simplified way of explaining the need for pointers. When you call malloc, it allocates memory on the heap and gives you an address, which you must store in a pointer.
int var = 5;
int * ptr = &var;
In this case, ptr is a variable (it was declared in all its glory) that holds the address of another variable, and so it is called a pointer. Now consider an excerpt from the tutorial you mentioned:
struct node* head = NULL;
head = (struct node*)malloc(sizeof(struct node));
In this case, the variable head will point to data allocated on the heap at runtime.
If you keep allocating nodes on the heap and assigning the returned address to the next member of the last node in the linked list, you will be able to iterate over the linked list simply by writing pointer_to_node = pointer_to_node->next. Example:
struct node * my_node = head; // my_node points to the first node in the linked list
while (true)
{
printf("%d\n", my_node->data); // print the data of the node we're iterating over
my_node = my_node->next; // advance the my_node pointer to the next node
if (my_node->next == NULL) // let's assume that the 'next' member of the last node is always set to NULL
{
printf("%d\n", my_node->data);
break;
}
}
You can, of course, insert an element into any position of the linked list, not just at the end as I mentioned above. Note though that the only node you ever have a name for is head, all the others are accessed through pointers because you can't possibly name all nodes your program will ever have a hold of.
When you declare 'struct node xyz;' in a function, it exists only so long as that function exists. If you add it to a linked list and then exit the function, that object no longer exists, but the linked list still has a reference to it. On the other hand, if you allocate it from the heap and add it to the linked list, it will still exist until it is removed from the linked list and deleted.
This mechanism allows an arbitrary number of nodes to be created at various times throughout your program and inserted into the linked list. The method you show above only allows a fixed number of specific items to be placed in the list for a short duration. You can do that, but it serves little purpose, since you could have just accessed the items directly outside the list.
Of course you can do like that. but how far ? how many nodes are you going to create ? We use linkedlists when we don't know how many entries we need when we create the list. So how can you create nodes ? How much ?
That's why we use malloc() (or new nodes).
But what if you had a file containing an unknown number of entries, and you needed to iterate over them, adding each one to the linked list? Think about how you might do that without malloc.
You would have a loop, and in each iteration you need to create a completely new "instance" of a node, different to all the other nodes. If you just had a bunch of locals, each loop iteration they would still be the same locals.
Your code and approach is correct as long as you know the number of nodes that you need in advance. In many cases, though, the number of nodes depends on user input and is not known in advance.
You definitely have to decide between C and C++, because typecasting and malloc belong in C only. Your C++ linked list code won't be doing typecasting nor using malloc precisely because it's not C code, but C++ code.
Say you are writing an application such as a text editor. The writer of the application has no idea how big a file a user in the future may want to edit.
Making the editor always use a large amount of memory is not helpful in multi-tasking environments, especially one with a large number of users.
With malloc() an editing application can take additional amounts of memory from the heap as required, with different processes using different amounts of memory, without large amounts of memory being wasted.
You can, and you can exploit this technique to create cute code like this, to use the stack as a malloc in a way:
The code below should be safe enough assuming there are no tail optimizations enabled.
#include <stdio.h>
typedef struct node_t {
struct node_t *next;
int cur;
int n;
} node_t;
void factorial(node_t *state, void (*then)(node_t *))
{
node_t tmp;
if (state->n <= 1) {
then(state);
} else {
tmp.next = state;
tmp.cur = state->n * state->cur;
tmp.n = state->n - 1;
printf("down: %x %d %d.\n", tmp);
factorial(&tmp, then);
printf("up: %x %d %d.\n", tmp);
}
}
void andThen(node_t *result)
{
while (result != (node_t *)0) {
printf("printing: %x %d %d.\n", *result);
result = result->next;
}
}
int main(int argc, char **argv)
{
node_t initial_state;
node_t *result_state;
initial_state.next = (node_t *)0;
initial_state.n = 6; // factorial of
initial_state.cur = 1; // identity for factorial
factorial(&initial_state, andThen);
}
result:
$ ./fact
down: 28ff34 6 5.
down: 28ff04 30 4.
down: 28fed4 120 3.
down: 28fea4 360 2.
down: 28fe74 720 1.
printing: 28fe74 720 1.
printing: 28fea4 360 2.
printing: 28fed4 120 3.
printing: 28ff04 30 4.
printing: 28ff34 6 5.
printing: 0 1 6.
up: 28fe74 720 1.
up: 28fea4 360 2.
up: 28fed4 120 3.
up: 28ff04 30 4.
up: 28ff34 6 5.
factorial works differently than usual because we can't return the result to caller because the caller will invalidate it with any single stack operation. a single function call will destroy the result, so instead, we must pass it to another function that will have its own frame on top of the current result, which will not invalidate the arbitrary number of stack frames it's sitting on top of that hold our nodes.
I imagine there are many ways for this to break other than tail call optimizations, but it's really elegant when it doesn't, because the links are guaranteed to be fairly cache local, since they are fairly close to each other, and there is no malloc/free needed for arbitrary sized consecutive allocations, since everything is cleaned as soon as returns happen.
Lets think you are making an Application like CHROME web browser, then you wanna create link between tabs created by user at run time which can only possible if you use Dynamic Memory Allocation.
That's why we use new, malloc() etc to apply dynamic memory allocation.
☺:).

Obtain node number n of a linked list

If I have a linked list:
first node -----> second node ------> third node ---> ?
Can I show the third node value ( for example ) without use a classic list-linear-searching algorithm?
My attempt of getting the n'th node:
struct node* indexof( struct node* head, int i )
{
int offset = (int)((char*)head->next - (char*)head);
return ((struct node*)((char*)head + offset * i));
}
That depends on your exact linked list implementation, but in general, no. You will have to traverse the list in order to access the nth element.
This is a characteristic of linked lists, in the sense that the normal tricks you could use for computing an offset into an array or other sequence-like structure will not work, as your individual list elements are not guaranteed to be laid out in memory in any sensible way, so you are forced to follow the next pointers in-order to retrieve the third element.
You could consider other data structures that provide constant-time indexed access into your linked list.
Sounds like you've picked the wrong data structure. If you want to go straight to nth then you should use an array.
Failing that, what's so bad about going through in linear fashion? Would have to be called a lot on a very long linked list to be causing performance problem.
One of the purposes of a linked list is to be able to easily add and delete nodes with little cost.
You can renounce that capability and use an array of payload pointers, but then it is no longer a linked list (what would the purpose be of having a pointer to the next node when the same node can be obtained trivially by arithmetic increment?).
E.g. instead of
struct
{
struct node *next;
void *payload;
...
} node;
node *root = NULL;
and allocate space for no nodes, you can have
typedef struct
{
void *payload;
...
} node;
node *vector = NULL;
size_t vectorsize = 0;
and allocate space for as many nodes as initially required, then using realloc to extend the list when needed, and memmove to remove nodes by shifting back the nodes beyond the deleted one. This incurs a clear performance loss when adding or removing nodes. On the other hand, the n-th node is just vector[n].
I repeat, this is no longer a linked list: it may be that whatever you're needing this for, it can be better accomplished with an array of pointers instead than a linked list.
Which reminds me, you'd do well to explain why you need the direct-addressing ability ("State the problem, don't ask how to implement the solution"): it may also well be that what you need is neither an array nor a linked list, but, who knows?, maybe a ring buffer, a stack, a hill, or a binary tree.
In some implementations you can even deploy two bonded structures, e.g. you might use a (doubly?) linked list in a first phase with lots of insertions and deletions especially of recently inserted data; then you build, and switch to, a pointer array for a second phase where you need direct addressing driven by the node number (use the array as "cache" of list node addresses):
for (listsize = 0, scan = root; scan; scan = scan->next)
listsize++;
if (NULL == (vector = (node *)malloc(listsize * sizeof(node))))
{
// out of memory
return EXIT_FAILURE;
}
for (listsize = 0, scan = root; scan; scan = scan->next)
vector[listsize++] = scan;
// Now vector[i]->payload is the payload of the i-th node

Why queues and stacks are declared as pointers?

I'm studying Data Structures, and I'm not getting why stacks and queues need to be declared like:
struct stack *Stack;
(forget about the struct syntax)
I mean, why it is always declared as a pointer?
They are not always declared like that!
In general, declaring a variable as a pointer is useful for later allocating it dynamically. This can be due to a couple of reasons:
The variable is too big for the program stack
You want to return that variable from a function
In your case, let's think of two different implementations of stack:
struct stack
{
void *stuff[10000];
int size;
};
This is a terrible implementation, but assuming you have one like this, then you'd most probably not want to put it on the program stack.
Alternatively, if you have:
struct stack
{
void **stuff;
int size;
int mem_size;
};
You dynamically change the size of stuff anyway, so there is absolutely no harm in declaring a variable of type struct stack on the program stack, i.e. like this:
struct stack stack;
Unless, you'd want to return it from a function. For example:
struct stack *make_stack(int initial_size)
{
struct stack *s;
s = malloc(sizeof(*s));
if (s == NULL)
goto exit_no_mem;
if (initial_size == 0)
initial_size = 1;
s->stuff = malloc(initial_size * sizeof(*s->stuff));
if (s->stuff == NULL)
goto exit_no_stuff_mem;
s->size = 0;
s->mem_size = initial_size;
return s;
exit_no_stuff_mem:
free(s);
exit_no_mem:
return NULL;
}
Personally, though, I would have declared the function like this:
int make_stack(struct stack *s, int initial_size);
and allocate the struct stack on the program stack.
It depends on how your stack structure is defined (not just the layout of the struct, but the operations that manipulate it as well).
It's entirely possible to define a stack as a simple array and index, such as
struct stack_ {
T data[N]; // for some type T and size N
size_t stackptr; // Nobody caught that error, so it never existed, right? ;-)
} stack;
stack.stackptr = N; // stack grows towards 0
// push operation
if (stack.stackptr)
stack.data[--stack.stackptr] = some_data();
else
// overflow
// pop operation
if (stack.stackptr < N)
x = stack.data[stack.stackptr++];
else
// underflow
However, fixed-sized arrays are limiting. One easy method of implementing a stack is to use a list structure:
struct stack_elem {
T data;
struct stack_elem *next;
};
The idea is that the head of the list is the top of the stack. Pushing an item onto the stack adds an element at the head of the list; popping an item removes that element from the head of the list:
int push(struct stack_elem **stack, T data)
{
struct stack_elem *s = malloc(sizeof *s);
if (s)
{
s->data = data; // new element gets data
s->next = *stack; // set new element to point to current stack head
*stack = s; // new element becomes new stack head
}
return s != NULL;
}
int pop(struct stack_elem **stack, T *data)
{
int stackempty = (*stack == NULL);
if (!stackempty)
{
struct stack_elem *s = *stack; // retrieve the current stack head
*stack = (*stack)->next; // set stack head to point to next element
*data = s->data; // get the data
free(s); // deallocate the element
}
return r;
}
int main(void)
{
struct stack_elem *mystack = NULL; // stack is initially empty
T value;
...
if (!push(&mystack, some_data()))
// handle overflow
...
if (!pop(&mystack, &value))
// handle underflow
...
}
Since push and pop need to be able to write new pointer values to mystack, we need to pass a pointer to it, hence the double indirection for stack in push and pop.
No, they don't have to be declared as pointers.
One can as well allocate stacks and queues as global variables:
struct myHash { int key; int next_idx; int data[4]; } mainTable[65536];
struct myHash duplicates[65536*10];
int stack[16384];
myHash also includes a linked list for duplicate entries using indices.
But as stated in the comments, if one has to add more elements to the structures, that was initially planned, then pointers come handy.
An additional reason to declare structures as pointers is that it typically with pointers one can access both the complete structure as a whole, any individual element of the structure or some subset of the elements. That makes the syntax more versatile. Also when the structure is passed as a parameter to some external function, a pointer is inevitable.
There's really no need to implement stacks and queues with pointers - others have already stated this fact clearly. Look at #JohnBode 's answer for how a stack can be perfectly implemented using arrays. The thing is that modelling certain data structures (such as stacks, queues and linked lists) using pointers, allows you to program them in a very efficient way in terms of both execution speed and memory consumption.
Usually an underlying array for holding a data structure is very good implementation choice if your use-cases require frequent random access to an element in the structure, given it's positional index (this is FAST with an array). However growing the structure past its initial capacity can be expensive AND you waste memory with the unused elements in the array. Insertion and deletion operations can also be very expensive since you may need to rearrange elements to either compact the structure or make space for the new elements.
Since a queue and a stack don't have this random-acess requirement and you don't insert or delete elements in the middle of them, it is a better implementation choice to dynamically allocate each individual element "on the fly", requesting memory when a new element is required (this is what malloc does), and freeing it as an element is deleted. This is fast and will consume no more memory than it is actually needed by your data structure.
As aleady pointed out it depends on how big the struct is.
Another reason is encapsulation. The stack implementation might not expose the definition of struct stack in its header file. This hides the implementation detail from the user, with the downside that free store allocation is required.

Resources