栈
什么是栈?栈是一种特殊的线性表,它只能在在表尾进行插入和删除操作。
栈的底部称为栈底,顶部称为栈顶,所有的操作只能在栈顶进行,也就是说,被压在下方的元素,只能等待其上方的元素出栈之后才能取出,就像我们往箱子里里面放的书一样,因为只有一个口取出里面的物品,所以被压在下面的书只能等上面的书被拿出来之后才能取出,这就是栈的思想,它是一种先进后出的数据结构。
使用C语言实现栈
一般使用栈这样的机构,常用的两个操作就是入栈和出栈
pop
:出栈操作,将栈顶的元素取出,并删除push
:入栈操作,将新元素置入栈顶
定义数据结构
我们在栈的底层使用数组进行存储,所以结构中将储存一个数组的指针,和数组的容量(方便初始化和申请内存);另外栈只能操作栈顶的元素,所以结构中还存着一个变量top
,表示栈顶的元素。
c
typedef int E;
struct Stack
{
E * array; // 一个数组的指针
int capacity; // 数组的容量
int top; // 栈顶的元素
};
typedef struct Stack * stack;
再给栈结构的指针起一个别名,便于后续对栈进行操作。
定义初始化方法
接着我们定义一个初始化栈的方法,在初始化方法中,我们默认初始化时,底层数组的最大容量是10个内存单位,也就是40字节(因为E是int类型),接着我们定义,刚初始化完栈之后是一个空栈,则栈顶的指针默认为-1。
top
变量是为后续的入栈出栈操作做铺垫的,它可以当作数组的索引,也可以用来当作是否是空栈的标识。
c
int initStack(stack stack)
{
stack->array = malloc(sizeof(E) * 10); // 申请一个40字节的内存
if (stack->array == NULL) return 0;
stack->capacity = 10; // 记录底层数组的容量
stack->top = -1; // 栈没有元素默认为-1
return 1;
}
在main函数中,实例化一个Stack
类型,接着使用initStack
函数就完成了栈的初始化:
c
int main()
{
struct Stack stack;
initStack(&stack);
return 0;
}
实现入栈操作
入栈操作,就是把一个元素放到栈的最上方,也就是栈顶上,所以实现该操作的函数只需要有两个参数就可以了:
- 栈的指针
- 需要入栈的元素
另外,底层数组的默认容量是10,如果入栈超过10个元素的话,内存就会造成泄露,所以对底层数组进行扩容操作也是必不可少的。
c
int pushStack(stack stack,E element)
{
if (stack->top == stack->capacity - 1) // 扩容
{
int newCapacity = stack->capacity * 2;
E* newArray = realloc(stack->array,newCapacity * sizeof(E));
if (newArray == NULL) return 0;
stack->array = newArray;
stack->capacity = newCapacity;
}
stack->top++;
stack->array[stack->top] = element;
return 1;
}
在扩容操作中,我们使用realloc
函数将原数组拷贝到一个新的大小的内存中,这个内存地址我们使用newArray
来接收,最后将原栈中的array
指向newArray
,将原栈中的capacity
指向newCapacity
。
简单编写一个打印栈元素的函数,用于测试栈:
c
void printStack(stack stack)
{
printf("|");
for (int i = 0;i<=stack->top;i++)
{
printf("%d, ",stack->array[i]);
}
printf("\n");
}
int main()
{
struct Stack stack;
initStack(&stack);
for(int i = 1;i<=10;i++)
{
pushStack(&stack,i);
}
printStack(&stack);
return 0;
}
控制台输出:
|1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
入栈操作成功实现~
实现出栈操作
出栈操作就是把栈顶的元素取出,然后把top
执行自减操作,如果不自减元素出栈之后栈顶的元素还会是原来的元素。
c
E popStack(stack stack)
{
if (stack->top == -1)
{
printf("栈为空,不能出栈\n");
return -1;
}
E element = stack->array[stack->top];
stack->top--;
return element;
}
我们接着测试一下:
c
void printStack(stack stack)
{
printf("|");
for (int i = 0;i<=stack->top;i++)
{
printf("%d, ",stack->array[i]);
}
printf("\n");
}
int main()
{
struct Stack stack;
initStack(&stack);
for(int i = 1;i<=20;i++)
{
pushStack(&stack,i);
}
printStack(&stack);
printf("出栈的元素顺序:");
while (1)
{
printf("%d, ",stack.array[stack.top]);
popStack(&stack);
if (stack.top == -1) break;
}
return 0;
}
会发现控制台输出的信息为:
|1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
出栈的元素顺序:20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
出栈的顺序是从20到1的,说明元素是从栈顶依次出去的,至此出栈操作也实现好了。
总结
使用C语言手动把栈的结构和各种操作实现一遍是很重要的,它能够加深学习者对计算机底层的认识,也能够为后续的算法题的解题提供一种新的思路。
本篇博文所有代码:
c
#include "stdio.h"
#include "stdlib.h"
typedef int E;
struct Stack
{
E * array; // 一个数组的指针
int capacity; // 数组的容量
int top; // 栈顶的元素
};
typedef struct Stack * stack;
int initStack(stack stack)
{
stack->array = malloc(sizeof(E) * 10); // 申请一个40字节的内存
if (stack->array == NULL) return 0;
stack->capacity = 10; // 记录底层数组的容量
stack->top = -1; // 栈没有元素默认为-1
return 1;
}
int pushStack(stack stack,E element)
{
if (stack->top == stack->capacity - 1) // 扩容
{
int newCapacity = stack->capacity * 2;
E* newArray = realloc(stack->array,newCapacity * sizeof(E));
if (newArray == NULL) return 0;
stack->array = newArray;
stack->capacity = newCapacity;
}
stack->top++;
stack->array[stack->top] = element;
return 1;
}
E popStack(stack stack)
{
if (stack->top == -1)
{
printf("栈为空,不能出栈\n");
return -1;
}
E element = stack->array[stack->top];
stack->top--;
return element;
}
void printStack(stack stack)
{
printf("|");
for (int i = 0;i<=stack->top;i++)
{
printf("%d, ",stack->array[i]);
}
printf("\n");
}
int main()
{
struct Stack stack;
initStack(&stack);
for(int i = 1;i<=20;i++)
{
pushStack(&stack,i);
}
printStack(&stack);
printf("出栈的元素顺序:");
while (1)
{
printf("%d, ",stack.array[stack.top]);
popStack(&stack);
if (stack.top == -1) break;
}
return 0;
}