——引子
栈是系统软件必不可少的数据结构,其应用十分的广泛,例如:储存管理、函数调用以及表达式求值等,都离不开栈这个数据结构。
另外,深度优先搜索算法也需要借助栈来完成。
那么,栈到底是一种怎样的结构呢?
栈的特点
栈其实也是线性表中的一种,不过,它是作为线性表中比较特例的存在。
这种结构,只允许在栈的一端进行操作,这个位置,也被称为栈顶!
对于栈的一切操作,例如增、删、查等,都只能在栈顶进行,对于栈其他位置的元素,没有影响。
所以,这也就造成了栈最大的特点:后进先出!
对于栈的理解
对于栈,我们可以将其想象成一个瓶子,而元素,就是瓶子中的水,瓶口就相当于是栈顶,瓶底相当于栈底。
当一个元素进入栈时,就像水一样,直接进入瓶子的最底层,而想要倒水出来,那最先出来的只能是上层的水。
所以,将栈这个结构,当作一个水瓶,是在好不过的了~
还有,关于栈元素的出栈顺序,利用的就是我们的卡特兰数,对于卡特兰数,在这里我就不做过多的解释,想要了解的小伙伴们,可以自己找找相关的书籍~
在为大家大概的讲明栈的逻辑意义之后,就是我们的重头戏了,下面就让我们一起,看看栈结构,到底是如何实现的~
栈的实现
既然栈也是一种特别的线性表,那么在之前的了解之中,我们已经知道,线性表分为两种,一种是顺序的,一种是链式的。
那么,既然栈作为一个线性表,那肯定也有两种实现的形式,接下来,就让我们一起看看,这两种形式的栈,有何不同之处!
顺序栈
使用顺序存储的方式实现栈结构与实现顺序表的结构类似,不同的是,对于栈内元素的操作,我们只能从栈顶进行。
由于顺序表中,在尾部插入和删除元素比较容易,所以,在下面的代码中,我们将表的尾部当作栈顶,具体实现如下:
类型定义
typedef int data;
typedef struct{
int top;
int capacity;
data * array;
}stack;
//这里利用typedef关键字,可以将代码的可重用性提高一点,想要转变数据类型时,直接改变就行,数据类型可以时结构体!
初始化
stack * init_stack(stack * p, int d){
p->top = 0;
p->capacity = d?d:8;
p->array = (data *)malloc(sizeof(data) * p->capacity);
return p;
}//初始化栈
销毁
void destory_stack(stack * p){
free(p->array);
}//销毁栈
判断栈是否为空
_Bool isEmpty_stack(stack * p){
return p->top == 0;
}//判断栈是否为空
扩展空间
stack * extend_stack(stack * p){
int i = 0;
data * new_array = (data *)malloc(sizeof(data) * p->capacity * 2);
for(i = 0; i < p->top; i++){
new_array[i] = p->array[i];
}
free(p->array);
p->array = new_array;
return p;
}//空间扩展
入栈
stack * push_stack(stack * p, data d){
if(p->top == p->capacity){
extend_stack(p);
}
p->array[p->top++] = d;
return p;
}//入栈
弹栈
stack * pop_stack(stack * p){
if(p->top == 0){
return (void *)0;
}
p->top--;
return p;
}//弹栈
读取栈顶元素
stack * read_stack(stack * p, data * da){
if(p->top == 0){
return (void *)0;
}
* da = p->array[p->top - 1];
return p;
}//读取栈顶元素
测试数据
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main()
{
stack s;
init_stack(&s,4);
if(isEmpty_stack(&s)){
printf("栈为空!\n");
}
printf("开始入栈操作:1,2,3,4,5\n");
push_stack(&s,1);
push_stack(&s,2);
push_stack(&s,3);
push_stack(&s,4);
push_stack(&s,5);
printf("入栈操作完成!\n");
while(!isEmpty_stack(&s)){
data d;
read_stack(&s,&d);
printf("此时栈顶元素是:%d\n",d);
printf("弹栈操作执行!\n");
pop_stack(&s);
}
if(isEmpty_stack(&s)){
printf("栈为空!\n");
}
destory_stack(&s);
return 0;
}
结果
链式栈
使用链式存储的方式实现栈,由于链表的头插和头删比较快,所以我们将链表的表头当作是栈顶,具体实现如下:
在这里插入代码片
类型定义
typedef int Data;
typedef struct N{
Data data;
struct N * next;
}Node;
typedef struct{
Node * top;
int size;
}Link_stack;
//这里将数据域类型起别名的原因和上面的顺序栈相同,都是为了代码可重用!
创建节点
Node * make_node(Data d){
Node * new_node = (Node *) malloc(sizeof(Data));
new_node->data = d;
new_node->next = (void *)0;
return new_node;
}//创建节点
初始化
Link_stack * init_stack(Link_stack * p){
p->size = 0;
p->top = (void *)0;
return p;
}//初始化
销毁
void destory_stack(Link_stack * p){
while(p->top){
Node * del = p->top;
p->top = p->top->next;
free(del);
}
}//销毁栈
判断栈是否为空
_Bool isEmpty_stack(Link_stack * p){
return p->top == (void *)0;
}//判断栈是否为空
入栈
Link_stack * push_stack(Link_stack * p, Data d){
Node * new_node = make_node(d);
if(!new_node){
return (void *)0;
}
new_node->next = p->top;
p->top = new_node;
p->size++;
return p;
}//入栈
弹栈
Link_stack * pop_stack(Link_stack * p){
if(p->size == 0){
return (void *)0;
}
Node * del = p->top;
p->top = p->top->next;
free(del);
p->size--;
return p;
}//弹栈
读取栈顶元素
Link_stack * read_stack(Link_stack * p, Data * d){
if(p->size == 0){
return (void *)0;
}
*d = p->top->data;
return p;
}//读取栈顶元素
测试数据
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main()
{
int a[5] = {1,2,3,4,5};
int i = 0;
Link_stack s;
init_stack(&s);
if((&s)->size == 0){
printf("栈创建成功!\n");
}
printf("需要入栈的元素有:1,2,3,4,5\n");
for(i = 0; i < 5; i++){
printf("此时入栈的元素是:%d\n",a[i]);
push_stack(&s,a[i]);
if(!isEmpty_stack(&s)){
printf("入栈成功!\n");
}
}
while((&s)->top){
Data d;
printf("此时栈顶元素是:");
read_stack(&s,&d);
printf("%d\n",d);
Link_stack * x = pop_stack(&s);
if(x != (void *)0){
printf("弹栈成功!\n");
}
}
destory_stack(&s);
return 0;
}
结果
总结
这次给大家带来的就是这么多了,下一次窝瓜将给大家带来队列的简单实现~