栈
栈的概念及结构
栈是一种特殊的线性表。其只允许在固定一端操作数据。进行插入数据和删除数据的一端叫做栈顶,另一端叫做栈底。删除数据操作称为出栈,插入数据称为入栈,压栈,进栈。其进行数据操作遵循LIFO(last in first out) 后进先出的原则。
栈的实现
根据栈的性质,构建栈的方式用两种:
- 链表,插入数据就是头插操作。
- 数组,插入数据就是尾插。
这两种方法都可以实现栈。但是,选择数组会跟好一些,为什么?当我们进行插入数据时,链表需要找到最后一个结点,需要遍历一遍,数组直接根据下标找到最后一个位置。
如果在链表中加一个尾指针指向最后一个位置,似乎可以解决这个问题。但是,怎么让尾指针指向上个结点呢?那就要遍历一遍了,这样就增加负担了。所以,使用数组是较好的选择。
那么接下来用数组实现栈
//Stack.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<malloc.h>
#include<stdlib.h>
typedef int STDataType;
#define CAPACITY 4
//栈的结构有数组,有效元素和容量构成
typedef struct Stack {
int* s;
int top;
int capacity;
}ST;
void STInit(ST* ps);//栈的初始化
void STDestroy(ST* ps);//栈的销毁
bool STEmpty(ST* ps);//判断栈是否为空
void STPush(ST* ps, STDataType x);//进行插入数据
void STPop(ST* ps);//删除数据
STDataType STTop(ST* ps);//寻找栈顶的数据
int STSize(ST* ps);//查看栈有几个元素
//Stack.c
#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
void STInit(ST* ps)
{
assert(ps);//栈不能为空
ps->s = (STDataType*)malloc(sizeof(STDataType) * CAPACITY);//malloc开辟空间
if (ps->s == NULL)
{
perror("malloc fail");//开辟失败报错
return;
}
ps->top = 0;//将top置为0,代表数据的个数。赋值为多少无所谓,看个人习惯
ps->capacity = CAPACITY;//将容量先置为空
}
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
void STDestroy(ST* ps)
{
assert(ps);
free(ps->s);//释放空间
ps->s = NULL;//同时将指针置为空
ps->top = 0;
ps->capacity = 0;
}
void STPush(ST* ps, STDataType x)
{
assert(ps);
//要先检查容量,判断为不为满,满了进行扩容
//如果扩容步骤使用很多次,就可以写个扩容函数,只有一次就没必要
if (ps->top == ps->capacity)
{
//realloc开辟空间失败会返回NULL,所以使用一个新的指针变量
STDataType* new = (STDataType*)realloc(ps->s, sizeof(STDataType) * ps->capacity * 2);
if (new == NULL)
{
perror("realloc fail");
return;
}
ps->s = new;
ps->capacity *= 2;//开辟成功,容量变为之前的两倍
}
ps->s[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->s[ps->top - 1];//返回元素减一的数据
}
int STSize(ST* ps)
{
assert(ps);
return ps->top;//返回有效元素
}
//Test.c
#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
int main()
{
ST st;
STInit(&st);
STPush(&st, 1);
STPop(&st);
STDestroy(&st);
return 0;
}
队列
队列的概念及结构
队列是一种特殊的线性结构。其只允许在一端插入数据,在另一端删除数据。插入数据操作称为入队列,该位置为队尾,删除数据操作称为出队列,该位置为队头。队列的操作原则是FIFO(firtst in first out) 先进先出。
队列的实现
构建队列方式有两种:
- 动态数组。
- 链表。
使用链表较好。队列的性质是先进先出,数组如何实现先出?后面元素依次向前覆盖,时间复杂度很大,而链表只需要记住头指针就行,必要时加上尾指针。
//Queue.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<malloc.h>
#include<stdbool.h>
typedef int QDataType;
//这是结点的结构,但是队列是FIFO,所以要记录头指针和尾指针,方便尾入和头出
typedef struct QueueNode {
struct QueueNode* next;
QDataType data;
}QNode;
//我们需要头指针和尾指针,尾指针进行插入数据时是需要的,就不用遍历了浪费时间。size记录元素个数很有必要,在一些函数有重大意义
//多个数据可以用结构体来构建
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);//队列的头部删除数据
bool QueueEmpty(Queue* pq);//判断队列为不为空
int QueueSize(Queue* pq);//知道队列有几个有效元素
QDataType QueueFront(Queue* pq);//返回队列的首个元素
QDataType QueueBack(Queue* pq);//返回队列的尾元素
//Queue.c
#define _CRT_SECURE_NO_WARNINGS
#include"Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;//队列的初始化将两个指针置为空
pq->size = 0;//有效元素为0
}
void QueueDestroy(Queue* pq)
{
assert(pq);
//将一个结点指针指向头节点,作为循环的条件
QNode* cur = pq->head;
while (cur)
{
QNode* del = cur->next;//一个要删除的结点
free(cur);
//我这里写成了cur = cur->next,这是不对的,cur的内存已经释放了,还指向next,就是野指针。
//因为这个细节,导致一个题目死活过不了。
cur = del;//指向下一个结点
}
//销毁时及时将两个指针置为空,避免野指针的出现
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));//malloc申请新的结点
if (newnode == NULL)
{
perror("malloc fail");//申请失败报错并返回
return;
}
//如果为空队列,那么就要将指针同时指向新的结点
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else//这里没加else语句,那么if执行了的话就会混乱
{
pq->tail->next = newnode;
pq->tail = newnode;//同时将尾指针指向新的结点
}
//将尾结点的next指向新的结点
newnode->data = x;//新的结点进行赋值
newnode->next = NULL;
pq->size++;//有效元素进行加1
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));//空的队列就不能删除
QNode* del = pq->head;
pq->head = pq->head->next;//将头指针指向下个结点
free(del);//释放空间
del = NULL;
//这里要考虑只有一个节点的时候,tail可能会是野指针
if (pq->head == NULL)
{
pq->tail = NULL;
}
pq->size--;//有效元素减1
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;//有效元素为0,那么就是空
}
int QueueSize(Queue* pq)
{
assert(pq);
return 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;//返回队尾数据
}
//Test.c
#define _CRT_SECURE_NO_WARNINGS
#include"Queue.h"
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
//根据队列的性质,只能取队头打印,然后删除数据,才能打印下个队头
printf("%d ", q.head->data);
QueuePop(&q);
printf("%d ", q.head->data);
QueuePop(&q);
printf("%d ", q.head->data);
QueuePop(&q);
printf("%d ", q.head->data);
QueuePop(&q);
QueueDestroy(&q);
return 0;
}
实现了链表和顺序表的基础上,再来实现栈和队列就简单多了,