几种简单队列的C语言实现

2,557 阅读6分钟

最近写了蛮多栈的题,对于栈的思考也很多,可能需要多一些时间来消化整理,今天就简单总结一下队列吧。

这里说的队列是指简单的队列实现,关于队列的使用(使用的多半是优先队列)暂且不表,待学习完后面的**优先队列(堆)**过后再展开。

用链表还是数组?

这个问题和之前的栈类似,我们可以用链表实现,它们的常用操作(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);
}

值得注意的是:

  1. cnt域的添加,它使得判断队空/满的检验变得简单直接并有效,后续你可以看到去除这个域后的判断条件将会比它更难理解。

  2. 每当遇到rear-1/front-1时,我们给它再加上一个size,这将是一个防止访问负标造成越界的有效办法

  3. 循环队列的循环二字体现在%这个符号

  4. front与rear的初始距离为1,因此申请内存时比k + 1

  5. 获得队头队尾时,访问的分别是 array[front+1]array[rear-1]

  6. 入队出队分别检查队满和队空cnt++

  7. 入队时, 赋值给当前的rear,rear移动一个位置(考虑循环

  8. 出队时,并不需要改变front的值,只让它向右移动一个位置(考虑循环

  9. 没有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中所有的元素都pushs2,经过这么一次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);
}