Deque
双端队列顾名思义:两端都可以进行插入删除元素的队列。
在双端队列两端如果进行限制插入删除操作,就会退化成队列或者栈,所以如果你掌握了双端队列,那么队列和栈自然就更加的easy了。
以下是双端队列这个抽象数据类型的英文描述:
A “deque” is a data structure consisting of a list of items, on which the following operations are possible:
Push(X,D): Insert item X on the front end of deque D.
Pop(D): Remove the front item from deque D and return it. Inject(X,D): Insert item X on the rear end of deque D.
Eject(D): Remove the rear item from deque D and return it.
PUSH和POP为双端队列头部插入和删除操作
inject和Eject为双端队列尾部插入和删除操作。
首先我们先定义双端队列的物理结构
#define ElementType int
typedef struct Node *PtrToNode;
struct Node {
ElementType Element;
PtrToNode Next, Last;
};
typedef struct DequeRecord *Deque;
struct DequeRecord {
PtrToNode Front, Rear;
};
图解形式
这里的小伙伴很容易理解错,再刷pat时我也遇到这种情况,以前学数据结构的时候,双端队列说是左右都能插入,图解的形式是
但是实际存储的时候一开始并不是这样,这明显是插入删除多次之后才会变成这样,不管是数组还是链表,一开始的front和rear都是在第一幅图的样子,有些小伙伴会出错的地方在,front插入第一个元素时用尾插法会把front指针往左移添加,其实front一开始的位置是不能动的,而且最好用头插写比较好。
首先我们创建双端队列链表形式
Deque CreateDeque()
{
Deque p;
p = (Deque)malloc(sizeof(struct DequeRecord));
//队列开空间
p->Front = (PtrToNode)malloc(sizeof(struct Node));
//队列头部指向一个新地址
p->Rear = (PtrToNode)malloc(sizeof(struct Node));
//队列尾部也指向一个新地址
p->Rear->Last = p->Front;//头尾相等
p->Front->Next = p->Rear;
p->Front->Last = NULL;
p->Rear->Next = NULL;
return p;
}
因为网上只有单头指针的形式,导致push,pop,inject,eject的代码操作上不够简洁,看上去也难以理解,我最新创了一种双头结点的写法,pat上ac过了,是可行的,虽然多用了一块的空间,但是影响不大。因为这样4个操作统一了,看上去代码也更好看了。
第二步我们写出4种操作的具体原理代码
int Push(ElementType X, Deque D){
struct Node* tmp;
tmp = (struct Node*)malloc(sizeof(struct Node));
//开新的节点
if (!tmp)return 0;
tmp->Element = X;//给新节点赋值
tmp->Last = D->Front;
tmp->Next = D->Front->Next;
D->Front->Next->Last = tmp;
D->Front->Next = tmp;
return 1;//对其进行新节点头插入双链表的写法
}
ElementType Pop(Deque D)
{
//队列为空
if (D->Front->Next == D->Rear)return ERROR;
//删除头部结点的操作
int num = D->Front->Next->Element;
PtrToNode tmp = D->Front->Next;
D->Front->Next = tmp->Next;
tmp->Next->Last = D->Front;
free(tmp);
return num;
}
int Inject(ElementType X, Deque D)
{
struct Node* tmp;
tmp = (struct Node*)malloc(sizeof(struct Node));
//开新的节点
if (!tmp)return 0;
tmp->Element = X;//给新结点赋值
tmp->Next = D->Rear;
tmp->Last = D->Rear->Last;
D->Rear->Last->Next = tmp;
D->Rear->Last = tmp;
return 1;//对其进行新节点头插入双链表的写法
}
ElementType Eject(Deque D)
{
//队列为空
if (D->Front->Next == D->Rear)return ERROR;
//删除尾部的结点
int num = D->Rear->Last->Element;
PtrToNode tmp = D->Rear->Last;
D->Rear->Last = tmp->Last;
tmp->Last->Next = D->Rear;
free(tmp);
return num;
}
4个操作比其他网上的写法要简洁很多,而且操作统一,尾部和头部操作相同,只是front和rear,last和next改变,最最重要的是相比不插尾部头结点的方法要好在,不用判断没有结点插入和一个结点插入的情况了。
最后完整代码贴上,可以自己尝试一下实现
#include <stdio.h>
#include <stdlib.h>
#define ElementType int
#define ERROR 1e5
typedef enum { push, pop, inject, eject, end } Operation;
typedef struct Node *PtrToNode;
struct Node {
ElementType Element;
PtrToNode Next, Last;
};
typedef struct DequeRecord *Deque;
struct DequeRecord {
PtrToNode Front, Rear;
};
//主函数入口
int main()
{
ElementType X;
Deque D;
int done = 0;
D = CreateDeque();
while (!done) {
switch (GetOp()) {
case push:
scanf("%d", &X);
if (!Push(X, D)) printf("Memory is Full!\n");
break;
case pop:
X = Pop(D);
if (X == ERROR) printf("Deque is Empty!\n");
break;
case inject:
scanf("%d", &X);
if (!Inject(X, D)) printf("Memory is Full!\n");
break;
case eject:
X = Eject(D);
if (X == ERROR) printf("Deque is Empty!\n");
break;
case end:
PrintDeque(D);
done = 1;
break;
}
}
return 0;
}
//push,pop,inject,enject4种操作的具体形式
int Push(ElementType X, Deque D){
struct Node* tmp;
tmp = (struct Node*)malloc(sizeof(struct Node));//开新的节点
if (!tmp)return 0;
tmp->Element = X;//赋值
tmp->Last = D->Front;
tmp->Next = D->Front->Next;
D->Front->Next->Last = tmp;
D->Front->Next = tmp;
return 1;
}
ElementType Pop(Deque D)
{
//队列为空
if (D->Front->Next == D->Rear)return ERROR;
int num = D->Front->Next->Element;
PtrToNode tmp = D->Front->Next;
D->Front->Next = tmp->Next;
tmp->Next->Last = D->Front;
free(tmp);
return num;
}
int Inject(ElementType X, Deque D)
{
struct Node* tmp;
tmp = (struct Node*)malloc(sizeof(struct Node));//开新的节点
if (!tmp)return 0;
tmp->Element = X;//赋值
tmp->Next = D->Rear;
tmp->Last = D->Rear->Last;
D->Rear->Last->Next = tmp;
D->Rear->Last = tmp;
return 1;
}
ElementType Eject(Deque D)
{
if (D->Front->Next == D->Rear)return ERROR;
int num = D->Rear->Last->Element;
PtrToNode tmp = D->Rear->Last;
D->Rear->Last = tmp->Last;
tmp->Last->Next = D->Rear;
free(tmp);
return num;
}
//输入判断入口
Operation GetOp()
{
char a[111];
scanf("%s", a);
//push, pop, inject, eject, end
if (!strcmp("Push", a))
return push;
if (!strcmp("Pop", a))
return pop;
if (!strcmp("Inject", a))
return inject;
if (!strcmp("Eject", a))
return eject;
if (!strcmp("End", a))
return end;
}
//打印函数
void PrintDeque(Deque D)
{
while (D->Front != D->Rear)
{
printf("%d ", Pop(D));
}
puts("");
}
//声明:内部代码有部分使用pat