一、什么叫栈
1、栈:一种特殊的线性表,其只允许在固定的一端插入和删除元素操作。
2、进行数据插入和删除操作的一端称为栈顶。
另一端即封闭的一端称为栈底。
想要理解栈,可以把栈想象成一个杯子,把数据放进去,最先进去的数据就被放到了杯子的最下面,而后放入的数据就放在了杯子的上面。所以要想将数据取出来只能先去上面的数据。这也是栈的特点:先入后出,后入先出。
对于栈的实现可以使用数组和链表,下面程序中使用的是数组。
二、什么叫队列
1、队列的特性和栈是相反的。
2、它只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的特性。
3、入队列:进行插入操作的一端称为队尾;4、出队列:进行删除操作的一端称为队头
对于队列来说,一种入队顺序,只有一种出队顺序
对于队列的实现,我们使用的是链表。
三、如何用C语言使用他们
以下所有操作都是在虚拟机的VIM里进行实现
1、关于栈
好了,下面用程序来实现栈,让大家更详细的了解栈的操作。
我们使用数组来操作栈,数组是一块连续的内存。我们要在每一个栈中存储数据那么我们就要使用到结构体,这个结构体里面放些什么呢?先来思考一下。
操作栈,我们是不是需要申请空间来储存数据,既然要申请空间来储存数据那么程序员申请空间就需要申请堆区的空间,使用malloc来申请。如果不知道如何申请空间的可以看我之前的文章,里面有详细介绍。申请完空间我们就需要知道空间的首地址,首地址就可以保存在结构体里面。
栈的大小我们也是需要的,以及入栈使用的空间个数。大概就这三个。
struct stack
{
int *m_space; //栈空间的起始地址
int len; //栈的长度
int count; //使用的个数
};
既然创建了栈的节点,那么我们下一步需要对结构体进行初始化。这里我选择封装函数来完成。在写函数之前我们先来写一个主函数。
int main()
{
struct stack s;
int n=5;//栈的长度
return 0;
}
现在来创建初始化函数,首先我们要了解我们在初始化的时候是需要对s里面的值进行改变的,改变实参的值就需要使用地址传参,所以我们需要将s的地址传过去,除此之外还有一个栈长也需要传过去。代码如下。
void stack_init(struct stack *ps, int n)
{
ps->len = n;
ps->m_space = (int*)malloc(sizeof(int)*n);
assert(ps->m_space != NULL);
ps->count = 0;
}
这里使用了一个assert函数,这个函数需要包含头文件
include <assert.h>
主要就是用来判断ps->m_space是否申请空间成功。如果不成功的话程序就会在此处停止并且报错说明原因,具体想了解的可以百度一下或者在ubuntu的命令里man一下。
初始化完了我们就要进行判断了。判断栈是否为空,是否为满。为什么要判断呢,这里我解释一下。主要是为了下面入栈和出栈做说明,我们在入栈操作时,如果栈满了我们就无法入栈,入栈程序就终止。如果做出栈操作,栈是空的,都没有东西给我们出栈,那么出栈操作也就终止了。
所以我们首先判断栈是否为空,这里使用了bool,在c当中没有这个类型,所以我们需要包含他的头文件include<stdbool.h>
//判断栈是否为空
//true表示为空
bool is_empty(struct stack s)
{
if(s.count == 0)
{
return true;
}
else
{
return false;
}
}
//判断栈是否已满
//true表示已经满了
bool is_full(struct stack s)
{
if(s.count == s.len)//通过判断使用个数和总长是否相等
{
return true;
}
else
{
return false;
}
}
准备工作做完我们开始进行入栈出栈操作
//入栈
//成功返回true 失败返回false
bool stack_push(struct stack *ps, int data)
{
//判断栈是否已满
if(is_full(*ps))
{
printf("入栈失败,栈已满!\n");
return false;
}
//正确入栈操作
ps->m_space[ps->count] = data;
(ps->count) ++;//记录使用个数
return true;
}
进去了,我们当然得出来了,不然你呆在里面干嘛(手动狗头)
下面是出栈操作
//出栈
bool stack_pop(struct stack *ps, int *pdata)
{
//判断栈是否为空
if(is_empty(*ps))
{
printf("出栈失败, 栈为空!\n");
return false;
}
//正确出栈操作
if(pdata != NULL)
{
*pdata = ps->m_space[ps->count-1];
}
ps->count --;
return true;
}
最后我们来完善一下主程序
int main()
{
struct stack s;
int data;
int i=0;
int num;
initStack(&s,5);
printf("请输入5个不同的数\n");
while(i<5)
{
scanf("%d",&num);
stack_push(&s,num);
i++;
}
printf("出栈后\n");
for(int j=0;j<5;j++)
{
stack_pop(&s,&data);
printf("data: %d\n", data);
}
}
最后编译结果
是不是,先入后出
下面是文件用到的所有头文件
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
2、关于队列
队列的话我们使用链表进行操作,废话少说直接上才艺,呸,上代码。
下面是队列使用到的头文件
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
老规矩,上来先写结构体
//表示链表节点的类型
struct node
{
int data;
struct node *next;
};
//表示队列的基本信息
struct queue
{
struct node *first; //第一个节点的地址
struct node *tail ; //最后一个节点的地址
int len; // 队列的长度
int count; //队列的实际长度
};
接下来就是初始化,判断队列是否为空,队列是否满了,跟上面大差不差,让我们直接看代码吧。
//队列的初始化
void queue_init(struct queue *pq, int n)
{
pq->first = NULL;
pq->tail = NULL;
pq->len = n;
pq->count = 0;
}
//判断队列是否为空
bool is_empty(struct queue q1)
{
if(q1.first == NULL)
{
return true;
}
else
{
return false;
}
}
//判断队列是否已满
bool is_full(struct queue q1)
{
if(q1.len == q1.count)
{
return true;
}
else
{
return false;
}
}
准备工作做完,接下来是重头戏了。
//入队(尾部追加)
bool queue_push(struct queue *pq, int data)
{
//判断队列是否已满
if(is_full(*pq))
{
printf("入队失败,队列已满!\n");
return false;
}
//正确入队操作
struct node *pnew = NULL;
pnew = (struct node*)malloc(sizeof(struct node));
assert(pnew!=NULL);
pnew->data = data;
pnew->next = NULL;
if(pq->first == NULL) //pnew 是队列的第一个节点
{
pq->first = pnew;
pq->tail = pnew;
}
else //队列是一个非空的队列
{
pq->tail->next = pnew;
pq->tail = pnew;
}
pq->count ++;
return true;
}
入队列的时候需要考虑是否是第一个节点的问题。
入队列完了,然后是出队列。
//出队(头部删除)
bool queue_pop(struct queue *pq, int *pdata)
{
//判断队列是否为空
if(is_empty(*pq))
{
printf("出队失败,队列为空!\n");
return false;
}
//正常出队操作
struct node *pdel = NULL;
pdel = pq->first;
if(pdata != NULL) //返回出队的数据
{
*pdata = pdel->data;
}
if(pq->first->next == NULL) //队列只有一个节点
{
pq->first = NULL;
pq->tail = NULL;
free(pdel);
}
else //多个节点
{
pq->first = pdel->next;
free(pdel);
}
pq->count --;
return true;
}
出队列的时候也得考虑考虑了,分两种情况,队列里只有一个节点的情况以及多个节点的情况。
在调试前我们先来完善一下主程序
int main()
{
struct queue q1;
queue_init(&q1, 5);
queue_push(&q1, 2);
queue_push(&q1, 4);
queue_push(&q1, 6);
queue_push(&q1, 8);
int data;
queue_pop(&q1, &data);
printf("Data: %d\n", data);
queue_pop(&q1, &data);
printf("Data: %d\n", data);
queue_pop(&q1, &data);
printf("Data: %d\n", data);
queue_pop(&q1, &data);
printf("Data: %d\n", data);
return 0;
}
看看打印结果,不出意外的话应该是,2,4,6,8