一、栈
1、栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈,出数据也在栈顶。
2、栈的实现
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。
Note:
- 数组栈有一个缺点,就是内存不够时需要增容,但是增容的消耗也还好。
- 链式栈删除的话:如果用表尾做栈顶,尾插尾删,要设计成双向链表,否则删除数据效率低;如果用表头做栈顶,头插头删,就可以设计成单链表。
两种都可以,非要选一种的话,首推数组栈。链式栈相比于数组栈的优势只是避免了空间浪费,但除非是很需要节省空间,否则数组栈更好一些。
定义栈的结构
// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
typedef int STDataType;
#define N 10
typedef struct Stack
{
STDataType a[N];
int top; // 栈顶
}Stack;
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; // 栈顶
int capacity; // 容量
}Stack;
初始化栈
void StackInit(ST* ps) {
assert(ps); //给的结构体指针不能为空
ps->a = NULL;
ps->top = 0; //top也可以给-1;
//初始化时,top给0的话,意味着top指向栈顶数据的下一位;先放数据再加一
//初始化时,top给-1的话,意味着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 = realloc(ps->a, sizeof(STDataType) * newcapacity);
if(tmp == NULL) {
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
出栈
void StackPop(ST* ps) {
assert(ps);
assert(ps->top > 0);
ps->top--;
}
获取栈顶元素
STDataType StackTop(ST* ps) {
assert(ps);
assert(!StackEmpty(ps));//不写这句话会有bug,不断言会产生随机值,越界访问
//assert(ps->top > 0);
return ps->a[ps->top - 1];
}
获取栈中有效元素个数
int StackSize(ST* ps) {
assert(ps);
return ps->top;
}
检测栈是否为空,如果为空返回true,如果不为空返回false
bool StackEmpty(ST* ps) {
assert(ps);
/*if (ps->top == 0) {
return true;
}
else {
return false;
}*/
//三目操作符也可以
return ps->top == 0;
}
销毁栈
void StackDestroy(ST* ps) {
assert(ps);
free(ps->a);//free不是free的指针,而是free指针所指的空间
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
二、队列
2.1队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头
2.2队列的实现
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。需要移动结点(循环队列除外)
实现单链表只一个头指针,实现队列头插有头指针,实现队列尾删有尾指针
定义队列的结构
// 链式结构:表示队列
// 队列的结构
typedef int QDataType;
typedef struct QueueNode {
QDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue {
QueueNode* head;
QueueNode* tail;//单链表尾指针实现意义不大
//单链表尾删还是得找尾指针前的结点,故引入带头双向循环链表
//队列不需要尾删,只是尾插,故用尾指针更好
}Queue;
初始化队列
这里不需要二级指针,只是把结构体的地址传过去,改结构体里的指针就可以了;改指针变量才传二级指针
Note:关于函数传值调用还是传址调用
你有单个值就定义单个值,你有多个值就定义结构体,你要改变什么就要传它的地址,你要改变一级指针就要传它的地址(二级指针),你要改变结构就要传它的结构体指针(地址),你要改变单个值就要传单个值的指针(地址)
void QueueInit(Queue* pq) {//有些书上是Queue &pq cpp引用的语法
assert(pq);//目前写的队列情况不可能为空,故不会出现断言失败的情况
//为空不敢去断言,不可能随便就让程序退出
pq->head = NULL;//不带头结点(哨兵位),因为这个对队列的实现没有价值
pq->tail = NULL;
}
队尾入队列
void QueuePush(Queue* pq, QDataType x) {
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
newnode->data = x;
newnode->next = NULL;
if (pq->head == NULL) {
pq->head = pq->tail = newnode;
}
else {
pq->tail->next = newnode;
pq->tail = newnode;
}
}
队头出队列
void QueuePop(Queue* pq) {
assert(pq);
assert(!QueueEmpty(pq));
QueueNode* next = pq->head->next;
free(pq->head); //free不是free的指针,而是free指针所指的空间
pq->head = next;
if (pq->head == NULL) {//防止尾指针为野指针,会存在隐患
/取队尾数据/的接口函数会导致有隐患
pq->tail = NULL;
}
}
获取队列头部元素
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;
}
获取队列中有效元素个数
int QueueSize(Queue* pq) {//有些地方会在结构体里给size,直接访问就可以
//但一般面试不给
assert(pq);
int n = 0;
QueueNode* cur = pq->head;
while (cur) {
++n;
cur = cur->next;
}
return n;
}
检测队列是否为空,如果为空返回true,如果非空返回false
bool QueueEmpty(Queue* pq) {
assert(pq);
return pq->head == NULL;
}
销毁队列
void QueueDestroy(Queue* pq) {
assert(pq);
QueueNode* cur = pq->head;
while (cur != NULL) {//cur!=pq->tail会导致最后一个节点没删掉
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
2.3循环队列
另外扩展了解一下,实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。
Note:
循环队列特点:符合先进先出;空间大小是固定的
重点:循环队列,无论是用数组实现还是链表实现,都要多开辟一个空间,就是意味着,要是一个存k个数据的循环队列,要开辟k+1个空间,否则无法实现判空和队满
数组实现 Front=rear空 (Tail+1)%(k+1)=front满
链表实现 Front=rear空 rear->next=front 满