题目地址:641. 设计循环双端队列 - 力扣(LeetCode)
队列——>循环队列——>双端循环队列,兼具循环和双端的特性。循环表现在可以充分利用队列空间,队列是一个环;双端表现在插入、删除和获取操作既可以在队首执行,也可以在队尾执行。
双端队列结构体的组成元素:队首指针,队尾指针,队列尺寸,一个指向一块存储空间大小为队列尺寸的一级指针;
typedef struct {
int *arr;
int head;
int tail;
int size;
} MyCircularDeque; //1个结构体就代表1个双端队列,队列空间用结构体中的数组成员来表示;
在队尾删除:判断队列是否为空,队尾指针前移,因为循环队列存在队尾指针指向索引为0的情况,这时队尾指针-1为负数索引,所以前移后的队尾指针obj->tail = (obj->tail-1+obj->size) % obj->size;
bool myCircularDequeDeleteLast(MyCircularDeque* obj) {
if ( myCircularDequeIsEmpty(obj)) return false;
obj->tail = (obj->tail - 1 + obj->size) % obj->size;
//因为循环队列存在队尾指针指向索引为0的情况,此时队尾指针-1为负数索引;
return true;
}
在队首删除:判断队列是否为空,因为循环队列存在队首指针后移越界的情况,所以后移后的队首指针obj->head = (obj->head+1) % obj->size;
bool myCircularDequeDeleteFront(MyCircularDeque* obj) {
if ( myCircularDequeIsEmpty(obj)) return false;
obj->head = (obj->head + 1) % obj->size;
//因为循环队列存在队首指针后移越界的情况;
return true;
}
在队尾添加:判断队列是否为满,在此时队尾指针指向的位置存储添加的元素,队尾指针后移,后移后的队尾指针obj->tail = (obj->tail+1) % obj->size;
bool myCircularDequeInsertLast(MyCircularDeque* obj, int value) {
if ( myCircularDequeIsFull(obj)) return false;
obj->arr[obj->tail] = value;
obj->tail = (obj->tail + 1) % obj->size;
return true;
}
在队首添加:判断队列是否为满,队首指针前移,前移后的队首指针obj->head = (obj->head+obj->size-1) % obj->size,在前移后的队首指针指向的位置存储添加的元素;
bool myCircularDequeInsertFront(MyCircularDeque* obj, int value) {
if (myCircularDequeIsFull(obj)) return false;
int pos = (obj->head - 1 + obj->size) % obj->size;
//因为循环队列存在队首指针指向索引为0的情况,此时队首指针-1为负数索引;
obj->arr[pos] = value;
obj->head = pos;
return true;
}
获得队首元素:因为队首指针指向的就是队首元素,所以在判断队列不为空的情况下,直接返回队首指针指向的元素;
int myCircularDequeGetFront(MyCircularDeque* obj) {
if ( myCircularDequeIsEmpty(obj) ) return -1;
return obj->arr[obj->head];
}
获得队尾元素;在队列不为空的情况下,返回队尾指针指向位置的前一个位置的元素(考虑循环特性,返回元素的索引表达式和队尾指针前移的表达式相同);
int myCircularDequeGetRear(MyCircularDeque* obj) {
if ( myCircularDequeIsEmpty(obj) ) return -1;
int pos = (obj->tail - 1 + obj->size) % obj->size;
//考虑队尾指针指向位置0的情况;
return obj->arr[pos];
}
总结:无论是队首指针还是队尾指针,考虑到循环队列的循环特性,后移(队首删除&队尾添加)表达式相同且比较简单,前移(队首添加&队尾删除)表达式相同不要忘记进行+obj->size操作;
判断队列为空:首尾指针重合;
bool myCircularDequeIsEmpty(MyCircularDeque* obj) {
return (obj->head == obj->tail) ? true : false;//头尾指针重合
}
判断队列为满:队尾指针+1与队首指针重合,考虑到循环特性判断表达式为obj->head == (obj->tail+1) % obj->size,
bool myCircularDequeIsFull(MyCircularDeque* obj) {
return (obj->head == (obj->tail + 1) % obj->size) ? true : false;
//队尾指针+1与队首指针重合,考虑队尾指针+1后越界的情况;
}
注意这实际上牺牲了一个存储空间,这也是为什么队列尺寸初始化为k+1,以及为队列malloc一块大小为k+1的存储空间;
MyCircularDeque* myCircularDequeCreate(int k) {
MyCircularDeque *obj = malloc(sizeof(MyCircularDeque));
obj->arr = malloc(sizeof(int) * (k+1)); //循环队列有意浪费一个位置;
obj->head = 0; //用整形表示队首和队尾指针,索引从0开始;
obj->tail = 0;
obj->size = k + 1;
return obj;
}
void myCircularDequeFree(MyCircularDeque* obj) {
free(obj->arr); //先释放队列空间即结构体内的数组成员;
free(obj); //再释放队列结构体指针;
return ;
}
疑问:栈顶指针top初始值为-1,使用++top进行入栈操作,top始终指向栈顶元素;队列的尾指针都指向队尾元素的下一个位置?
//测试代码;
void main() {
MyCircularDeque *obj;
obj = myCircularDequeCreate(3);
printf("%d\n", myCircularDequeInsertLast(obj, 1));
printf("%d\n", myCircularDequeInsertLast(obj, 2));
printf("%d\n", myCircularDequeInsertFront(obj, 3));
printf("%d\n", myCircularDequeInsertFront(obj, 4));
printf("%d\n", myCircularDequeGetRear(obj));
printf("%d\n", myCircularDequeIsFull(obj));
printf("%d\n", myCircularDequeDeleteLast(obj));
printf("%d\n", myCircularDequeInsertFront(obj, 4));
printf("%d\n", myCircularDequeGetFront(obj));
}
python版本:
class MyCircularDeque:
def __init__(self, k: int):
self.front = self.rear = 0
self.elements = [0] * (k + 1)
def insertFront(self, value: int) -> bool:
if self.isFull():
return False
self.front = (self.front - 1 + len(self.elements)) % len(self.elements)
self.elements[self.front] = value
return True
def insertLast(self, value: int) -> bool:
if self.isFull():
return False
self.elements[self.rear] = value
self.rear = (self.rear + 1) % len(self.elements)
return True
def deleteFront(self) -> bool:
if self.isEmpty():
return False
self.front = (self.front + 1) % len(self.elements)
return True
def deleteLast(self) -> bool:
if self.isEmpty():
return False
self.rear = (self.rear - 1 + len(self.elements)) % len(self.elements)
return True
def getFront(self) -> int:
return -1 if self.isEmpty() else self.elements[self.front]
def getRear(self) -> int:
return -1 if self.isEmpty() else self.elements[(self.rear - 1 + len(self.elements)) % len(self.elements)]
def isEmpty(self) -> bool:
return self.front == self.rear
def isFull(self) -> bool:
return self.front == ((self.rear + 1) % len(self.elements))