1.1栈的概念及结构
栈是一种特殊的线性表,只允许在固定的一端进行插入和删除元素的操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,插入的数据在栈顶。
出栈:栈的删除操作叫做出栈。删除的数据也是在栈顶。
1.2栈的实现
栈的实现既可以使用数组也可以使用链表,这两种实现方法,对于而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。下面就以数组的方式来实现一个栈。
1.21定义栈结构
要实现栈首先就要定义栈的结构,栈应当包含一个指向栈顶位置的变量,以及存储数据的动态数组,同时还要有为了能够实现动态变化,我们应该还要定义一个变量来记录动态数组的容量大小,以便实现对栈大小的扩容。
typedef int STDataType;
typedef struct stack
{
STDataType* a;
int top;//栈顶
int capacity;//栈容量
}ST;
1.22实现栈的相关功能
栈的初始化:void STInit(ST* ps);
void STInit(ST*ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
这里在在对栈进行初始化时没有为a所指向的空间开辟动态内存,而是先将a置空,再压栈的时候再调整a所指向的空间,这里也可以先为a所指向的空间开辟一块空间,在后续压栈调整空间也是没有问题的,可以有不同的实现思路和方法。
入栈:void STPush(ST *ps,STDataType x);
void STPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->capacity == ps->top)//检查容量是否已满
{
int newCapacity = (ps->capacity==0 ? 4:2*ps->capacity);
STDataType* temp = (STDataType*)realloc(ps->a,sizeof(STDataType)*newCapacity);
if (temp == NULL)
{
perror("realloc");
exit(-1);
}
ps->a = temp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
(ps->top)++;
}
元素入栈要考虑栈的容量是否足够大,容量不够时要利用realloc函数及时调整栈大小,在调整好新的栈容量大小后也不要忘记将原来栈容量大小替换为新的容量大小,然后再进行入栈的操作,此外每次元素入栈栈顶的大小都要+1,指向栈顶元素的下一个位置。
出栈:void STPop(ST *ps);
void STPop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
元素出栈这步比较简单,只需要将栈顶的位置下调一个单位,这样就完成了元素的出栈操作,该操作本质上是将栈的访问空间大小下调一个单位,这样原先的栈顶元素即使不用销毁也能达到出栈的效果,即不能再访问该元素。
销毁栈:void STDestroy(ST *ps);
void STDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
销毁栈这里没有什么需要注意的地方,只需要将为a在堆 上动态开辟内存释放即可。
判断栈是否为空:bool STEmpty(ST*ps);
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;//为真返回true,为假返回false
}
判断栈是否为空利用了栈顶大小为栈大小这一关系,栈顶top初始大小为0,此时没有元素,为空栈。
2.1队列的概念及结构
对比与栈结构,队列与栈不相同的是:队列只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的原则。
入队列:进行插入操作的一端称为队尾。
出队列:进行删除操作的一端称为队头。
2.2队列的实现
与栈结构相同,队列也可以使用数组和链表的结构来实现,不同的是队列使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。下面以链表的结构来实现一个队列。
2.21定义队列结构
要用链表结构来实现队列,首先我们要清楚的定义队列的结构,首先要定义一个包含指针域和数据域的指针,这是一个链表的基本结构,然后,一个完整的队列又应当包含一个指向队头以及队尾的指针,便于队列的入队和出队,此外还可以定义一个变量来记录队列的长度,这里则刚好可以额外定义一个结构体类型,同时包含这两个指针以及用于记录队列长度的变量。
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;//数据域
struct QueueNode* next;//指针域
}QNode;
typedef struct Queue
{
QNode* head;//头指针
QNode* tail;//尾指针
int size;//队列数据个数
}Que;
2.22实现队列的相关功能
初始化队列:void QInit(Que* pq);
void QInit(Que* pq)
{
assert(pq);
pq->head=pq->tail = NULL;
pq->size = 0;
}
对队列的初始化十分简单,只要将初始化队列的长度为0,以及将头指针和尾指针置空即可,在后续的元素入队以及出队中,队头以及队尾指针也将发生相应的变化。
入队:void QPush(Que* pq, QDataType x);
void QPush(Que* pq, QDataType x)
{
assert(pq);
QNode* temp = (QNode*)malloc(sizeof(QNode));
if (temp == NULL)
{
perror("malloc");
exit(-1);
}
temp->data = x;
temp->next = NULL;
if (pq->head == NULL)//空表
{
pq->head = pq->tail = temp;
}
else
{
pq->tail->next = temp;
pq->tail = temp;//尾指针后移
}
pq->size++;//队长加一
}
元素入队需要开辟新的结点,需要注意的是,在将新节点接入到队尾时需要判断队列是否为空队列,对于空队列,头尾指针所指向的都为空,需要将新接入的结点同时作为队头和队尾,将头尾指针同时指向该结点;对于非空队列,只需要正常将新节点接入到队尾的后面,再将尾指针指向新节点即可。
出队:void QPop(Que* pq);
void QPop(Que* pq)
{
assert(pq);
assert(pq->size != 0);//空队列
if (pq-size == 1)//仅有一个元素的队列
{
QNode* temp = pq->head;
free(temp);
pq->head = pq->tail = NULL;
}
else
{
QNode* headNext = pq->head->next;
free(pq->head);
pq->head = headNext;
}
pq->size--;
}
元素出队列也需要分情况而定,首先断言空链表这里应该问题不大,剩下的还要分两种情况,一种是队列只有一个数据时,此时队头和队尾为同一结点,元素出队列需要将头尾指针均置空;剩下的一个情况则是大于一个数据的队列,此时正常释放队头结点,将头指针指向下一结点即可。
销毁队列:void QDestroy(Que* pq);
void QDestroy(Que* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* curNext = cur->next;
free(cur);
cur = curNext;
}
}
队列的销毁本质上是从队头开始遍历一遍队列,保存下一结点,逐一释放结点。
判断队列是否为空:bool QEmpty(Que* pq);
bool QEmpty(Que* pq)
{
assert(pq);
return pq->size == 0;
}
判断队列是否为空十分简单,只需要利用储存队列长度的变量size判断其大小是否为0即可知道。
结语:
本期关于栈和队列的相关知识到这就介绍完了,如果感觉对你有帮助的话还请点个赞支持一下!有不对或者需要改正的地方还请指正,感谢各位的观看。