[数据结构]共享栈与双端队列:算法思想分析及C语言实现

目录

共享栈

算法思想:

C/C++实现,关键之处给出注释:

双端队列

算法实现:

C/C++实现,关键之处给出注释:

总结

提问


引言

共享栈和双端队列是除了栈和队列以外,限定性的数据结构。接下来一起来从算法思想上理解它们。

共享栈

共享栈是将两个栈(Typedef struct {}Stack1,Stack2 )存放在同一段连续数组空间中的结构。数组的两端分别作为两个栈的栈底:栈1从数组的下标0处,向数组中部延伸,栈2从数组的下标MAXSIZE-1处,相对逆向延伸。这两个栈的栈顶指针在各自的相反方向移动,当两个指针相邻时表示栈满,即共享栈的终止条件:top1 +1 == top2。共享栈存在的意义在于,提高数组的空间利用率,避免单个栈空间固定造成的浪费。

共享栈通过top1++/++top1 和top2++/++top2实现相向移动。

算法思想:

共享栈是指两个栈共用同一块连续存储空间(数组)。通常将数组的两端分别作为两个栈的栈底:

  • 栈1 的栈底在数组下标 0 处,栈顶指针 top1 初始为 -1,入栈时 top1++,出栈时 top1--

  • 栈2 的栈底在数组下标 MAXSIZE-1 处,栈顶指针 top2 初始为 MAXSIZE,入栈时 top2--,出栈时 top2++

top1 + 1 == top2 时,说明栈满,无法再插入新元素。这种结构能够最大限度地利用数组空间,避免了一个栈空闲、另一个栈溢出的情况。

C/C++实现,关键之处给出注释:

#include <stdio.h>

#include <stdlib.h>

#include <stdbool.h>

#define MAXSIZE 100 // 共享栈的总容量

// 共享栈结构体

typedef struct {

int dataMAXSIZE; // 存储元素的数组

int top1; // 栈1的栈顶指针(指向栈顶元素)

int top2; // 栈2的栈顶指针(指向栈顶元素)

} SharedStack;

// 初始化共享栈

void initSharedStack(SharedStack *s) {

s->top1 = -1; // 栈1初始栈顶为-1

s->top2 = MAXSIZE; // 栈2初始栈顶为MAXSIZE(指向数组最后一个元素的下一个位置)

}

// 判断栈1是否为空

bool isEmpty1(SharedStack *s) {

return s->top1 == -1;

}

// 判断栈2是否为空

bool isEmpty2(SharedStack *s) {

return s->top2 == MAXSIZE;

}

// 判断共享栈是否已满(两个栈顶相邻)

bool isFull(SharedStack *s) {

return s->top1 + 1 == s->top2;

}

// 栈1入栈

bool push1(SharedStack *s, int value) {

if (isFull(s)) {

printf("共享栈已满,无法入栈!\n");

return false;

}

s->data++s-\>top1 = value; // top1先加1,再赋值

return true;

}

// 栈2入栈

bool push2(SharedStack *s, int value) {

if (isFull(s)) {

printf("共享栈已满,无法入栈!\n");

return false;

}

s->data--s-\>top2 = value; // top2先减1,再赋值

return true;

}

// 栈1出栈

bool pop1(SharedStack *s, int *value) {

if (isEmpty1(s)) {

printf("栈1为空,无法出栈!\n");

return false;

}

*value = s->datas-\>top1--; // 取出栈顶元素,然后top1减1

return true;

}

// 栈2出栈

bool pop2(SharedStack *s, int *value) {

if (isEmpty2(s)) {

printf("栈2为空,无法出栈!\n");

return false;

}

*value = s->datas-\>top2++; // 取出栈顶元素,然后top2加1

return true;

}

// 获取栈1栈顶元素(不出栈)

bool getTop1(SharedStack *s, int *value) {

if (isEmpty1(s)) return false;

*value = s->datas-\>top1;

return true;

}

// 获取栈2栈顶元素(不出栈)

bool getTop2(SharedStack *s, int *value) {

if (isEmpty2(s)) return false;

*value = s->datas-\>top2;

return true;

}

// 简单测试

int main() {

SharedStack ss;

initSharedStack(&ss);

push1(&ss, 10);

push1(&ss, 20);

push2(&ss, 100);

push2(&ss, 200);

int val;

pop1(&ss, &val);

printf("栈1出栈: %d\n", val); // 20

pop2(&ss, &val);

printf("栈2出栈: %d\n", val); // 200

return 0;

}

双端队列

双端队列(Deque,Double Ended Queue)是一种允许在两端进行插入和删除操作的线性表。其特性为既能在队列头部进行插入和删除操作,也能在队尾进行插入和删除操作。

权限探究:

队列头部 插入和删除权限 || 队列尾部 插入和删除权限,栈

队列头部 插入和删除操作&& 队列尾部 插入和删除操作,双端队列

队列头部插入和删除 && 队列尾部 插入 || 队列头部插入 && 队列尾部 插入和删除,共享栈

队列头部插入 && 队列尾部 删除 || 队列头部删除 && 队列尾部 插入,队列

队列头部&&队列尾部 删除/插入,NULL

通常用循环数组或者双向链表实现,采用"牺牲一个单元"的方法区分队空和队满:队空条件,front == rear; 队满条件,(rear+1)%MAXSIZAE == front.双端队列存在的的意义在于,提供更灵活的数据操作方式,适用于滑动窗口、双端优先调度等场景。

双端队列通过(front++)%MAXSIZE/(front--)%MAXSIZE/(++front)%MAXSIZE/(--front)%MAXSIZE实现相向移动。

