文章目录
栈
一、栈的概念及其结构
一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端
**称为栈顶,另一端称为栈底。**栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
【注意】栈和栈区的区别:栈区是内存划分的一块区域,属于操作系统的学科,而栈是在内存中管理数据的一种结构,采用在堆上开辟/申请空间的方式,属于数据结构学科。
栈的相关概念选择题
1.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出
栈的顺序是( )。
A 12345ABCDE
B EDCBA54321
C ABCDE12345
D 54321EDCBA
【解析】 B 这道题我们只要根据栈的特点(后进先出/先进后出),就可知答案选B
2.若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
A 1,4,3,2
B 2,3,4,1
C 3,1,4,2
D 3,4,2,1
【解析】 C 题目中告诉我们,进栈过程中可以出栈,所以对于这种题我们可以对照选项来分析有没有这种出栈序列的可能,依次对他们进行分析:
A
B
C
对于C来说,我们要取出3,就不许入栈1和2,然后选项中3出栈后,紧接着就要1出栈,要1出栈就必须2要先出栈,显然是不可能的。
D
二、栈的实现
栈是一种特殊的线性表,所以既可以用顺序表实现,也可以用链表实现,这里我们用顺序表实现,理由如下:
1.栈的插入和删除操作都在栈顶,也就是在数据的尾部进行操作,而顺序表在尾部插入和删除的时间效率为O(1),链表(非双向循环链表)则每次都需要遍历一遍,找到尾部进行插入和删除的操作,而双向循环链表结构复杂,空间内存占用大,综合而言顺序表比较合适。
2.从整体而言,顺序表扩容和链表频繁malloc空间的效率是差不多的。只是顺序表可能会存在一定的空间浪费,但顺序表的缓存利用率高(局部性原理)。
3.顺序表支持随机访问,链表却做不到。
1.栈结构的定义
栈的定义和顺序表一样,只是我们习惯用top,而不是size。top用来获取栈顶的元素。
//结构和符号的定义
typedef int STDataType; //重命名数据类型,数据类型更改时只需要改这一个地方即可
typedef struct StackNode
{
STDataType* a; //指向动态开辟的数组
int top; //记录栈顶
int capacity; //记录容量,容量满时扩容
}ST;
2.初始化栈
初始化栈的时候可以先开辟一定大小的空间,也可以先不开辟,在入栈的时候开辟,我这里先不开辟空间。
//初始化栈
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
3.入栈
由于我们在初始化的时候没有开辟空间,在进行入栈操作的时候需要检查容量进行扩容(第一次入栈的时候开辟空间),入栈的时候需要考虑是数据是放在top的位置还是top++的位置,这个需要看初始化的时候top的初始值,top=-1,放在top++的位置,top是0,放在top的位置。
//入栈
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//检查是否需要扩容
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType)realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
//入栈
ps->a[ps->top] = x;
ps->top++;
}
4.出栈
出栈的时候需要检查栈的容量是否为空,然后top–即可。
//出栈
void StackPop(ST* ps)
{
assert(ps);
//栈为空时不能出栈
assert(!StackEmpty(ps));
ps->top--;
}
5.返回栈顶元素
返回栈顶元素的时候同样需要判断栈是否为空,也需要注意top的初始值,top初始值是-1时返回ps->top位置的值,top的初始值是0,则返回ps->top-1位置的值。
//返回栈顶元素
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];//数组下标从0开始返回ps->top-1;从-1开始返回ps->top
}
6.判断栈是否为空
//判断栈是否为空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
7.返回栈元素的个数
//返回栈元素的个数
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
8.销毁栈
//销毁栈
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
【注意】我们不需要定义一个栈的打印函数,因为栈不能遍历,如果我们需要获取栈顶的前一个函数,我们首先需要把栈顶的元素出栈,然后获取栈顶的元素。
三、完整代码
1.Stack.h
#pragma once //防止头文件被重复包含
//头文件的声明
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
//结构和符号的定义
typedef int STDataType; //重命名数据类型,数据类型更改时只需要改这一个地方即可
typedef struct StackNode
{
STDataType* a; //指向动态开辟的数组
int top; //记录栈顶
int capacity; //记录容量,容量满时扩容
}ST;
//函数的声明
//初始化栈
void StackInit(ST* ps);
//销毁栈
void StackDestroy(ST* ps);
//入栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//返回栈顶元素
STDataType StackTop(ST* ps);
//判断栈是否为空
bool StackEmpty(ST* ps);
//返回栈元素的个数
int StackSize(ST* ps);
2.Stack.c
#include "Stack.h"
//初始化栈
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
//销毁栈
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
//入栈
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//检查是否需要扩容
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType)realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
//入栈
ps->a[ps->top] = x;
ps->top++;
}
//出栈
void StackPop(ST* ps)
{
assert(ps);
//栈为空时不能出栈
assert(!StackEmpty(ps));
ps->top--;
}
//返回栈顶元素
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];//数组下标从0开始返回ps->top-1;从-1开始返回ps->top
}
//判断栈是否为空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
//返回栈元素的个数
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
3.test.c
#include "Stack.h"
// 解耦 -- 低耦合 高内聚
// 数据结构建议不要直接访问结构数据,一定要通过函数接口访问
void TestStack()
{
ST st;
//初始化栈
StackInit(&st);
//入栈
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
//打印栈顶元素
printf("%d ", StackTop(&st));
//出栈
StackPop(&st);
printf("%d ", StackTop(&st));
StackPop(&st);
StackPush(&st, 4);
StackPush(&st, 5);
//栈不能遍历,只能获取栈顶元素之后,出栈一个元素
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
StackPop(&st);
}
printf("\n");
//销毁栈
StackDestroy(&st);
}
int main()
{
TestStack();
return 0;
}
队列
一、队列的概念及其结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出
FIFO(First In First Out) 入队列。
入队列:进行插入操作的一端称为队尾 出队列。
出队列:进行删除操作的一端称为队头。
队列概念相关选择题
以下( )不是队列的基本运算?
A 从队尾插入一个新元素
B 从队列中删除第i个元素
C 判断一个队列是否为空
D 读取队头元素的值
【解析】B 队列只能删除对头的元素,即第一个元素。
二、队列的实现
1.队列结构的定义
队列和栈一样,既可以用顺序表实现,也可以用链表来实现,这里我们用单链表来实现,理由如下:
1.队列只能删除对头的元素,链表的头插头删的效率为O(1),这里我们用单链表就足够了,不需要用双向带头循环链表,不然这样结构太复杂,有点大材小用了。
2.使用链表可以按需申请空间,避免了空间的浪费。
但是我们使用单链表实现队列的时候,单链表尾插和计算队列长度的时候的时间效率为O(N),这些不太好,用复杂链表又不太合适,所以我们在这里增加三个变量,一个节点指针指向对头,一个指针指向队尾,还有一个用来记录队列元素个数的size。
结构的定义
//结构和符号的定义
typedef int QDataType; //数据类型重定义
//定义队列的一个节点
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue
{
QNode* head; //记录队列的头
QNode* tail; //记录队列的尾
int size; //记录队列的长度
}Queue;
2.初始化队列
初始化的时候,需要头结点的指针和尾节点的指针置为空,size置为0.
//初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
3.从队尾入队列
因为队列和栈一样,只能从一端插入数据,所以我们不用单独写一个创建节点的函数。
由于这里我们使用了结构体来记录队列的头和队列的尾,那么我们这里改变队列的头和尾时只需要改变结构体即可,所以只需要传一级指针。这里我们我们要考虑第一次入队和非第一次入队两种情况。
//队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
//开辟新节点
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
else
{
//节点的数据复制为x,指针置为空
newnode->data = x;
newnode->next = NULL;
}
//空队列在队列头部
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
//尾指针后移
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
4.从队头出队列
这里我们除了正常的队列进行判空之外,还需要注意的是,当队列只有一个元素的时候,我们删除了之后会让头指针置空,但尾指针仍然会指向头删之前的那个节点,会形成野指针,所以只有一个元素(pq->head==pq->tail)的时候,我们删除了之后要把两个指针都置为空。
//对头出队列
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq)); //队列为空时不能出队列
//只有一个元素的时候,出队列之后,头尾指针都置为空
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* del = pq->head;
pq->head = pq->head->next;
free(del);
del = NULL;
}
pq->size--;
}
5.获取队头元素
获取元素时都要判断队列是否为空。
//获取对头元素
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
6.获取队尾元素
//获取队尾元素
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
7.获取队列长度
我们没有加size这个变量的时候,只能遍历计数计算队列的长度。
//返回队列元素个数
int QueueSize(Queue* pq)
{
assert(pq);
/*int count = 0;
QNode* cur = pq->head;
while (cur)
{
cur = cur->next;
count++;
}
return count;*/
return pq->size;
}
8.判断队列是否为空
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL && pq->tail == NULL;
}
9.销毁队列
销毁队列的时候,首先要把节点遍历是否,然后再释放头结点和尾结点。
//销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
//遍历删除
QNode* cur = pq->head;
while (cur)
{
QNode* del = cur;
cur = cur->next;
free(del);
}
pq->head = pq->tail = NULL;
}
三、完整代码
1.Queue.h
#pragma once //防止头文件被重复包含
//包含头文件
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
//结构和符号的定义
typedef int QDataType; //数据类型重定义
//定义队列的一个节点
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue
{
QNode* head; //记录队列的头
QNode* tail; //记录队列的尾
int size; //记录队列的长度
}Queue;
//函数的声明
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq, QDataType x);
//对头出队列
void QueuePop(Queue* pq);
//获取对头元素
QDataType QueueFront(Queue* pq);
//获取队尾元素
QDataType QueueBack(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
//返回队列元素个数
int QueueSize(Queue* pq);
2.Queue.c
#include "Queue.h"
//初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
//销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
//遍历删除
QNode* cur = pq->head;
while (cur)
{
QNode* del = cur;
cur = cur->next;
free(del);
}
pq->head = pq->tail = NULL;
}
//队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
//开辟新节点
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
else
{
//节点的数据复制为x,指针置为空
newnode->data = x;
newnode->next = NULL;
}
//空队列在队列头部
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
//尾指针后移
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
//对头出队列
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq)); //队列为空时不能出队列
//只有一个元素的时候,出队列之后,头尾指针都置为空
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* del = pq->head;
pq->head = pq->head->next;
free(del);
del = NULL;
}
pq->size--;
}
//获取对头元素
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
//获取队尾元素
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL && pq->tail == NULL;
}
//返回队列元素个数
int QueueSize(Queue* pq)
{
assert(pq);
/*int count = 0;
QNode* cur = pq->head;
while (cur)
{
cur = cur->next;
count++;
}
return count;*/
return pq->size;
}
3.test.c
#include "Queue.h"
void TestQueue()
{
Queue q;
//初始化队列
QueueInit(&q);
//队尾入队列
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
//获取对头元素
printf("%d ", QueueFront(&q));
//出队列
QueuePop(&q);
printf("%d ", QueueFront(&q));
QueuePop(&q);
QueuePush(&q, 5);
QueuePush(&q, 6);
QueuePush(&q, 7);
//获取队列元素的个数
int count = QueueSize(&q);
printf("%d\n", count);
//遍历队列
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
//销毁队列
QueueEmpty(&q);
}
int main()
{
TestQueue();
return 0;
}
ULL && pq->tail == NULL;
}
//返回队列元素个数
int QueueSize(Queue* pq)
{
assert(pq);
/*int count = 0;
QNode* cur = pq->head;
while (cur)
{
cur = cur->next;
count++;
}
return count;*/
return pq->size;
}
### 3.test.c
```c
#include "Queue.h"
void TestQueue()
{
Queue q;
//初始化队列
QueueInit(&q);
//队尾入队列
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
//获取对头元素
printf("%d ", QueueFront(&q));
//出队列
QueuePop(&q);
printf("%d ", QueueFront(&q));
QueuePop(&q);
QueuePush(&q, 5);
QueuePush(&q, 6);
QueuePush(&q, 7);
//获取队列元素的个数
int count = QueueSize(&q);
printf("%d\n", count);
//遍历队列
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
//销毁队列
QueueEmpty(&q);
}
int main()
{
TestQueue();
return 0;
}