1.栈
1.1栈的概念及结构
压栈:栈的插入操作叫做进栈 / 压栈 / 入栈, 入数据在栈顶 。出栈:栈的删除操作叫做出栈。 出数据也在栈顶 。
栈的这种结构是后进先出,也可以说是先进后出,有一个形象的比喻,就是给弹夹装子弹,先装的子弹被压到最底下,也最后被发射出去,还有就是浏览器的回退功能,仔细想想看是不是也是用栈实现的,最近浏览的网页弹出来。
1.2 栈的实现
接下来我们来实现一下栈,栈的基础功能有几个StackPush,StackPop,StackDestroy,StackTop,StackEmpty,还有一些,不是说代码中有这样一个栈的数据结构,而是我们根据概念来创建一种数据结构实现这样的功能,栈的功能不难,可以用顺序表和链表来实现,这里我们选用顺序表来实现,顺序表的实现前面我们讲的差不多,这里需要注意的几个点是,如下:
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
栈里面的Topi相当与顺序表中的size,然后其他的就和顺序表一样了,我们需要的是
入数据只能从栈顶入,出数据也只能从栈顶出,
//初始化
void StackInit(ST* pst);
//销毁
void StackDestroy(ST* pst);
//插入数据
void StackPush(ST* pst,STDataType x);
//删除数据
void StackPop(ST* pst);
//取栈顶数据
STDataType StackTop(ST* pst);
//判断是否为空
bool StackEmpty(ST* pst);
//元素个数
int StackSize(ST* pst);
以上是栈的相关接口函数,接口函数的实现难度并不难,接下来是它的接口函数的代码,更重要的是理解它的意义,这里同样也是对顺序表的一个回顾和更深的运用。
1.2.1 初始化和销毁
顺序表的初始话和销毁比较简单,直接free(pst->a)就行,
代码如下:
void StackInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->capacity = 0;
pst->top = 0;
}
void StackDestroy(ST* pst)
{
free(pst->a);
pst->a = NULL;
pst->capacity = 0;
pst->top = 0;
}
1.2.2 插入和删除
插入和顺序表一样,要检查数组空间是否足够,但是这里不需要把它额外封装成一个函数,因为这里只有插入函数需要使用,删除函数需要断言一下数组中是否有元素,代码如下:
void StackPush(ST* pst, STDataType x)
{
assert(pst);
if (pst->capacity == pst->top)
{
int newCapacity = (pst->capacity == 0 ? 4 : pst->capacity * 2);
STDataType* tmp = (STDataType*)realloc(pst->a,sizeof(STDataType) * newCapacity);//realloc
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
pst->a = tmp;
pst->capacity = newCapacity;
}
//pst->a = tmp;这句话放到这里是会报:未声明的标识符
pst->a[pst->top] = x;
pst->top++;
}
void StackPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
1.2.3 元素个数
因为Top可以表示数组最后一个元素的下一个位置,也指代数组中的元素个数,所以实现起来很简单,代码如下:
int StackSize(ST* pst)
{
assert(pst);
return pst->top;
}
1.2.4 判空
因为Top可以表示数据个数,所以也能用来判空,代码如下:
bool StackEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
1.2.5 取栈顶数据
因为有Top,所以取栈顶数据也变得简单起来了,代码如下:
STDataType StackTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
以上就是栈的代码实现,所以栈没有很复杂很难以理解的代码,多学习,多敲代码就能学会
下面来看一下测试代码:
先初始化,然后往里面插入数据,打印一下里面的size,然后是循环打印直到链表里面是空的,最后再销毁。
2. 队列
2.1 队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)入队列:进行插入操作的一端称为 队尾出队列:进行删除操作的一端称为 队头
队列和栈相反,是先进先出,
2.2 栈的实现
队列分对头和队尾,队列的实现有些难度,这里我们用单链表来实现队列,关键是理解两个结构体之间的关系,以及为什么要把定义两个结构体,先看代码:
typedef int QDataType;
typedef struct QueueNode
{
QDataType val;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* ptail;
size_t size;
}Queue;
为什么要定义两个结构体?如果没有Queue这个结构体,取对头的数据容易,但是取队尾的数据就需要遍历一遍,比较麻烦且效率不高。
这两个结构体之间的关系?Queue里面的结构体可以指向QueueNode中的头节点和尾节点,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);
//元素个数
size_t QueueSize(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
2.2.1 初始化和销毁
接下来实现一下队列,和栈一样,队列也有初始化和销毁函数,但会由于对列是使用单链表实现的,所以销毁时需要一个一个节点释放空间,代码如下:
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
QNode* cur = pq->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
2.2.2 插入和删除
队列的插入和删除函数需要插入函数和头插差不多,就是插入完后需要改变ptail的位置,需要注意的时,删除函数可能会把头节点的一直删除,删到了ptail的位置还删除,这是ptail就是野指针了,
所以当ptail==NULL的时候,我们需要单独把ptail赋值为NULL,还有需要注意的是,插入完数据需要将size++,删除数据需要将size--,代码如下:
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
if (pq->phead == 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* del = pq->phead;
pq->phead = pq->phead->next;
free(del);
del = NULL;
if (pq->phead == NULL)
{
pq->ptail = NULL;
}
pq->size--;
}
2.2.3 取对头队尾数据
取对头队尾数据就比较简单了,因为有Queue这个结构体,所以可以直接取到,别忘了断言一下,队列是否为空就行。代码如下:
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;
}
2.2.4 元素个数
因为Queue中有一个size的变量用来记录元素个数,所以很简单
size_t QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
2.2.5 判空
判空是用判断phead==NULL来判断的,
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;//pq->size == 0;
}
下面是队列的测试结果:
3. 代码
3.1 Stack.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
//初始化
void StackInit(ST* pst);
//销毁
void StackDestroy(ST* pst);
//插入数据
void StackPush(ST* pst,STDataType x);
//删除数据
void StackPop(ST* pst);
//取栈顶数据
STDataType StackTop(ST* pst);
//判断是否为空
bool StackEmpty(ST* pst);
//元素个数
int StackSize(ST* pst);
3.2 Queue.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
QDataType val;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* ptail;
size_t 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);
//元素个数
size_t QueueSize(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
3.3 Stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
void StackInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->capacity = 0;
pst->top = 0;
}
void StackDestroy(ST* pst)
{
free(pst->a);
pst->a = NULL;
pst->capacity = 0;
pst->top = 0;
}
void StackPush(ST* pst, STDataType x)
{
assert(pst);
if (pst->capacity == pst->top)
{
int newCapacity = (pst->capacity == 0 ? 4 : pst->capacity * 2);
STDataType* tmp = (STDataType*)realloc(pst->a,sizeof(STDataType) * newCapacity);//realloc
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
pst->a = tmp;
pst->capacity = newCapacity;
}
//pst->a = tmp;这句话放到这里是会报:未声明的标识符
pst->a[pst->top] = x;
pst->top++;
}
void StackPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
STDataType StackTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
bool StackEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
int StackSize(ST* pst)
{
assert(pst);
return pst->top;
}
3.4 Queue.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
QNode* cur = pq->phead;
while (cur)
{
QNode* 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");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
if (pq->phead == 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* del = pq->phead;
pq->phead = pq->phead->next;
free(del);
del = NULL;
if (pq->phead == NULL)
{
pq->ptail = NULL;
}
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;
}
size_t QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;//pq->size == 0;
}
3.5 Test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
#include "Stack.h"
//Queue
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePush(&q, 5);
QueuePush(&q, 6);
QueuePush(&q, 7);
printf("Size == %zd\n",QueueSize(&q));
QueuePop(&q);
printf("Back == %d\n", QueueBack(&q));
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
QueueDestroy(&q);
return 0;
}
//Stack
int main()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
StackPush(&st, 6);
StackPush(&st, 7);
printf("Size == %d\n", StackSize(&st));
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
StackPop(&st);
}
StackDestroy(&st);
return 0;
}
4. 写在最后
最近在学数据结构,时间比上次利用的多了,也少了些许恐惧,不过还是会胡思乱想,路还很长,还有很多东西要学,我们都要加油,要努力,最重要的是坚持下去,路漫漫其修远兮,吾将上下而求索,要无惧,要坚持,别害怕学Linux,别害怕学Linux,加油!加油!加油!