栈和队列
文章目录
1.栈的概念与存储结构
栈的概念:
- 栈:是一种特殊的线性表,只允许在表尾进行插入和删除元素—表尾又称为栈顶,另一端称为栈底
- 压栈:栈的插入叫做压栈
- 出栈:栈的删除叫做出栈
- 栈遵守先进后出原则:FILO—(First In Last Out)
栈对线性表的插入和删除的位置做了限制,并没有对元素进出的时间进行限制,所以,在不是所有元素都进栈的情况下,事先进去的元素也可以出栈,只要保证是栈顶元素出栈就可以,所以栈是:一种入栈顺序,多种出栈顺序
比如:现在有元素1、2、3依次进栈,出栈顺序有哪些?
- 第一种:123(1进、1出、2进、2出、3进、3出)
- 第二种:321(1、2、3进,3、2、1出)
- 第三种:213(1、2进,2、1出,3进、3出)
- 第四种:132(1进、1出,2、3进,3、2出)
- 第五种:231(1、2进,2出,3进,3出,1出)
栈的存储结构:
- 栈的顺序存储结构:数组的首元素作为栈底,另外一端作为栈顶,同时定义一个变量 top 来记录栈顶元素在数组中的位置
- 栈的链式存储结构:单链表的尾部作为栈底,头部作为栈顶,方便插入和删除(进栈头插,出栈头删),头指针和栈顶指针 top 合二为一
2.队列的概念与存储结构
队列的概念:
- 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表
- 队列具有先进先出—FIFO(First In First Out)
- 入队列:进行插入操作的一端称为队尾
- 出队列:进行删除操作的一端称为队头
入队出队形式:一种入队顺序,只有一种出队顺序—这种结构非常适合用来排队、聊天
队列的存储结构:
- 队列的顺序结构:入队,不需要移动任何元素,时间复杂度为O(1)—出队,所有元素需要往前移动,时间复杂度为O(N)
- 队列的链式结构:入队(尾插),时间复杂度为O(1)—出队(头删),时间复杂度为O(1)
3.栈的基本实现
3.1 栈的功能API
//初始化栈
void StackInit(Stack* ps);
//销毁栈
void StackDestroy(Stack* ps);
//入栈
void StackPush(Stack* ps, STDataType x);
//出栈
void StackPop(Stack* ps);
//检测栈是否为空,如果为空返回true,否则返回NULL
bool StackEmpty(Stack* ps);
//获取栈中有效元素个数
int StackSize(Stack* ps);
//获取栈顶元素
STDataType StackTop(Stack* ps);
3.2 栈的功能API实现
0.栈的定义:
//静态栈 typedef int STDataType; //类型重命名,栈中元素类型先假设为int #define N 20 struct Stack { STDataType a[N]; //定长数组 int top; //记录栈顶位置 }SqStack; //动态栈 typedef int STDataType; //类型重命名,栈中元素类型先假设为int typedef struct Stack { STDataType* a; //指向动态开辟的数组 int top; //记录栈顶位置 int capacity; //栈的容量大小 }Stack;
1.栈的初始化:
//初始化栈 void StackInit(Stack* ps) { assert(ps); ps->a = NULL; ps->top = -1; ps->capacity = 0; }
2.栈的销毁:
//销毁栈 void StackDestroy(Stack* ps) { assert(ps); if (ps->a) { free(ps->a); } ps->a = NULL; ps->top = -1; ps->capacity = 0; }
3.入栈:
//入栈 void StackPush(Stack* ps, STDataType x) { assert(ps); if (ps->top == ps->capacity - 1) //检查栈空间是否满了 { //如果栈原始容量为0,新容量设为4,否则设为原始容量的2倍 int newcapacity = (ps->capacity == 0) ? 4 : (ps->capacity) * 2; //扩容至新容量 STDataType* temp = realloc(ps->a, newcapacity * sizeof(STDataType)); if (temp == NULL) { perror("realloc"); exit(-1); } ps->a = temp; //更新容量 ps->capacity = newcapacity; } ps->top++; //栈顶指针加一 ps->a[ps->top] = x; //将新增元素放入栈顶空间 }
4.出栈:
//出栈 void StackPop(Stack* ps) { assert(ps); assert(ps->top != -1); //栈不能为空 ps->top--; //栈顶指针减一 }
5.检测栈是否为空:
//检测栈是否为空,如果为空返回true,否则返回NULL bool StackEmpty(Stack* ps) { assert(ps); return ps->top == -1; }
6.获取栈中有效元素个数:
//获取栈中有效元素个数 int StackSize(Stack* ps) { assert(ps); return ps->top + 1; }
7.获取栈顶元素:
//获取栈顶元素 STDataType StackTop(Stack* ps) { assert(ps); assert(!StackEmpty(ps)); //栈不能为空 return ps->a[ps->top]; }
3.3 栈的功能API实现效果
测试代码:
void TestStack() //测试函数
{
Stack s;
//初始化栈
StackInit(&s);
//入栈
StackPush(&s, 1);
StackPush(&s, 2);
StackPush(&s, 3);
StackPush(&s, 4);
StackPush(&s, 5);
//出栈
while (!StackEmpty(&s))
{
printf("%d ", StackTop(&s)); //获取栈顶元素
StackPop(&s);
}
printf("\n");
//销毁栈
StackDestroy(&s);
}
效果:
4.队列的基本实现
4.1 队列的功能API
//初始化队列 void QueueInit(LinkQueue* pQ); //销毁队列 void QueueDestroy(LinkQueue* pQ); //入队(尾插) void QueuePush(LinkQueue* pQ, QDataType x); //出队(头删) void QueuePop(LinkQueue* pQ); //获取队列元素个数 int QueueSize(LinkQueue* pQ); //获取队头元素 QDataType QueueFront(LinkQueue* pQ); //获取队尾元素 QDataType QueueBack(LinkQueue* pQ); //检查队列是否为空 bool QueueEmpty(LinkQueue* pQ);
4.2 队列的功能API实现
0.队列的定义:
typedef int QDataType; typedef struct QueueNode //队列结点结构 { QDataType data; //结点数据 struct QueueNode* next; //结点指针 }QueueNode; typedef struct QueuePtr //队列的链式结构 { QueueNode* phead; //队头指针 QueueNode* ptail; //队尾指针 }LinkQueue;
1.队列的初始化:
//初始化队列 void QueueInit(LinkQueue* pQ) { assert(pQ); //队列为空 pQ->phead = pQ->ptail = NULL; }
2.队列的销毁:
//销毁队列 void QueueDestroy(LinkQueue* pQ) { assert(pQ); QueueNode* cur = pQ->phead; while (cur) //遍历链式队列 { QueueNode* next = cur->next; free(cur); cur = next; } cur = NULL; pQ->phead = pQ->ptail = NULL; //队列为空 }
3.入队(尾插):
//入队(尾插) void QueuePush(LinkQueue* pQ, QDataType x) { assert(pQ); QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode)); //动态申请一个节点 if (newnode == NULL) //检查是否申请成功 { perror("malloc"); exit(-1); } newnode->data = x; newnode->next = NULL; //尾节点next指针置空 if (pQ->phead == NULL) //队列为空 { pQ->phead = newnode; } else //队列不为空 { pQ->ptail->next = newnode; } pQ->ptail = newnode; //更新队尾指针 }
4.出队(头删):
//出队(头删) void QueuePop(LinkQueue* pQ) { assert(pQ); assert(!QueueEmpty(pQ)); //队列不能为空 if (pQ->phead == pQ->ptail) //队列中只有一个节点 { free(pQ->phead); pQ->phead = pQ->ptail = NULL; } else { QueueNode* next = pQ->phead->next; //记录头节点的直接后继 free(pQ->phead); //释放头节点 pQ->phead = next; //更新队头指针 } }
5.获取队列元素个数:
//获取队列元素个数 //如果会频繁调用这个接口函数,可以在QueuePtr中加一个size记录数据个数 int QueueSize(LinkQueue* pQ) { assert(pQ); int size = 0; QueueNode* cur = pQ->phead; while (cur) //遍历链表 { size++; cur = cur->next; } return size; }
6.获取队头元素:
//获取队头元素 QDataType QueueFront(LinkQueue* pQ) { assert(pQ); assert(!QueueEmpty(pQ)); //队列不能为空 return pQ->phead->data; }
7.获取队尾元素:
//获取队尾元素 QDataType QueueBack(LinkQueue* pQ) { assert(pQ); assert(!QueueEmpty(pQ)); //队列不能为空 return pQ->ptail->data; }
8.检查队列是否为空:
//检查队列是否为空,若为空返回true,否则返回false bool QueueEmpty(LinkQueue* pQ) { assert(pQ); return pQ->phead == NULL && pQ->ptail == NULL; }
4.3 队列的功能API实现效果
测试用例:
void TestQueue()
{
LinkQueue Q;
QueueInit(&Q);
QueuePush(&Q, 1);
QueuePush(&Q, 2);
QueuePush(&Q, 3);
QueuePush(&Q, 4);
//打印队列
while (!QueueEmpty(&Q))
{
printf("%d ", QueueFront(&Q)); //获取队头元素
QueuePop(&Q); //出队
}
printf("\n");
}
效果:
5.循环队列问题
循环队列:是一种顺序表示的队列,用一组地址连续的存储单元依次存放从队头到队尾的元素。由于队列中队头和队尾的位置是动态变化的,要附设两个指针front和rear ,分别指示队头元素和队尾元素在数组中的位置。初始化队列时,令 front = rear = 0。循环队列的基本操作包括初始化,队列创建,输出,入队,出队等
跟普通队列操作方式差异不大,主要记住:
- 初始化队列:front=rear=0
- 入队:rear=(rear+1)%maxsize
- 出队:front=(front+1)%maxsize
- 队满条件:(rear+1)%maxsize==front
- 对空条件:front==rear
- 队列元素数目:(rear-front+maxsize)%maxsize