目录
1.首先,我们要创建一个结构体,结构体的成员我们设置一个数组指针,和两个整形变量 一个变量top用来记录栈顶元素的位置,可以理解为下标,另一个变量capacity用来表示栈的大小
2.创建数组栈基本框架,Init(初始化)、Destroy(销毁)、Push(插入)、Pop(删除)、Top(返回栈顶元素)、Empty(检查栈是否为空)、Size(计算栈内数据个数)
前言
相信很多小伙伴都接触过栈和队列,对于栈和队列的底层逻辑和基本框架,我想和小伙伴们分享一下我学习的心得,如有不对的地方,还请大牛们多多指点
一、栈的定义与实现
1.栈的定义
栈(Stack)是只允许在一端进行插入或删除操作的线性表,并且栈对于插入和删除的顺序也是有限制的,栈的底层逻辑是后进先出,什么意思呢,就是后进栈的先出栈,如果有学过函数栈帧的小伙伴应该知道,栈是从栈顶入,栈顶出,压栈的时候会把数据压到栈底,取数据,也就是删除数据的时候是从栈顶取,这也就很好的体现了栈插入和删除的特点,如下图
2.栈的实现
栈的实现其实可以是数组栈,也可以用链式栈,但是数组栈有一个优势,那就是数组的物理内存是连续的,cpu在读取数据的时候会先到缓存里看数据是否存在,不存在就会把数据加载到缓存,然后在访问,重点是在加入缓存的时候是以长段连续的数据进行加载,这就很好体现了数组的优势,所以小编在这里就用数组栈来实现
1.首先,我们要创建一个结构体,结构体的成员我们设置一个数组指针,和两个整形变量 一个变量top用来记录栈顶元素的位置,可以理解为下标,另一个变量capacity用来表示栈的大小
注意,如果你的top初始化值为0,则表示的是栈顶元素的下一个元素的位置,你也可以让top表示栈顶元素的位置,那么你初始化的时候就用改把top赋值为-1,两个底层逻辑,后面的逻辑都得遵循你初始化的时候top的值
代码
#pragma once #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> //bool函数,要么返回true,要么返回false typedef int STDataType; typedef struct Stack { STDataType* a; int top; //表示栈顶位置的 int capacity; //栈的大小 }ST;
2.创建数组栈基本框架,Init(初始化)、Destroy(销毁)、Push(插入)、Pop(删除)、Top(返回栈顶元素)、Empty(检查栈是否为空)、Size(计算栈内数据个数)
//队列初始化 void QueueInit(Queue* pq); //队列销毁 void QueueDestroy(Queue* pq); //队列插入数据 void QueuePush(Queue* pq, QueueDataType x); //队列删除数据 void QueuePop(Queue* pq); //队列从头取数据 QueueDataType QueueTopFront(Queue* pq); //队列从尾取数据 QueueDataType QueueTopBack(Queue* pq); //判断队列是否为空 bool QueueEmpty(Queue* pq); //计算队列数据个数 int QueueSize(Queue* pq);
3.各端口的实现
#define _CRT_SECURE_NO_WARNINGS 1 #include "Stack.h" //我这里是用的另一个.c文件写的函数实现过程,所以得引自己的头文件 //栈的初始化 void STInit(ST* pst) { assert(pst); pst->a = NULL; pst->capacity = 0; //注意,你可以有两个逻辑,一个是让top表示栈顶元素的下标,另一个是让top表示栈顶元素的下一个元素的下标 //pst->top = -1 //这里的top表示的就是栈顶元素的位置 pst->top = 0; // 这里的top表示栈顶元素的下一个元素的位置 } //栈的销毁 void STDestroy(ST* pst) { free(pst->a); pst->a = NULL; pst->top = pst->capacity = 0; //pst->top+1 = pst->capacity = 0; // top = -1的写法 } //栈顶插入数据 void STPush(ST* pst, STDataType x) { assert(pst); //判断栈是否满了,满了就扩容 if (pst->top == pst->capacity) //if(pst->top+1 == pst->capacity) //top = -1 的写法 { STDataType newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2; STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType)*newcapacity); if (tmp == NULL) { perror("ralloc fail"); return; } pst->a = tmp; pst->capacity = newcapacity; } //如果你用的逻辑是top = -1 表示栈顶元素的位置的话,这里得先让top往后走,不然就会越界 //pst->top++; //如果初始化用的top=-1,这里让top先往后走 pst->a[pst->top] = x; pst->top++; } //栈顶删除数据(弹出) void STPop(ST* pst) { assert(pst); //断言pst是否为空指针 //判断top是否大于0;如果等于0,减减就为-1了 ,需要断言一下 assert(pst->top > 0); //assert(pst->top+1>0) pst->top--; } //返回栈顶元素 STDataType STTop(ST* pst) { assert(pst); //断言top是否大于0,如果等于0,减减为-1,需要断言 assert(pst->top > 0);//assert(pst->top+1>0) return pst->a[pst->top - 1]; //返回栈顶元素,栈的原则是后进先出,所以[top-1]是栈顶元素的地址 //return pst->a[pst->top]; //这是top=-1的写法 } //判断栈是否为空 bool STEmpty(ST* pst) { assert(pst); return pst->top == 0; //如果top等于0返回true,表示栈为空,如果不为0返回fail,表示栈不为空 //return pst->top+1 == 0; // 这是top = -1 的写法 } //计算栈内的数据个数 int STSize(ST* pst) { assert(pst); //top表示的是第二个元素的下标,所以top为栈元素的个数 ,如果把top定义为-1的话,这里就得+1 return pst->top; //return pst->top+1; // 这是top = -1 的写法 }
3.简单的测试一下
#include "Stack.h" int main() { ST s; STInit(&s); STPush(&s, 1); STPush(&s, 2); STPush(&s, 3); STPush(&s, 4); STPush(&s, 5); STPush(&s, 6); STPush(&s, 7); while (!STEmpty(&s)) //当返回true的时候表示栈为空,所以这里要判断如果栈不为空才进入循环,也就是返回值为fail才会进入循环 { printf("%d ", STTop(&s)); //打印之后弹出那个元素 STPop(&s); } printf("\n"); STDestroy(&s); return 0; }
二、队列的定义与实现
1.队列的定义
队列(queue)是只允许在一端进行插入操作,在另一端进行删除操作的线性表,简称“队”。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。
允许插入的一端称为队尾(tail),允许删除的一端称为队头(front)。
向队列中插入新的数据元素称为入队,新入队的元素就成为了队列的队尾元素。
从队列中删除队头元素称为出队,其后继元素成为新的队头元素。
2.队列基础框架的实现
队列的实现我推荐的方法是单项不带头链表,当然有感兴趣的小伙伴也可以尝试一下用双向链表实现,如果用单链表实现的话,就得考虑一个问题,怎么找尾,我们实现单链表的时候,之所以没有单独定义一个为节点指针是因为我们不仅得处理尾插,还得处理尾删,所以就很麻烦,但是这里队列的逻辑是只能头出尾插,所以就可以单独定义一个尾指针,那么就得传二级指针,因为插入数据的时候需要修改指针的值,还有一种方法可以不用二级指针,那就是单独创建一个结构体,把头指针和尾指针放到结构体里,这样就可以通过结构体指针解引用来修改头指针和尾指针。
代码如下:
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> typedef int QueueDataType; typedef struct QueueNode { QueueDataType val; struct QueueNode* next; }QNode; typedef struct Queue { QNode* ptail; QNode* phead; int size; }Queue; //队列初始化 void QueueInit(Queue* pq); //队列销毁 void QueueDestroy(Queue* pq); //队列插入数据 void QueuePush(Queue* pq, QueueDataType x); //队列删除数据 void QueuePop(Queue* pq); //队列从头取数据 QueueDataType QueueTopFront(Queue* pq); //队列从尾取数据 QueueDataType QueueTopBack(Queue* pq); //判断队列是否为空 bool QueueEmpty(Queue* pq); // bool函数返回值要么为true ,要么为false,需引头文件#include<stdbool.h> //计算队列数据个数 int QueueSize(Queue* pq);
队列基本框架各函数端口的实现:
#define _CRT_SECURE_NO_WARNINGS 1 #include "Queue.h" //我这里是用的另外的.c文件写的,所以需要引自己的头文件 //队列初始化 void QueueInit(Queue* pq) { //断言是否为空指针 assert(pq); pq->phead = pq->ptail = NULL; pq->size = 0; } //队列销毁 void QueueDestroy(Queue* pq) { assert(pq); QNode* cur = pq->phead; while (cur) { QNode* next = cur->next; free(cur); cur = next; } pq->phead = pq->ptail = NULL; pq->size = 0; } //队列插入数据 void QueuePush(Queue* pq, QueueDataType x) { assert(pq); QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("malloc fail"); return; } newnode->next = NULL; newnode->val = x; if (pq->ptail == NULL) { pq->ptail = pq->phead = newnode; } else { pq->ptail->next = newnode; pq->ptail = newnode; } pq->size++; } //队列删除数据 void QueuePop(Queue* pq) { assert(pq); assert(pq->phead); QNode* cur = pq->phead; pq->phead = pq->phead->next; free(cur); if (pq->phead == NULL) { pq->ptail = NULL; } pq->size--; } //队列从头取数据 QueueDataType QueueTopFront(Queue* pq) { assert(pq); assert(pq->phead); return pq->phead->val; } //队列从尾取数据 QueueDataType QueueTopBack(Queue* pq) { assert(pq); assert(pq->ptail); return pq->ptail->val; } //判断队列是否为空 bool QueueEmpty(Queue* pq) { assert(pq); return pq->phead == NULL; } //计算队列数据个数 int QueueSize(Queue* pq) { assert(pq); return pq->size; }
下面我们来测试一下,我这里是尾差数据 1 2 3 4 5 6 ,然后从头取出数据,所以打印结果是 1 2 3 4 5 6
#define _CRT_SECURE_NO_WARNINGS 1 #include "Queue.h" void Test1() { Queue q; QueueInit(&q); QueuePush(&q, 1); QueuePush(&q, 2); QueuePush(&q, 3); QueuePush(&q, 4); QueuePush(&q, 5); QueuePush(&q, 6); while (!QueueEmpty(&q)) { printf("%d ", QueueTopFront(&q)); QueuePop(&q); } QueueDestroy(&q); } int main() { Test1(); return 0; }
总结
栈和队列的实现并不是固定的,取决于小伙伴们想用哪种方法实现,但是方法肯定也有好和不好之分,小编的建议是栈最好用顺序表实现,也就是数组栈,队列则用链表实现比较好,至于是单链表还是双链表,是带头还是不带头,这个小伙伴们都可以自由发挥,好啦,本期的经验分享就到这里啦,觉得小编写的还可以的记得点赞转发加关注哦~~~