算法实现:

双端队列(Deque)允许在队列的两端进行插入和删除操作。通常用循环数组双向链表 实现。此处采用循环数组方式:

  • 设定两个指针 frontrearfront 指向队头元素的位置,rear 指向队尾元素的下一个位置(或指向队尾元素,取决于实现习惯)。

  • 为了区分队空和队满,通常牺牲一个数组单元 ,即当 (rear + 1) % MAXSIZE == front 时认为队满;当 front == rear 时认为队空。

  • 入队操作:从队头入队时,front 向前移动(front = (front - 1 + MAXSIZE) % MAXSIZE);从队尾入队时,rear 向后移动(rear = (rear + 1) % MAXSIZE)。

  • 出队操作:从队头出队时,front 向后移动;从队尾出队时,rear 向前移动。

这种结构可以在 O(1) 时间内完成两端操作,且空间利用率高。

C/C++实现,关键之处给出注释:

#include <stdio.h>

#include <stdlib.h>

#include <stdbool.h>

#define MAXSIZE 10 // 双端队列最大容量(实际可用 MAXSIZE-1 个元素)

// 双端队列结构体(循环数组)

typedef struct {

int dataMAXSIZE;

int front; // 指向队头元素的位置

int rear; // 指向队尾元素的下一个位置

} Deque;

// 初始化双端队列

void initDeque(Deque *dq) {

dq->front = 0;

dq->rear = 0;

}

// 判断队列是否为空

bool isEmpty(Deque *dq) {

return dq->front == dq->rear;

}

// 判断队列是否已满(牺牲一个单元)

bool isFull(Deque *dq) {

return (dq->rear + 1) % MAXSIZE == dq->front;

}

// 从队头插入元素

bool pushFront(Deque *dq, int value) {

if (isFull(dq)) {

printf("双端队列已满,无法在队头插入!\n");

return false;

}

// 队头指针前移一位(循环)

dq->front = (dq->front - 1 + MAXSIZE) % MAXSIZE;

dq->datadq-\>front = value;

return true;

}

// 从队尾插入元素

bool pushBack(Deque *dq, int value) {

if (isFull(dq)) {

printf("双端队列已满,无法在队尾插入!\n");

return false;

}

dq->datadq-\>rear = value;

dq->rear = (dq->rear + 1) % MAXSIZE;

return true;

}

// 从队头删除元素

bool popFront(Deque *dq, int *value) {

if (isEmpty(dq)) {

printf("双端队列为空,无法从队头删除!\n");

return false;

}

*value = dq->datadq-\>front;

dq->front = (dq->front + 1) % MAXSIZE;

return true;

}

// 从队尾删除元素

bool popBack(Deque *dq, int *value) {

if (isEmpty(dq)) {

printf("双端队列为空,无法从队尾删除!\n");

return false;

}

// 队尾指针前移一位(指向最后一个有效元素)

dq->rear = (dq->rear - 1 + MAXSIZE) % MAXSIZE;

*value = dq->datadq-\>rear;

return true;

}

// 获取队头元素(不出队)

bool getFront(Deque *dq, int *value) {

if (isEmpty(dq)) return false;

*value = dq->datadq-\>front;

return true;

}

// 获取队尾元素(不出队)

bool getBack(Deque *dq, int *value) {

if (isEmpty(dq)) return false;

int last = (dq->rear - 1 + MAXSIZE) % MAXSIZE;

*value = dq->datalast;

return true;

}

// 简单测试

int main() {

Deque dq;

initDeque(&dq);

pushBack(&dq, 1);

pushBack(&dq, 2);

pushFront(&dq, 0);

pushFront(&dq, -1);

int val;

popFront(&dq, &val);

printf("队头出队: %d\n", val); // -1

popBack(&dq, &val);

printf("队尾出队: %d\n", val); // 2

return 0;

}

总结

共享栈与双端队列的共性。两者都优先采用数组作为底层容器,利用连续内存空间,基于顺序存储(链表)具备随机存储特性。双端操作的思想,共享栈的两个栈分别从数组两端延伸;双端队列允许两端插入/删除。对比于常规的栈和队列,上述限定数据结构有更高的空间利用率,共享栈动态分配空间为两个栈;双端队列循环利用数组空间,避免"假溢出"。共享栈和双端队列的基本操作的时间复杂度均为O(1)/O(n)。

提问

双端队列的"假溢出"?

时间复杂度什么时候用O(1)/O(n)?

是否有只插入或者删除的应用场景?

数据结构还有哪些限定的结构?

相关推荐
우리帅杰1 小时前
【AI测试】Python AI大模型介绍
开发语言·人工智能·python·ai编程
我是一颗柠檬1 小时前
【Java项目技术亮点】全链路分层限流:从网关到数据库的多层防护体系
java·开发语言·数据库
The Sheep 20231 小时前
C#多线程学习
开发语言·学习·c#
Shadow(⊙o⊙)1 小时前
QT常用控件1.0,enabled() geometry() QIcon的.qrc文件导入
开发语言·c++·qt
geovindu1 小时前
python: Generators Pattern
开发语言·python·设计模式·生成器模式
wuminyu1 小时前
Java锁膨胀机制之偏向锁到轻量级锁源码剖析
java·linux·c语言·jvm·c++
没有不重的名么1 小时前
spyder使用教程
开发语言·python
阿正的梦工坊1 小时前
【Rust】06-函数、控制流与模块组织
开发语言·算法·rust
码不停蹄的玄黓1 小时前
SpringBoot 实现拦截器
java·spring boot·后端