一、栈
- 我们使用数组实现栈结构。
快速复习
-
结构定义:
顺序表实现栈,
定义顺序表结构体:指针,大小,容量。 -
相关函数:
-
构造析构:
-
初始化:分配初始空间,设置大小容量。
-
销毁:释放动态内存,大小容量清零。
-
-
增删查改:
-
遍历:普通循环遍历
-
压栈:判满,扩容,赋值,top++;
-
弹栈:判空,取top-1数据,top–,返回数据;
-
栈顶:判空,返回top-1数据;
-
-
判空判满:
-
大小:return top;
-
判空:return top == 0;
-
判满:return top == capacity
-
-
1.结构的声明及定义
typedef int STDataType;
typedef struct stack{
STDataType *arr;
int top;
int capacity;
}ST;
ST st;
StackInit(&st);
2.初始化栈
void StackInit(ST *pst){
assert(pst != NULL);
pst->capacity = 0;
pst->top = 0;
pst->arr = NULL;
}
- top可以初识化为0,压栈时top位置先赋值再++,此时top表示栈顶元素的下一个位置。
- top还可以初始化为-1,压栈时top先++再赋值,此时top表示栈顶元素的位置。
2.压栈
void StackPush(ST *pst, STDataType val){
assert(pst != NULL);
//检查是否需要扩容
if (Stacksize(pst) == pst->capacity){
int newcapacity = pst->capacity == 0 ? 5 : pst->capacity * 2;
STDataType *tmp = (STDataType*)realloc(pst->arr, newcapacity * sizeof(STDataType));
if (tmp == NULL){
perror("StackPust");
exit(1);
}
pst->arr = tmp;
pst->capacity = newcapacity;
}
//压栈
pst->arr[pst->top] = val;
pst->top++;
}
3.弹栈
void StackPop(ST *pst){
assert(pst != NULL);
assert(!StackEmpty(pst));
pst->top--;
}
STDataType StackTop(ST *pst){
assert(pst != NULL);
assert(!StackEmpty(pst));
return pst->arr[pst->top - 1];
}
void Test2(){
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
while (!StackEmpty(&st)){
printf("%d ", StackTop(&st));
StackPop(&st);
}
}
- 弹栈操作需提前检测栈是否为空
- 注意,这里返回的是top-1位置的值
4.销毁栈
void StackDestroy(ST *pst){
assert(pst != NULL);
free(pst->arr);
pst->arr = NULL;
pst->capacity = 0;
pst->top = 0;
}
5.获取栈中有效元素个数
int Stacksize(ST *pst){
assert(pst != NULL);
return pst->top;
}
6.检测栈是否为空
bool StackEmpty(ST *pst){
assert(pst != NULL);
return pst->top == 0;
}
二、队列
- 由于队列队头出队,队尾入队的性质,使用链表实现队列更为方便。
快速复习
-
结构定义:
单链表(可带头)实现队列,
定义节点结构体:数据,指针;
定义队列结构体:头、尾指针,大小。 -
相关函数:
-
构造析构
-
初始化:将头尾指针置空,设置大小;
-
销毁:释放各节点,直到NULL;(删空改尾指针)
-
-
增删查改
-
遍历:遍历各节点数据,直到NULL;
-
入队:创建节点,链接到队尾,size++;(首添改头指针)
-
出队:判空,取数据,头删,size--,返回数据;(删空改尾指针)
-
取队头队尾:判空,返回队头/队尾数据;
-
-
判空判满
-
大小:return size;
-
判空:return head == NULL;
-
-
1.结构的声明及定义
typedef int QDataType;
//声明链表的节点
typedef struct QueueNode{
QDataType data;
struct QueueNode *next;
}QueueNode;
//声明队列
typedef struct Queue{
struct QueueNode *head;//头指针
struct QueueNode *tail;//尾指针,方便尾插入队
}Queue;
Queue que;
QueueInit(&que);
2.初始化队列
void QueueInit(Queue *pq){
assert(pq!=NULL);
pq->head = NULL;
pq->tail = NULL;
}
3.队尾入队
void QueuePush(Queue *pq, QDataType val){
assert(pq != NULL);
QueueNode *newnode = (QueueNode*)malloc(sizeof(QueueNode));
newnode->data = val;
newnode->next = NULL;
//不带哨兵位的链表需要考虑改变头指针的指向
if (pq->head == NULL){
pq->head = pq->tail = newnode;
}
else{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
4.队头出队
void QueuePop(Queue *pq){
assert(pq != NULL);
assert(!QueueEmpty(pq));
QueueNode *del = pq->head;
pq->head = del->next;
free(del);
if (pq->head == NULL){
pq->tail = NULL;
}
}
队头出队需要注意两点:
- 需要提前检测队列是否为空
- 最后一个元素出队后,切记将tail置空。
5.获取队头、队尾元素
//获取队头元素
QDataType QueueFront(Queue *pq){
assert(pq != NULL);
assert(!QueueEmpty(pq));
return pq->head->data;
}
//获取队尾元素
QDataType QueueBack(Queue *pq){
assert(pq != NULL);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
- 注意提前检测队列是否为空
6.获取队列中有效元素的个数
int QueueSize(Queue *pq){
assert(pq != NULL);
QueueNode *cur = pq->head;
int n = 0;
while (cur != NULL){
cur = cur->next;
n++;
}
return n;
}
7.检测队列是否为空
bool QueueEmpty(Queue *pq){
assert(pq != NULL);
return pq->head == NULL;
}
8.销毁队列
void QueueDestroy(Queue *pq){
assert(pq != NULL);
QueueNode *del;
while (pq->head != NULL){
del = pq->head;
pq->head = del->next;
free(del);
}
pq->tail = NULL;
}
- 循环结束后,注意将tail指针置空。
三、循环队列
循环队列:
1. 符合先进先出
2. 循环队列的容量是固定的
3. 无论是数组实现还是链表实现,都要多开一个空间。存k个数据,开k+1个空间。否则无法实现判空和判满。
4. front == tail 为空
5. tail+1== front 为满(数组实现考虑循环)
循环队列可以采用循环链式结构或数组结构,一下的代码是链式结构的实现。想要看数组结构请点击这里 >>>>【【Leetcode算法|第3期】栈和队列(栈和队列的简单应用、循环队列)】
1.结构的声明及定义
typedef int CQueDataType;
typedef struct CirQueueNode{
CQueDataType data;
struct CirQueueNode *next;
}CQNode;
typedef struct CirQueue{
CQNode *head;
CQNode *tail;
int sz;
}CQue;
2.初始化循环队列
void CQueInit(CQue *pq, int k){
//开辟K+1个节点的内存空间
pq->head = pq->tail = (CQNode*)malloc(sizeof(CQNode));
for (int i = 0; i < k; i++)
{
CQNode *newnode = (CQNode*)malloc(sizeof(CQNode));
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->tail->next = pq->head;//首尾相连,构造环状结构
pq->tail = pq->head;//将队列置空
pq->sz = 0;
}
- 循环队列的容量是固定的
- 无论是数组实现还是链表实现,都要多开一个空间。存k个数据,开k+1个空间。否则无法实现判空和判满。
3.队尾入队
void CQuePush(CQue *pq, CQueDataType val){
assert(pq != NULL);
assert(!CQueFull(pq));
pq->tail = pq->tail->next;//先移动指针
pq->tail->data = val;//后赋值
pq->sz++;
}
- 入队前要先判满
- 这里先移动尾指针再赋值是为了让尾指针指向队尾元素,方便读取队尾元素的值。
- 当然也可以先赋值再移动指针,但此时尾指针指向的是队尾元素的后一个节点。而由于单链表指针不能向前移动,所以在读取队尾元素时必须需要从头遍历到尾才行。
4.队头出队
CQueDataType CQuePop(CQue *pq){
assert(pq != NULL);
assert(!CQueEmpty(pq));
CQueDataType data = pq->head->next->data;//取head->next
pq->head = pq->head->next;
pq->sz--;
return data;
}
- 出对前要先判空
- 由于入队时是先移动尾指针再赋值,所以读取队头元素的值时应该读取的是队头指针指向元素的下一个元素的值。
5.获取对头/队尾元素
CQueDataType CQueHead(CQue *pq){
assert(pq != NULL);
assert(!CQueEmpty(pq));
return pq->head->next->data;
}
CQueDataType CQueTail(CQue *pq){
assert(pq != NULL);
assert(!CQueEmpty(pq));
return pq->tail->data;
}
6.检测队列是否为空/满
bool CQueEmpty(CQue *pq){
assert(pq != NULL);
return pq->head == pq->tail;
}
bool CQueFull(CQue *pq){
assert(pq != NULL);
return pq->tail->next == pq->head;
}
int CQueSize(CQue *pq){
assert(pq != NULL);
return pq->sz;
/*int ct = 0;
CQNode *cur = pq->head;
while (cur != pq->tail)
{
ct++;
cur = cur->next;
}
return ct;*/
}
7.销毁队列
void CQueDestroy(CQue *pq){
assert(pq != NULL);
CQNode *cur = pq->head->next;
CQNode *del;
while (cur != pq->head){
del = cur;
cur = cur->next;
free(del);
}
free(pq->head);
pq->head = pq->tail = NULL;
pq->sz = 0;
}
注意:不能从队头指针销毁到队尾指针,否则无法将循环队列完全销毁会造成内存泄漏。