1. 栈
1.1 栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一段称为栈顶,另一端称为栈底。
栈中的数据元素遵守 后进先出 LIFO(Last In First Out)的原则,后进来的数据先出去
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
出栈:栈的删除操作叫做出栈。出数据也在栈顶
1.2 栈的实现
我们先分析一下,栈我们应该用顺序表还是链表实现?
栈中的数据的插入和删除都是在栈顶实现的,不需要随机位置的插入删除,此时链表的优势就不大了。相比之下顺序表还有内存命中率高的优点,所以最后我们选择用顺序表实现栈
头文件,源文件,测试文件这里我就不展示了,直接写栈中功能的实现
1.2.1 栈的初始化和销毁
这个没什么好说的,直接上代码
1.2.2 判断栈是否真是空
1.2.3 压栈
压栈涉及到扩容,如果栈内空间不够就要扩容,栈满的判断标准就是 栈顶top 和 总空间大小capacity 数值相同,然后压完之后不要忘记top++
1.2.4 出栈
这个很简单,让栈顶回退一个位置就行了
1.2.5 取栈顶元素
这里要注意我们在取栈顶的时候要取 top-1 因为我们在初始化的时候设定的top为0,实际上此时的栈顶指向了一个空的位置,之后我们每压一次栈top都会向后移动一下,指向下一个空的位置,所以我们实际取栈顶的时候要 top-1 或者说,top是指向了栈顶的下一个元素
1.2.6 返回栈的大小(多少有效数据)
这没啥好说的,上代码
1.3 栈的完整代码
Stack.h
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>typedef int STDataType;
typedef struct Stack
{STDataType* a;int top; //栈顶int capacity;
}ST;void STInit(ST* ps);
void STDestory(ST* ps);//压栈
void STPush(ST* ps, STDataType x);
//出栈
void STPop(ST* ps);//取栈顶元素
STDataType STTop(ST* ps);int STSize(ST* ps);bool STEmpty(ST* ps);
Stack.c
#include"Stack.h"//初始化
void STInit(ST* ps)
{assert(ps);ps->a = NULL;ps->top = 0;ps->capacity = 0;
}//销毁
void STDestory(ST* ps)
{free(ps->a);ps->a = NULL;ps->top = ps->capacity = 0;
}//压栈
void STPush(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, newcapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail");exit(-1);}ps->a = tmp;ps->capacity = newcapacity;}ps->a[ps->top] = x;ps->top++;
}//出栈
void STPop(ST* ps)
{assert(ps);//栈不为空才能删assert(!STEmpty(ps));ps->top--;
}//取栈顶元素
STDataType STTop(ST* ps)
{assert(ps);assert(!STEmpty(ps));return ps->a[ps->top-1];
}//返回栈的大小(多少有效数据)
int STSize(ST* ps)
{assert(ps);return ps->top;
}//判断栈是否真是空
bool STEmpty(ST* ps)
{assert(ps);//如果真是空就返回真//如果不是空就返回假return ps->top == 0;
}
2. 队列
2.1 队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(first in first out)特点
入队列:进行插入操作的一端为队尾
出队列:进行删除操作的一端为队头
2.2 队列的实现
首先我们分析一下应该怎么实现队列
如果用顺序表的话,数据从前向后依次存放,此操作还比较方便,但是到删除时,从前向后删除,每删一个元素,就要将后面的元素向前挪一位,时间复杂度O(N)
所以推荐使用链表解决问题,尾插数据,头删数据,就像上面画的那个类似管道的示意图一样,此时问题又来了,尾插数据需要遍历链表,这样时间复杂度的问题依旧没解决,怎么办?
解决方案就是我们再引入一个新的结构体,专门用来维护这条链表,新的结构体中存放这个链表的头节点和尾节点的地址,还有这个链表的长度,这样只需将新节点尾插到ptail后面就好了,不用遍历链表了。
创建链表节点:
创建新结构体用于维护链表:
之后在主函数中用也这个新结构体来代替链表头节点进行传参,或者说这个新结构体代替的整条链表,而链表头节点只是一个节点,孰强孰弱,一目了然。
2.2.1 队列的初始化和销毁
因为我们创建的链表是不带头的,所以初始化都是无脑置空就好
2.2.2 队列中剩余元素
2.2.3 入队列
2.2.4 出队列
出队列要注意链表的三种状态,分别是链表尾空、链表只有一个节点、链表有多个节点。为空的情况用断言处理,多个节点就正常删除头节点就行,但是要注意只有一个节点的时候要处理ptail 否则它将变成野指针
2.2.5 取队头、取队尾
2.2.6 判断队列为空
2.3 队列的完整代码
Queue.h
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>typedef int QDataType;
typedef struct QueueNode
{QDataType val;struct QueueNode* next;
}QNode;typedef struct Queue
{QNode* phead;QNode* ptail;int size;
}Queue;//初始化
void QueueInit(Queue* pq);//销毁
void QueueDestory(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);
Queue.c
#include"Queue.h"//初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = NULL;pq->ptail = NULL;pq->size = 0;
}//销毁
void QueueDestory(Queue* pq)
{assert(pq);QNode* cur = pq->phead;QNode* next = NULL;while (cur){next = cur->next;free(cur);cur = next;}pq->phead = NULL;pq->ptail = NULL;pq->size = 0;
}//入队列
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode=(QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");return;}newnode->val = x;newnode->next = NULL;if (pq->ptail == NULL){pq->phead = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;
}//出队列
void QueuePop(Queue* pq)
{assert(pq);//保正头部有的删assert(pq->phead);QNode* next = pq->phead->next;//当只有一个节点时,要处理ptailif (pq->phead == pq->ptail){pq->ptail = NULL;}free(pq->phead);pq->phead = next;pq->size--;
}//取队头
QDataType QueueFront(Queue* pq)
{assert(pq);//取队头前提是有队头assert(pq->phead);return pq->phead->val;
}//取队尾
QDataType QueueBack(Queue* pq)
{assert(pq);//取队尾前提是有队尾assert(pq->ptail);return pq->ptail->val;
}//判断队列为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}//队列中剩余元素
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}