1.栈的基本概念
栈是一种特殊的线性数据结构,它遵循特定的操作顺序,即后进先出(Last In First Out, LIFO)。这意味着最后添加到栈中的元素将是第一个被移除的元素。栈的这种特性使得它在处理具有递归性质的问题和算法中非常有用。
【特点】
后进先出:栈的基本操作确保了最后被添加的元素总是第一个被移除。
单端操作:所有的添加和移除操作仅在栈的同一端进行,这一端被称为“栈顶”,另一端称为“栈底”。
2.基本操作
栈的操作通常包括以下几种:
1. Push(入栈):将一个元素添加到栈顶。这是一个添加操作,将元素放置在栈的最上方。
2. Pop(出栈):从栈顶移除一个元素,并返回被移除的元素。这是一个移除操作,它删除并返回栈顶元素。
3. Peek 或 Top(查看栈顶元素):返回栈顶元素,但不从栈中移除它。这允许我们查看栈顶的元素而不改变栈的状态。
4. IsEmpty(检查栈是否为空):确定栈是否为空。如果栈中没有元素,此操作返回 true;否则,返回 false。
5.Size(获取栈的大小):返回栈中元素的数量。这允许我们了解栈中存储了多少元素。
3.栈的实现
3.1 数组OR链表?
实现栈的两种常见方法是使用数组和链表。每种方法都有其独特的优势和劣势,选择哪种方式取决于具体的应用场景和需求。下面我们将详细探讨使用数组和链表实现栈的优势和劣势,并解释为什么在某些情况下数组可能是更好的选择。
使用数组实现栈的优势:
随机访问:数组提供了O(1)的时间复杂度来访问任何一个元素,这使得访问栈顶元素非常快速。
内存连续性:数组在内存中是连续存储的,这有助于提高空间和缓存的效率。
简单性:使用数组实现栈的代码通常更简单直观,特别是在栈操作不频繁或栈大小固定时。
使用数组实现栈的劣势:
固定大小:数组的大小在初始化时就固定了,这限制了栈的最大容量,可能会导致栈溢出。
扩容问题:如果栈满了需要扩容,就必须创建一个更大的数组并复制原数组的元素,这个过程的时间复杂度是O(n)。
使用链表实现栈的优势:
动态大小:链表实现的栈可以动态地增长和缩小,不受固定大小的限制,从而避免了栈溢出的问题。
内存利用率:链表是非连续存储的,它可以更好地利用分散的内存空间,理论上可以达到系统内存的限制。
使用链表实现栈的劣势:
内存开销:链表的每个元素都需要额外的空间来存储指向下一个元素的指针。
时间开销:相比数组实现,链表访问和操作元素(特别是非栈顶元素)的时间开销较大。
缓存不友好:由于链表的元素在内存中是非连续存储的,它不如数组那样缓存友好。
3.2 为什么数组更好?
在特定的应用场景下,数组实现的栈可能更受青睐,主要是因为以下几点:
性能:对于栈顶元素的操作,数组提供了更快的访问速度,尤其是在需要频繁访问或操作栈顶元素的情况下。
简单性和高效性:数组实现的栈在代码上更直观简单,且由于内存连续性,它在空间和缓存效率上通常表现更好。
可预测的性能:数组的固定大小虽然是一个限制,但也意味着栈的性能是可预测的,不会因为动态扩容而出现性能波动。
3.3 数组实现栈(静态)
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100 // 栈的最大容量
typedef struct stack_s
{
int data[MAX_SIZE]; // 用于存储栈中元素的数组
int top; // 指示栈顶元素的下标
} stack_t;
void stack_init(stack_t *stack);
int stack_full(stack_t *stack);
int stack_empty(stack_t *stack);
void stack_push(stack_t *stack, int value);
int stack_pop(stack_t *stack);
int stack_view_peek(stack_t *stack);
int main (int argc, char **argv)
{
stack_t stack;
stack_init(&stack);
/*
for(i=0; i<3; i++)
{
stack_push(&stack, value[i]);
}
*/
stack_push(&stack, 10);
stack_push(&stack, 20);
stack_push(&stack, 30);
printf("栈顶元素:%d\n",stack_view_peek(&stack));
printf("出栈元素:%d\n", stack_pop(&stack));
printf("出栈元素:%d\n", stack_pop(&stack));
printf("栈顶元素:%d\n",stack_view_peek(&stack));
return 0;
}
void stack_init(stack_t *stack)
{
stack->top = -1;//栈为空
}
int stack_empty(stack_t *stack)
{
return stack->top == -1;
}
int stack_full(stack_t *stack)
{
return stack->top == MAX_SIZE - 1;
}
void stack_push(stack_t *stack, int value)
{
if( stack_full(stack) )
{
printf("栈满,无法入栈\n");
return;
}
stack->data[++(stack->top)] = value;
}
int stack_pop(stack_t *stack)
{
if( stack_empty(stack) )
{
printf("栈空,无法出栈\n");
return -1;
}
return stack->data[(stack->top)--];
}
int stack_view_peek(stack_t *stack)
{
if( stack_empty(stack) )
{
printf("栈空,无法查看\n");
return -2;
}
return stack->data[stack->top];
}
3.4 单链表实现(动态)
链表的结点相当于数组的一个元素,malloc动态分配一段存储空间。下面是入栈和出栈的草图
#include <stdio.h>
#include <stdlib.h>
typedef struct Node
{
int data;
struct Node* next;
} Node;
typedef struct Stack
{
Node* top;
} Stack;
void initStack(Stack* stack);
void push(Stack* stack, int data);
int pop(Stack* stack);
int peek(Stack* stack);
void display(Stack* stack);
int main()
{
Stack stack;
initStack(&stack);
push(&stack, 10);
push(&stack, 20);
push(&stack, 30);
display(&stack);
printf("Top element is %d\n", peek(&stack));
pop(&stack);
display(&stack);
pop(&stack);
pop(&stack);
display(&stack);
return 0;
}
//初始化栈时,栈顶指针应该指向NULL
void initStack(Stack* stack)
{
stack->top = NULL;
}
//检查栈是否为空
int isEmpty(Stack* stack)
{
return stack->top == NULL;
}
//入栈操作
void push(Stack* stack, int data)
{
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL)
{
printf("Memory allocation failed\n");
return;
}
newNode->data = data;
newNode->next = stack->top;
stack->top = newNode;
printf("Pushed %d onto the stack\n", data);
}
//出栈操作
int pop(Stack* stack)
{
if (isEmpty(stack))
{
printf("Stack underflow\n");
exit(EXIT_FAILURE);
}
Node* temp = stack->top;
int poppedData = temp->data;
stack->top = stack->top->next;
free(temp);
printf("Popped %d from the stack\n", poppedData);
return poppedData;
}
//查看栈顶元素
int peek(Stack* stack)
{
if (isEmpty(stack))
{
printf("Stack is empty\n");
exit(EXIT_FAILURE);
}
return stack->top->data;
}
//显示栈中的所有元素
void display(Stack* stack)
{
if (isEmpty(stack))
{
printf("Stack is empty\n");
return;
}
Node* current = stack->top;
printf("Stack elements are:\n");
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}