一、队列(queue)(可与栈对比进行学习)
思想:队列实现的是一种先进先出(first-in,first-out,FIFO)策略。(《算法导论》)
定义:队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表(具有线性关系/前驱后继关系)。(《大话数据结构》)
术语:
队列的两端:队头(head):进行删除操作的一端。队尾(tail):进行插入操作的一端。
操作:队列的插入操作(insert):入队(enqueue)。 队列的删除操作(delete):出队(dequeue)。
空队列:不含任何数据元素的队列(empty queue)。(与此对应的应该有术语满队列(full queue),它们强调队列中数据状态)
队列下溢(underflow):对空队列进行出队操作。队列上溢(overflow):对满队列进行入队操作。
二、队列的抽象数据类型(ADT)
队列的主要方法实现/操作(主要参考《大话数据结构》):
1.初始化队列(InitQueue)
2.清空队列(ClearQueue)(将队列的数据清空,若内存是动态分配则释放内存)
3.判断队列是否为空队列(QueueEmpty)
4.获得队头元素(GetHead)
5.入队(EnQueue)
6.出队(DeQueue)
7.获得队列的元素个数(QueueLength)
队列的数据结构实现:(队列属于线性表这一类数据结构)
1.顺序队列:顺序表实现,采用顺序存储结构。(数据按照前驱后继关系在连续储存单元中存储,array implementation)
2.链队列:链表实现,采用链式存储结构。(数据通过指针或索引连接并在存储单元中不连续存储, linked-list implementation)
三、队列的思考
1.顺序表实现时采用循环队列(头尾相接的顺序存储结构)来节约队列存储空间和避免队列数据元素的大量移动。(实现:使用数组时引入指向队头和队尾的两个指针)
2.拓展:双端队列(deque):其插入和删除操作都可以在两端进行。在C++中含有STL实现deque(#include <deque>)(来自《算法导论》习题)
3.拓展:优先级队列。(来自《编程珠玑》)
4.打印机处理就是一个队列的简单应用。
四、算法C实现(C++可使用STL中queue: #include<queue>)
这里队列的顺序表实现采用静态数组,链表实现采用动态内存;
循环队列:顺序表实现(顺序存储结构):
1.顺序表实现采用循环队列而不是简单的顺序队列实现,避免数据元素整体前后移动,提高算法时间性能;
2.head指示队头数据在数组中的索引值,tail指示队尾数据在数组中的索引值加1(注意循环),队满时:(队尾tail + 1) %队列长度size = 队头head;空队时:队尾tail = 队头head;队列数据元素长度:(队尾tail - 队头head + 队列长度size) % 队列长度size
头文件 queue.h:
/** * @file queue.h * @brief queue data structure header. * The queue use sequence list. * @author chenxilinsidney * @version 1.0 * @date 2015-01-21 */ #ifndef __QUEUE_H__ #define __QUEUE_H__ #include <stdio.h> #include <stdlib.h> #include <string.h> // #define NDEBUG #include <assert.h> /// function return state #ifndef OK #define OK 1 #endif #ifndef ERROR #define ERROR 0 #endif #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif /// data type, can be modified typedef int Status; ///< status data type typedef int ElementType; ///< element data type typedef int CommonType; ///< common data type /// queue array maz length, can be modified #define QUEUE_MAXSIZE 1000 /// queue data structure typedef struct { ElementType data[QUEUE_MAXSIZE]; ///< queue elements CommonType head; ///< queue head data index CommonType tail; ///< queue tail data index }Queue; /// queue methods /// initialize queue void InitQueue(Queue* Q); /// clear queue void ClearQueue(Queue* Q); /// detect if queue is empty Status QueueEmpty(Queue* Q); /// get queue length CommonType QueueLength(Queue* Q); /// get head element from the queue Status GetHead(Queue* Q, ElementType* e); /// insert element to the queue Status EnQueue(Queue* Q, ElementType e); /// delete element from the queue Status DeQueue(Queue* Q, ElementType* e); #endif // __QUEUE_H__
实现文件 queue.c:
/** * @file queue.c * @brief queue method implements. * The methods use <assert.h> to help debug the program. * The queue use sequence list. * @author chenxilinsidney * @version 1.0 * @date 2015-01-21 */ #include "queue.h" /** * @brief initialize the queue. * * @param[in,out] Q queue struct pointer * */ void InitQueue(Queue* Q) { assert(Q != NULL); /// initialize index only, ignore the queue data Q->head = 0; Q->tail = 0; } /** * @brief clear the queue. * * @param[in,out] Q queue struct pointer * */ void ClearQueue(Queue* Q) { assert(Q != NULL && Q->head >= 0 && Q->tail >= 0); /// set index only, ignore the queue data Q->head = 0; Q->tail = 0; } /** * @brief detect if the queue is empty. * * @param[in] Q queue struct pointer * * @return return TRUE if empty, else return FALQE */ Status QueueEmpty(Queue* Q) { assert(Q != NULL && Q->head >= 0 && Q->tail >= 0); /// detect index value if (Q->head == Q->tail) return TRUE; else return FALSE; } /** * @brief get queue length. * * @param[in] Q queue struct pointer * * @return queue length */ CommonType QueueLength(Queue* Q) { assert(Q != NULL && Q->head >= 0 && Q->tail >= 0); return (Q->tail - Q->head + QUEUE_MAXSIZE) % QUEUE_MAXSIZE; } /** * @brief get head element from the queue. * * @param[in] Q queue struct pointer * @param[out] e the element * * @return return OK if success, else return ERROR */ Status GetHead(Queue* Q, ElementType* e) { assert(Q != NULL && e != NULL && Q->head >= 0 && Q->tail >= 0); if (Q->head == Q->tail) return ERROR; /// get element from queue *e = Q->data[Q->head]; return OK; } /** * @brief insert element to the queue. * * @param[in,out] Q queue struct pointer * @param[in] e the element to be insert * * @return return OK if success, else return ERROR */ Status EnQueue(Queue* Q, ElementType e) { assert(Q != NULL && Q->head >= 0 && Q->tail >= 0); /// queue is full if ((Q->tail + 1) % QUEUE_MAXSIZE == Q->head) return ERROR; /// set data first and then increase tail index Q->data[Q->tail] = e; Q->tail = (Q->tail + 1) % QUEUE_MAXSIZE; return OK; } /** * @brief delete element from the queue. * * @param[in,out] Q queue struct pointer * @param[out] e the element to be deleted * * @return return OK and set e if success, else return ERROR */ Status DeQueue(Queue* Q, ElementType* e) { assert(Q != NULL && e != NULL && Q->head >= 0 && Q->tail >= 0); /// queue is empty if (Q->tail == Q->head) return ERROR; /// get data first and then increase head index *e = Q->data[Q->head]; Q->head = (Q->head + 1) % QUEUE_MAXSIZE; return OK; }
链队列:链表实现(链式存储结构):
1.单链表的头部指针head为头结点,本身不存放数据,内部的next指向存放队头元素的结点,单链表的尾部指针tail存放队尾元素,内部的next指向NULL;
2.采用辅助变量count记录数据元素长度,避免遍历链表获取长度信息(不使用头结点的数据位是因为这个数据类型可能不足以存放长度)
头文件 queue.h:
/** * @file queue.h * @brief queue data structure header. * The queue use linked list. * @author chenxilinsidney * @version 1.0 * @date 2015-01-21 */ #ifndef __QUEUE_H__ #define __QUEUE_H__ #include <stdio.h> #include <stdlib.h> #include <string.h> // #define NDEBUG #include <assert.h> /// function return state #ifndef OK #define OK 1 #endif #ifndef ERROR #define ERROR 0 #endif #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif /// data type, can be modified typedef int Status; ///< status data type typedef int ElementType; ///< element data type typedef int CommonType; ///< common data type /// queue data structure typedef struct QueueNode { ElementType data; ///< queue elements struct QueueNode* next; ///< queue node pointer }QueueNode, *QueuePtr; typedef struct Queue { QueuePtr head; ///< queue head data pointer QueuePtr tail; ///< quaue tail data pointer CommonType count; ///< quaue data count }Queue; /// queue methods /// initialize queue void InitQueue(Queue* Q); /// clear queue void ClearQueue(Queue* Q); /// detect if queue is empty Status QueueEmpty(Queue* Q); /// get queue length CommonType QueueLength(Queue* Q); /// get head element from the queue Status GetHead(Queue* Q, ElementType* e); /// insert element to the queue Status EnQueue(Queue* Q, ElementType e); /// delete element from the queue Status DeQueue(Queue* Q, ElementType* e); #endif // __QUEUE_H__
实现文件 queue.c:
/** * @file queue.c * @brief queue method implements. * The methods use <assert.h> to help debug the program. * The queue use linked list. * @author chenxilinsidney * @version 1.0 * @date 2015-01-21 */ #include "queue.h" /** * @brief initialize the queue. * * @param[in,out] Q queue struct pointer * */ void InitQueue(Queue* Q) { assert(Q != NULL); if ((Q->head = (QueuePtr)malloc(sizeof(QueueNode))) == NULL) { assert(0); exit(EXIT_FAILURE); } Q->tail = Q->head; Q->head->next = NULL; Q->count = 0; } /** * @brief clear the queue. * * @param[in,out] Q queue struct pointer * */ void ClearQueue(Queue* Q) { assert(Q != NULL && Q->count >= 0); /// set index only, ignore the queue data QueuePtr new_node = Q->head; while (new_node != Q->tail) { Q->head = new_node->next; free(new_node); new_node = Q->head; } Q->count = 0; } /** * @brief detect if the queue is empty. * * @param[in] Q queue struct pointer * * @return return TRUE if empty, else return FALQE */ Status QueueEmpty(Queue* Q) { assert(Q != NULL && Q->count >= 0); /// detect index value if (Q->head == Q->tail) return TRUE; else return FALSE; } /** * @brief get queue length. * * @param[in] Q queue struct pointer * * @return queue length */ CommonType QueueLength(Queue* Q) { assert(Q != NULL && Q->count >= 0); return Q->count; } /** * @brief get head element from the queue. * * @param[in] Q queue struct pointer * @param[out] e the element * * @return return OK if success, else return ERROR */ Status GetHead(Queue* Q, ElementType* e) { assert(Q != NULL && e != NULL && Q->count >= 0); if (Q->head == Q->tail) return ERROR; /// get element from queue *e = Q->head->next->data; return OK; } /** * @brief insert element to the queue. * * @param[in,out] Q queue struct pointer * @param[in] e the element to be insert * * @return return OK if success, else return ERROR */ Status EnQueue(Queue* Q, ElementType e) { assert(Q != NULL && Q->count >= 0); QueuePtr new_node; if ((new_node = (QueuePtr)malloc(sizeof(QueueNode))) == NULL) { assert(0); exit(EXIT_FAILURE); } new_node->data = e; new_node->next = NULL; Q->tail->next = new_node; Q->tail = new_node; Q->count++; return OK; } /** * @brief delete element from the queue. * * @param[in,out] Q queue struct pointer * @param[out] e the element to be deleted * * @return return OK and set e if success, else return ERROR */ Status DeQueue(Queue* Q, ElementType* e) { assert(Q != NULL && e != NULL && Q->count >= 0); /// queue is empty if (Q->tail == Q->head) return ERROR; QueuePtr old_node = Q->head->next; *e = old_node->data; Q->head->next = old_node->next; if (Q->tail == old_node) Q->tail = Q->head; free(old_node); Q->count--; return OK; }
循环队列和链队列对比:
循环队列适用于数据元素个数的最大值可控的情况;
链队列适用于数据元素个数的最大值不可控的情况,因而外存放指针使内存开销增加;
入队和出队的时间复杂度都是O(1)。
五、实践——使用两个队列实现一个栈
思考:我们可以利用其中一个空队列作为缓存区,压栈时对非空的队列进行入队操作,入队的元素为压栈的元素;弹栈时将非空队列除去最后一个元素外的其他所有元素出队并入队到空队列中,最后元素再出队作为弹栈的元素。这种方案下,两个队列中总有一个空队列作为缓存区,每次弹栈都要对所有元素进行一组出队和入队操作(最后一个元素只进行出队操作),压栈的时间复杂度为O(1)(队列是否为空队列的检测不通过遍历队列而是通过count变量或辅助变量获得,或采用其他辅助标志位来判定哪个为空队列),而弹栈的时间复杂度为O(n)。
其他方案:上面的方案不固定哪个队列(A或B)为缓存区,只要为空队列即可。这个方案固定使用1个队列(如队列A)存放元素,另一个队列(如队列B)在没有操作的情况下保持为空队列,弹栈操作时,将除去队尾的元素由队列A入队到队列B,再将这些元素由队列B入队回队列A。这种方案效率不如上面的方案,因为弹栈时它多了一个从队列B将B中所有元素入队到队列A的操作,实际上时可以避免的。
实现时需注意:
1.两各队列均为空时弹栈错误处理,压栈时可任意采用一个(也可指定使用空队列1);非空队列为满队列时压栈错误处理。(主要思考的方向是队列为空队列和满队列这两种特殊状态)
2.调用栈的各个方法实现队列各个方法,而不是对队列的数据结构含的变量直接进行处理(假设队列的数据结构中的数据是封闭的,只有方法可以调用)。
算法C实现:
头文件 stack_by_queue.h(这里除了队列使用的数据结构和不同于普通队列外,方法实现的函数接口均一致):
/** * @file stack_by_stack.h * @brief implement of a stack by two queues. * @author chenxilinsidney * @version 1.0 * @date 2015-01-22 */ #ifndef __STACK_BY_QUEUE_H__ #define __STACK_BY_QUEUE_H__ #include "queue.h" /// stack data structure by two queue typedef struct Stack { Queue q1; Queue q2; }Stack; /// stack methods /// initialize stack void InitStack(Stack* S); /// clear stack void ClearStack(Stack* S); /// detect if stack is empty Status StackEmpty(Stack* S); /// get stack length CommonType StackLength(Stack* S); /// get top element from the stack Status GetTop(Stack* S, ElementType* e); /// push element to the stack Status Push(Stack* S, ElementType e); /// pop element from the stack Status Pop(Stack* S, ElementType* e); #endif // __STACK_BY_QUEUE_H__
实现文件 stack_by_queue.c:
/** * @file stack_by_queue.c * @brief stack method implements. * The methods use <assert.h> to help debug the program. * The stack use two queues. * @author chenxilinsidney * @version 1.0 * @date 2015-01-21 */ #include "stack_by_queue.h" /** * @brief initialize the stack. * * @param[in,out] S stack struct pointer * */ void InitStack(Stack* S) { assert(S != NULL); InitQueue(&S->q1); InitQueue(&S->q2); } /** * @brief clear the stack. * * @param[in,out] S stack struct pointer * */ void ClearStack(Stack* S) { assert(S != NULL); ClearQueue(&S->q1); ClearQueue(&S->q2); } /** * @brief detect if the stack is empty. * * @param[in] S stack struct pointer * * @return return TRUE if empty, else return FALSE */ Status StackEmpty(Stack* S) { assert(S != NULL); if (QueueEmpty(&S->q1) && QueueEmpty(&S->q2)) return TRUE; else return FALSE; } /** * @brief get stack length. * * @param[in] S stack struct pointer * * @return stack length */ CommonType StackLength(Stack* S) { assert(S != NULL); return QueueLength(&S->q1) + QueueLength(&S->q2); } /** * @brief get top element from the stack. * * @param[in] S stack struct pointer * @param[out] e the element * * @return return OK if success, else return ERROR */ Status GetTop(Stack* S, ElementType* e) { assert(S != NULL && e != NULL); if (QueueEmpty(&S->q1) == FALSE) { while (DeQueue(&S->q1, e)) { EnQueue(&S->q2, *e); } return OK; } else if (QueueEmpty(&S->q2) == FALSE) { while (DeQueue(&S->q2, e)) { EnQueue(&S->q1, *e); } return OK; } else { return ERROR; } } /** * @brief push element to the stack. * * @param[in,out] S stack struct pointer * @param[in] e the element to be insert * * @return return OK if success, else return ERROR */ Status Push(Stack* S, ElementType e) { assert(S != NULL); if (QueueEmpty(&S->q1) == FALSE) { return EnQueue(&S->q1, e); } else { return EnQueue(&S->q2, e); } } /** * @brief pop element from the stack. * * @param[in,out] S stack struct pointer * @param[out] e the element to be deleted * * @return return OK and set e if success, else return ERROR */ Status Pop(Stack* S, ElementType* e) { assert(S != NULL && e != NULL); if (QueueEmpty(&S->q1) == FALSE) { CommonType keep_one = QueueLength(&S->q1) - 1; while (keep_one--) { DeQueue(&S->q1, e); EnQueue(&S->q2, *e); } DeQueue(&S->q1, e); return OK; } else if (QueueEmpty(&S->q2) == FALSE) { CommonType keep_one = QueueLength(&S->q2) - 1; while (keep_one--) { DeQueue(&S->q2, e); EnQueue(&S->q1, *e); } DeQueue(&S->q2, e); return OK; } else { return ERROR; } }
六、优先队列
(待学习《编程珠玑》补充)
六、总结
关于队列的知识点和栈一样本身不多,主要是理解队列的先进先出的思想,能够结合实际的问题综合这种思想进行应用和法实现。