文章目录
- 栈
- 一、栈的概念及结构
- 二、栈的特点
- 三、栈的实现
- 1.初始化栈
- 2.判断栈空
- 3.入栈
- 4.出栈
- 5.取栈顶元素
- 6.栈的元素个数
- 7.销毁
- 队列
- 一、队列的概念及结构
- 二、队列的特点
- 三、队列的实现
- 1.初始化
- 2.入队
- 3.出队
- 4.判断队空
- 5.取队头元素
- 6.取队尾元素
- 总结
栈
一、栈的概念及结构
栈(Stack): 一种特殊的线性表,只允许在栈顶进行插入和删除元素操作。栈中的数据元素遵守后进先出LIFO (Last In First Out)的原则。
栈顶:允许进行插入、删除操作的一端称为栈的栈顶(top),也称为表尾。
栈底:固定不动的一端,称为栈底(bottom),也称为表头。
进栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈/弹栈,出数据也在栈顶。
栈也分为顺序栈和链栈(类比顺序表和链表)采用顺序存储的栈称为顺序栈,用一段物理地址连续的存储单元依次存储数据元素,通常以数组的形式实现;采用链式存储的栈称为链栈。
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些,因为数组尾插尾删的效率更高。
二、栈的特点
1.只能在栈顶进行插入和删除元素。
2.遵循后进先出原则,后入栈的元素先出栈,即最后插入的元素最先删除。
3.只能访问栈顶元素,不能从栈底或者中间访问元素。
三、栈的实现
栈的基本操作有:入栈(Push)、出栈(Pop)、取栈顶元素(Top)和判断栈是否为空(IsEmpty)等。
和顺序表一样,顺序栈采用动态存储的方式,根据需要来动态地申请空间。
队列的定义:
#define INIT_SZ 10 //初始空间大小
#define INC_SZ 4 //每次扩容的空间大小
typedef int STDataType;//方便改存储的数据类型,下面以int示例
typedef struct Stack
{
int* a;
int top;//栈顶索引
int capacity;//分配的空间容量
}ST;
1.初始化栈
//初始化
void STInit(ST* ps)
{
assert(ps);//提高代码健壮性
ps->a = (STDataType*)malloc(sizeof(STDataType) * INIT_SZ);
if (NULL == ps->a)
{
perror("malloc");
return;
}
ps->capacity = INIT_SZ;
//ps->top = -1;//top是栈顶元素的位置
ps->top = 0;//top是栈顶元素的下一个位置
}
注意:栈顶下标top在初始化时,可以选择初始化为-1,表示栈顶元素的位置;也可以选择初始化为0,表示栈顶元素的下一个位置。top初始化的值会导致后面的基本操作写法有一点不同,随机选择一种即可。
2.判断栈空
判空条件与初始化时top的值有关。
//判断栈空
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;//初始化的值
}
3.入栈
//入栈
void STPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)//栈内元素个数等于空间容量,需要进行扩容
{
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * (INC_SZ + ps->capacity));
if (NULL == tmp)
{
perror("malloc");
return;
}
ps->a = tmp;
ps->capacity += INC_SZ;
}
//ps->a[++ps->top] = x;//ps->top初始化为-1时的写法
ps->a[ps->top++] = x;
}
注意:栈顶下标top初始化为-1,表示栈顶元素的位置,每次入栈先++top,到下一个元素的位置,再插入;top如果初始化为0,表示栈顶元素的下一个位置,每次先插入,再++top。
4.出栈
//出栈
void STPop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps));//栈非空才能出栈
ps->top--;
}
注意:删除的空间不能free掉,因为动态开辟的空间不能局部释放。
5.取栈顶元素
//返回栈顶元素
STDataType STTop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps));//要保证栈非空才能取栈顶元素
//return ps->a[ps->top];//top初始化为-1时的写法
return ps->a[ps->top - 1];
}
这里也会受到前面top初始化值的影响。
6.栈的元素个数
//栈的元素个数
int STSize(ST* ps)
{
assert(ps);
//return ps->top+1;//top初始化为-1时的写法
return ps->top;
}
7.销毁
//销毁
void STDestroy(ST* ps)
{
assert(ps);
ps->a = NULL;
free(ps->a);
ps->top = 0 = ps->capacity = 0;
}
队列
一、队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)的特点。
就像人们排队一样,讲究先来后到,先排队的人享受完服务,先离开。
队头:进行删除操作的一端。
队尾:进行插入操作的一端。
入队列:队列的插入操作,入数据在队尾。
出队列:队列的删除操作,出数据在队头。
队列也可以用数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列是数组头删,效率会比较低。
队列也分为非循环队列和循环队列,可以用数组或链表实现。下面主要介绍用链表实现的普通的非循环队列。
二、队列的特点
1.只能在队头删除数据,只能在队尾插入数据。
2.遵循先进先出原则,先入队的元素先出队,即先插入的元素先删除。
3.只能访问队头和队尾元素,不能从中间访问元素。
三、队列的实现
队列的基本操作有:入队(Push)、出队(Pop)、判断队列空(IsEmpty)取队头元素(Front)、取队尾元素(Back)等。
队列的定义
队列的链式存储结构是一个带有队头指针和队尾指针的单链表。
typedef int QDataType;//数据类型
//结点
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
//队列
typedef struct Queue
{
QNode* head;//队头指针
QNode* tail;//队尾指针
}Queue;
1.初始化
不带头结点的链式队列的初始化。
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
}
2.入队
入队相当于链表的尾插,只不过链表没有尾指针,需要从头结点开始遍历到尾结点,而队列可以通过队尾指针直接访问尾结点,效率更高。
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newNode = (QNode*)malloc(sizeof(QNode));//申请结点
if (NULL == newNode)
{
perror("malloc");
return;
}
newNode->data = x;
newNode->next = NULL;
if (NULL == pq->head)//空队列,第一次入队
{
pq->head = pq->tail = newNode;
}
else
{
pq->tail->next = newNode;//队尾的下个结点指向新结点
pq->tail = newNode;//更新队尾指针
}
}
3.出队
出队相当于链表的头删。
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head);
QNode* head = pq->head;//保存头结点,释放空间
pq->head = head->next;//更新队头指针
free(head);//释放头结点的空间
head = NULL;//防止野指针
}
注意:删除时要释放结点空间,必须先保存头结点,否则更新队头指针后无法找到该结点从而导致无法释放这块空间。
4.判断队空
队列为空的条件与初始化有关。
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;//或者return pq->tail == NULL;
}
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;
}
总结
栈和队列都是一种特殊的线性表。
- 栈的特点:后进先出,只能在表尾进行插入和删除。
- 队列的特点:先进先出,只能在表头删除,在表尾插入。
它们都可以用顺序存储和链式存储的方式。
栈常用顺序存储结构即数组的方式,因为数组的尾插尾删效率高,但可能会存在频繁开辟内存空间和内存空间浪费的问题。而栈的链式存储结构,解决了空间浪费的问题,但每个节点都存放一个指针域,也会存在一定的内存开销,并且在每次申请和释放节点的过程中也存在一定的时间开销。
队列常用链式存储结构即链表的方式,比链表多定义一个尾指针,解决尾插效率低的问题,并且不存在空间浪费。 而队列的顺序存储结构,由于插入需要挪动数据,效率低下,但循环队列可以解决这个问题,时间复杂度从O(N)变成了O(1),但仍存在内存空间浪费的问题。