最近写了蛮多栈的题,对于栈的思考也很多,可能需要多一些时间来消化整理,今天就简单总结一下队列吧。
这里说的队列是指简单的队列实现,关于队列的使用(使用的多半是优先队列)暂且不表,待学习完后面的**优先队列(堆)**过后再展开。
用链表还是数组?
这个问题和之前的栈类似,我们可以用链表实现,它们的常用操作(push,pop)时间复杂度都将会是相同的O(1)。作为数组实现的唯一不足是栈的大小往往需要事先估计,无法准确估计时我们将建立一个较大的栈空间,但往往栈中不会存有很多元素,对大多数问题来说,空间复杂度仅为n,或者是n/2,因此我们通常使用数组实现。
通常出队操作和入队操作趋于平衡,队列的大小也往往不大,队列的大小通常也不希望它很大。若采用链表的话,需要的是初始的两个链表节点分别是front,rear,其链表的实现是直接的。
- 入队时让
rear->next指向新节点即可 - 出队则让
front = front->next - 判断队空则检验
front->next是否为null - 队满则一般不予考虑
如果采用数组作容器来实现队列,尤其是常用的循环队列(充分利用队列空间),比链表稍稍复杂一些。
复杂的点:
- 判断队空,方式很多
- 判断队满,方式很多
- front/rear 的移动
- 越界访问的问题
看一下我对循环队列的数组实现:
622. Design Circular Queue
typedef struct {
int* array;
int front;
int rear;
int cnt;
int size;
} MyQueue;
MyQueue* myQueueCreate(int maxSize){
MyQueue* mq = (MyQueue*)malloc(sizeof(MyQueue));
mq->array = (int*)malloc((maxSize + 1) * sizeof(int));
mq->front = 0;
mq->rear = 1;
mq->cnt = 0;
mq->size = maxSize + 1;
return mq;
}
int isFull(MyQueue* mq){
return mq->cnt == mq->size - 1;
}
int isEmpty(MyQueue* mq){
return mq->cnt == 0;
}
int enqueue(MyQueue* mq, int val){
if(isFull(mq))
return 0;
mq->array[mq->rear] = val;
mq->rear = (mq->rear + 1) % mq->size;
mq->cnt++;
return 1;
}
int dequeue(MyQueue* mq){
if(isEmpty(mq))
return 0;
mq->front = (mq->front + 1) % mq->size;
mq->cnt--;
return 1;
}
int getFront(MyQueue* mq){
if(isEmpty(mq))
return -1;
int index = (mq->front + 1) % mq->size;
return mq->array[index];
}
int getRear(MyQueue* mq){
if(isEmpty(mq))
return -1;
int index = (mq->rear - 1 + mq->size) % mq->size;
return mq->array[index];
}
void printQueue(MyQueue* mq){
int len = (mq->rear - mq->front - 1 + mq->size) % mq->size;
int start = (mq->front + 1) % mq->size;
printf("\n----detail----\n");
for (int i = 0; i < len; ++i)
{
int index = (start + i) % mq->size;
printf("%d-", mq->array[index]);
}
printf("end\nfront:%d\n", getFront(mq));
printf("rear:%d\n", getRear(mq));
printf("cnt:%d\n", mq->cnt);
printf("size:%d\n", mq->size-1);
printf("isEmpty:%d\n", isEmpty(mq));
printf("isFull:%d\n", isFull(mq));
printf("--------------\n");
}
void freeMyQueue(MyQueue* mq){
free(mq->array);
free(mq);
}
值得注意的是:
-
cnt域的添加,它使得判断队空/满的检验变得简单直接并有效,后续你可以看到去除这个域后的判断条件将会比它更难理解。 -
每当遇到
rear-1/front-1时,我们给它再加上一个size,这将是一个防止访问负标造成越界的有效办法 -
循环队列的循环二字体现在
%这个符号 -
front与rear的
初始距离为1,因此申请内存时比k + 1 -
获得队头队尾时,访问的分别是
array[front+1]和array[rear-1] -
入队出队分别检查队满和队空,
cnt++ -
入队时, 赋值给当前的
rear,rear向右移动一个位置(考虑循环) -
出队时,并不需要改变
front的值,只让它向右移动一个位置(考虑循环) -
没有
cnt,我们判断队空和队满的条件如下
int isFull(MyQueue* mq){
return ((mq->rear + 1)) % mq->size == mq->front;
}
int isEmpty(MyQueue* mq){
return ((mq->front + 1) % mq->size) == mq->rear;
}
leetcode有一道题是用栈来实现队列,还挺有意思,思路也很直接,出栈时用另一个栈来辅助,以调整顺序。
232. Implement Queue using Stacks
typedef struct {
//stack s2 is for auxiliary
int* s1;
int t1;
int* s2;
int t2;
int front;
} MyQueue;
/** Initialize your data structure here. */
MyQueue* myQueueCreate(int maxSize) {
MyQueue* queue = (MyQueue*)malloc(sizeof(MyQueue));
queue->s1 = (int*)malloc(maxSize * sizeof(int));
queue->s2 = (int*)malloc(maxSize * sizeof(int));
queue->t1 = -1;
queue->t2 = -1;
return queue;
}
/** Push element x to the back of queue. */
void myQueuePush(MyQueue* obj, int x) {
//abvious O(1)
//s1 is empty
if(obj->t1 == -1)
obj->front = x;
obj->s1[++(obj->t1)] = x;
}
/** Removes the element from in front of queue and returns that element. */
int myQueuePop(MyQueue* obj) {
//at the worst case,transport all element from s1 to s2
//amortized, the time complexity is O(1)
if(obj->t2 == -1)
while(obj->t1 != -1)
obj->s2[++(obj->t2)] = obj->s1[(obj->t1)--];
return obj->s2[(obj->t2)--];
}
/** Get the front element. */
int myQueuePeek(MyQueue* obj) {
if(obj->t2 == -1)
return obj->front;
return obj->s2[obj->t2];
}
/** Returns whether the queue is empty. */
bool myQueueEmpty(MyQueue* obj) {
return (obj->t1 == -1 && obj->t2 == -1) ? 1 : 0;
}
void myQueueFree(MyQueue* obj) {
free(obj->s1);
free(obj->s2);
free(obj);
}
值得注意的是出栈的时间复杂度分析,Amortized O(1), 出栈时如果s2栈顶有元素就出栈顶,时间复杂度为O(1),而假设下一次出栈操作,s2栈顶没有元素,那么就一次将s1中所有的元素都push进s2,经过这么一次O(n)的操作,我们接下来的n次出栈都将只弹出s2的栈顶元素,那么摊还分析下来,每次出栈操作还是 O(n)。
常见的还有双端循环队列,CircularDeque ,与普通循环队列的区别在于它可以在队头和队尾插入元素。
641. Design Circular Deque
typedef struct {
int* array;
int front;
int rear;
int size;
} MyCircularDeque;
/** Checks whether the circular deque is empty or not. */
int myCircularDequeIsEmpty(MyCircularDeque* obj) {
return ((obj->front + 1) % obj->size == obj->rear) ? 1 : 0 ;
}
/** Checks whether the circular deque is full or not. */
int myCircularDequeIsFull(MyCircularDeque* obj) {
return obj->front == obj->rear ? 1 : 0;
}
/** Initialize your data structure here. Set the size of the deque to be k. */
MyCircularDeque* myCircularDequeCreate(int k) {
MyCircularDeque* mq = (MyCircularDeque*)malloc(sizeof(MyCircularDeque));
//if k = 1, malloc k * sizeof int, the queue is full already
mq->array = (int*)malloc((k + 1) * sizeof(int));
mq->front = k;
mq->rear = 0;
mq->size = k + 1;
return mq;
}
/** Adds an item at the front of Deque. Return true if the operation is successful. */
int myCircularDequeInsertFront(MyCircularDeque* obj, int value) {
if(myCircularDequeIsFull(obj))
return 0;
obj->array[obj->front] = value;
obj->front = (obj->front - 1 + obj->size) % obj->size;
return 1;
}
/** Adds an item at the rear of Deque. Return true if the operation is successful. */
int myCircularDequeInsertLast(MyCircularDeque* obj, int value) {
if(myCircularDequeIsFull(obj))
return 0;
obj->array[obj->rear] = value;
obj->rear = (obj->rear + 1) % obj->size;
return 1;
}
/** Deletes an item from the front of Deque. Return true if the operation is successful. */
int myCircularDequeDeleteFront(MyCircularDeque* obj) {
if(myCircularDequeIsEmpty(obj))
return 0;
obj->front = (obj->front + 1) % obj->size;
return 1;
}
/** Deletes an item from the rear of Deque. Return true if the operation is successful. */
int myCircularDequeDeleteLast(MyCircularDeque* obj) {
if(myCircularDequeIsEmpty(obj))
return 0;
obj->rear = (obj->rear - 1 + obj->size) % obj->size;
return 1;
}
/** Get the front item from the deque. */
int myCircularDequeGetFront(MyCircularDeque* obj) {
int index = (obj->front + 1) % obj->size;
return myCircularDequeIsEmpty(obj) ? -1 : obj->array[index];
}
/** Get the last item from the deque. */
int myCircularDequeGetRear(MyCircularDeque* obj) {
int index = (obj->rear - 1 + obj->size) % obj->size;
return myCircularDequeIsEmpty(obj) ? -1 : obj->array[index];
}
void myCircularDequeFree(MyCircularDeque* obj) {
free(obj->array);
free(obj);
}