目录
栈的概念及其结构
栈是一种特殊的线性表,它的基本结构和顺序表大差不差,但是它遵循着先来后出的原则,这样的原则其实很像堆叠的书本,我们只取堆叠在最顶层的本子。
入栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
栈的实现
栈需要实现的功能不多,它更多的是借助本身特殊的数据存储与取出规则活跃在应用方面,比如解题或者是特殊的数据处理,所以我们只需要实现下面这些功能就好。
栈的实现可以用链表,也可以用数组,不过对于栈来说,数组在面对尾删的时候优势比链表大得多,所以我们使用数组。
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; // 栈顶
int capacity; // 容量
}Stack;
//初始化栈
void StackInit(Stack* ps);
//打印栈
void StackPrint(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
初始化栈
容量和栈顶都置零,数组指针置空
void StackInit(Stack* ps)
{
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
打印栈
跟顺序表是一样的打印规则
void StackPrint(Stack* ps)
{
assert(ps);
for (int i = 0; i < ps->top; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
入栈
入栈的过程和顺序表也是完全一样的。
void StackPush(Stack* ps, STDataType data)
{
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] = data;
ps->top++;
}
出栈
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
获取栈顶元素
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
if (ps->top == 0)
{
printf(" no data in this Stack!\n");
return;
}
return ps->a[ps->top - 1];
}
获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
if (ps->top == 0)
{
return 0;
}
return ps->top;
}
检测栈是否为空
检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps)
{
assert(ps);
if (ps->top)
return 0;
else
return 1;
}
销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
这样,一个具有基本功能的栈就实现好了。我们接下来实尝试现它的兄弟结构,队列。
队列
同结构栈的实现相同,我们可以选择使用数组或者链表去实现队列结构。
队列遵循着先进先出的原则:
综合考虑下来,对于链表尾插和头删的代价最小,为了实现取头部数据和头部数据删除的功能和尾部插入的功能,我们使用链表来实现这一结构。
而为了更好的管理,我们多创建两个指针去管理这个链表。
其实说穿了相当于对一个链表加上了两个指针来方便取队头的数据和尾插
我们还是以多项目的理念去实现这个结构,定义与声明分离。
队列的接口以及结构体的声明
第一个结构体用于实现链表的节点,第二个结构体则用来存放方便我们管理结构体的两个指针,为了更方便的获取到队列的大小,我多加了个size,如果不愿意也行,毕竟实现起来自己的想法最为重要,资料永远是参考。
同理,我们也可以不用一个结构体来装这两个指针,不过就有点麻烦了。
注意:我们创建的两个指针类型均为链表类型的指针,这样才可以间接的控制到链表。
// 链式结构:表示队列
typedef int QDataType;
typedef struct QListNode
{
QDataType data;
struct QListNode* next;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* head;
QNode* end;
int size;
}Queue;
接口:
// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
bool QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
初始化队列:
初始化不需要存入任何数据,我们将两个指针置空即可,在这里,我们在结构逻辑上希望这两个指针相当于控制了整个链表,也就是链表的上级,类似于控制数组的指针,开辟空间,指向头尾都由这两个指针来完成。
void QueueInit(Queue* q)
{
assert(q);
q->head = q->end = NULL;
q->size = 0;
}
入队列
创建节点,与前节点相连,同链表逻辑相同。
不过在这里,需要处理两种情况:
1.队列已有节点
2.队列内无节点
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(-1);
}
else
{
newnode ->data = data;
newnode ->next = NULL;
}
//若链表为空,也就是第一个数据,头尾指针都指向当前的节点
if (q->end == NULL)
{
q->end = q->head = newnode;
}
else
{
q->end->next = newnode;
q->end = newnode;
}
//长度计数
q->size++;
}
队头出队列
同链表头删相同
void QueuePop(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
if (q->head->next == NULL)
{
free(q->head);
q->head = q->end = NULL;
}
else
{
QNode* prev = q->head;
q->head = q->head->next;
free(prev);
prev = NULL;
}
q->size--;
}
获取队列头部元素
直接取出头部指针所指向节点的值即可
QDataType QueueFront(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->head->data;
}
获取队列队尾元素
QDataType QueueBack(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->end->data;
}
获取队列中有效元素个数
之前写的size就在这里可以很方便的使用了。
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
检测队列是否为空
如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q)
{
assert(q);
if (q->end == NULL || q->head == NULL)
{
return true;
}
else
return false;
}
销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->head;
while (cur)
{
QNode* del = cur;
cur = cur->next;
free(del);
}
q->head = q->end = NULL;
}
这样,一个具有基本功能的结构队列就写好了,我们运行起来玩玩。
栈和列表的运用
写了数据结构不拿来玩玩挺可惜的,做几道题可以更加深刻的理解数据结构
有效的括号
链接:力扣
这道题硬写还是非常难受的,需要判断的条件非常多,不过有了栈结构可以很方便的解决这道题。
主体的思路就是凑括号的逻辑实现,有两种需要判断的棘手情况
这两种判断的检测用栈才可以简单的应对,我们遍历整个字符串,每次将左括号放入栈内,遇到右括号的时候出栈,与栈顶的括号相比较,由于栈的先进后出特性,间接的完成了括号的成立判断。
主体逻辑如上,更多细节在代码段的注释内。
bool isValid(char * s){
Stack st;
StackInit(&st);
while (*s)
{
if(*s == '('||*s=='['||*s=='{')
{
StackPush(&st,*s);
}
else
{
//当数量不匹配,也就上来就是一个右括号,直接返回false
if(StackEmpty(&st))
{
StackDestroy(&st);
return false;
}
char top = StackTop(&st);
StackPop(&st);
if(top == '(' && *s != ')'
|| top == '[' && *s != ']'
|| top == '{' && *s != '}')
{
return false;
}
}
s++;
}
//当数量不匹配,也就是栈内部仍然还有数据的时候,也是直接返回false
bool flag = StackEmpty(&st);
StackDestroy(&st);
return flag;
}
队列实现栈
链接:力扣
简而言之,这道题主体思路就是如何利用队列的先进先出实现栈的先进后出。
我们其实使用两个队列就可以解决,既然队列是先进先出,而我们要让它实现先进后出,两个队列互相倒数据即可,我们保持其中一个队列为空,有数据的向空队列倒数据,传直到剩一个数据,然后取出,然后POP掉,保持至少有一个空队列。
这么说比较抽象,手绘了一个过程,画的简陋还望不要怪罪
1.
2.
3.
4.
5.
6.
7.
8.
所以整个代码中比较关键的是空列表的检测
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct QListNode
{
QDataType data;
struct QListNode* next;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(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);
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* del = cur;
cur = cur->next;
free(del);
}
pq->head = pq->tail = NULL;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
else
{
newnode->data = x;
newnode->next = NULL;
}
if (pq->tail == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* del = pq->head;
pq->head = pq->head->next;
free(del);
del = NULL;
}
pq->size--;
}
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;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL && pq->tail == NULL;
}
int QueueSize(Queue* pq)
{
assert(pq);
/*QNode* cur = pq->head;
int n = 0;
while (cur)
{
++n;
cur = cur->next;
}
return n;*/
return pq->size;
}
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
//初始化两个队列
QueueInit(&obj->q1);
QueueInit(&obj->q2);
return obj;
}
//入栈,挑一个不是空的队列压进去
void myStackPush(MyStack* obj, int x) {
if(!QueueEmpty(&obj->q1))
{
QueuePush(&obj->q1, x);
}
else
{
QueuePush(&obj->q2, x);
}
}
//出栈,找到那个不是空的队列,放进空的队列里
int myStackPop(MyStack* obj) {
Queue* isempty = &obj->q1;
Queue* noempty = &obj->q2;
if(!QueueEmpty(&obj->q1))
{
isempty = &obj->q2;
noempty = &obj->q1;
}
// 获取队列中有效元素个数
while(QueueSize(noempty)>1)
{
QueuePush(isempty,QueueFront(noempty));
QueuePop(noempty);
}
int top = QueueFront(noempty);
QueuePop(noempty);
return top;
}
int myStackTop(MyStack* obj) {
if(!QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q1);
}
else
{
return QueueBack(&obj->q2);
}
}
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
obj = NULL;
}
用栈实现队列
链接:力扣
这里的主体逻辑就是依靠栈的数据逆置的一些特点来实现,简单来说,负负得正。
两个栈,一个负责出,一个负责入,需要出数据的时候往出的栈里倒数据就好,当然前提是出的栈内部数据已经出完了才倒进去,不然会打乱顺序
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; // 栈顶
int capacity; // 容量
}Stack;
//初始化栈
void StackInit(Stack* ps);
//打印栈
void StackPrint(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
// 入栈
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
//够空间的话就不开空间直接赋值
//不够的话,也就是top此时=capaciy了,这个时候realoc一个新的空间;
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)
{
printf("realloc failed!");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = data;
ps->top++;
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top-1];
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
free(ps->a);
ps->capacity = ps->top = 0;
}
typedef struct {
Stack Pop;
Stack Push;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* obj =(MyQueue*) malloc(sizeof(MyQueue));
StackInit(&obj->Pop);
StackInit(&obj->Push);
return obj;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->Push,x);
}
void PushToPop(MyQueue* obj)
{
while (!StackEmpty(&obj->Push))
{
StackPush(&obj->Pop,StackTop(&obj->Push));
StackPop(&obj->Push);
}
}
//从队列的开头移除并返回元素
int myQueuePop(MyQueue* obj) {
if(StackEmpty(&obj->Pop))
PushToPop(obj);
int ret = StackTop(&obj->Pop);
StackPop(&obj->Pop);
return ret;
}
//返回队列开头的元素
int myQueuePeek(MyQueue* obj) {
if(StackEmpty(&obj->Pop))
PushToPop(obj);
return StackTop(&obj->Pop);
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->Push) && StackEmpty(&obj->Pop);
}
void myQueueFree(MyQueue* obj) {
StackDestroy(&obj->Push);
StackDestroy(&obj->Pop);
free(obj);
}
/**
* Your MyQueue struct will be instantiated and called as such:
* MyQueue* obj = myQueueCreate();
* myQueuePush(obj, x);
* int param_2 = myQueuePop(obj);
* int param_3 = myQueuePeek(obj);
* bool param_4 = myQueueEmpty(obj);
* myQueueFree(obj);
*/
循环队列
链接:力扣
可以用数组或者链表解决这个问题,循环队列最大的问题其实是无法判断空或者满,我们有两种方法解决·这个问题
为了更好的取到Back位置的数据,我们选用数组解决这个问题
typedef struct {
int* arr;
int N;
int back;
int front;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj =(MyCircularQueue*) malloc (sizeof(MyCircularQueue));
obj -> arr = (int*)malloc ((k+1) * sizeof (int));
obj ->front = 0 ;
obj ->back = 0;
obj ->N = k +1;
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->back;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
if((obj -> back+1)% obj ->N == obj ->front)
return true;
else
return false;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))
return false;
else
{
obj->arr[obj->back] = value;
obj->back++;
obj->back %= obj -> N;
return true;
}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return false;
else
{
obj->front++;
obj->front%= obj ->N;
return true;
}
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
else
return obj->arr[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
else if (obj ->back == 0)
return obj->arr[obj->N-1];
else
return obj->arr[obj->back-1];
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->arr);
free(obj);
}
/**
* Your MyCircularQueue struct will be instantiated and called as such:
* MyCircularQueue* obj = myCircularQueueCreate(k);
* bool param_1 = myCircularQueueEnQueue(obj, value);
* bool param_2 = myCircularQueueDeQueue(obj);
* int param_3 = myCircularQueueFront(obj);
* int param_4 = myCircularQueueRear(obj);
* bool param_5 = myCircularQueueIsEmpty(obj);
* bool param_6 = myCircularQueueIsFull(obj);
* myCircularQueueFree(obj);
*/
主要的关键还是在于利用%运算控制下标不要越界。
至此,这两种数据结构就记述到这了,希望对你有点帮助!感谢阅读!