1.用栈实现队列
我们都知道,栈是后进先出,队列是先进先出,那么怎么用栈来实现队列呢?
这是一个栈,栈顶元素是5,栈底元素是1,如果出栈肯定是5先出去了,我们想让1先出去,那就可以借助两个栈,我们先把12345按照54321的顺序出栈,出栈的元素不是没了,而是进入到了另一个栈中:
那么按照队列的性质先进先出,1就可以出去了,正好也符合pop栈中后进先出的性质。
所以我们可以使用两个栈来实现一个队列(一个出栈,一个入栈),当用来出队的栈没有元素时,就把入队的栈里的元素放到出栈中,根据栈的性质,会反过来,就达到了我们的目的。
代码:
typedef int STDataType;
typedef struct Stack
{
STDataType* array;
int capacity;
int size; // 容量
}Stack;
// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestory(Stack* ps);
#define DEFSTACKSIZE 100
void CheckCapacity(Stack* ps)
{
if (ps->size >= ps->capacity)
{
ps->capacity *= 2;
ps->array = (STDataType *)realloc(ps->array, ps->capacity * sizeof(STDataType));
}
}
void StackInit(Stack* ps)
{
ps->array = (STDataType *)calloc(DEFSTACKSIZE, sizeof(STDataType));
ps->capacity = DEFSTACKSIZE;
ps->size = 0;
}
void StackPush(Stack* ps, STDataType x)
{
CheckCapacity(ps);
ps->array[ps->size] = x;
ps->size++;
}
void StackPop(Stack* ps)
{
if (ps->size == 0)
{
return;
}
ps->size--;
}
STDataType StackTop(Stack* ps)
{
if (ps->size == 0)
{
return (STDataType)0;
}
return ps->array[ps->size - 1];
}
int StackEmpty(Stack* ps)
{
return ps->size == 0;
}
int StackSize(Stack* ps)
{
return ps->size;
}
void StackDestory(Stack* ps)
{
if (ps->array)
{
free(ps->array);
ps->array = NULL;
ps->size = 0;
ps->capacity = 0;
}
}
//以上为栈
typedef struct
{
Stack pushST;
Stack popST;
} MyQueue;
MyQueue* myQueueCreate()
{
MyQueue* nQueue = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&nQueue->pushST);
StackInit(&nQueue->popST);
return nQueue;
}
void myQueuePush(MyQueue* obj, int x)
{
StackPush(&obj->pushST,x);
}
int myQueuePop(MyQueue* obj) {
if(StackEmpty(&obj->popST)!=0)
{
while(StackEmpty(&obj->pushST)==0)
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
int f = StackTop(&obj->popST);
StackPop(&obj->popST);
return f;
}
int myQueuePeek(MyQueue* obj)
{
if(StackEmpty(&obj->popST)!=0)
{
while(StackEmpty(&obj->pushST)==0)
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
return StackTop(&obj->popST);
}
bool myQueueEmpty(MyQueue* obj)
{
return StackEmpty(&obj->pushST)&&StackEmpty(&obj->popST);
}
void myQueueFree(MyQueue* obj) {
StackDestory(&obj->popST);
StackDestory(&obj->pushST);
free(obj);
}
别看有点多,其实是前面有栈的实现,所以占了几十行。
2.用队列实现栈
用队列实现栈也是差不多的思路,用两个队列实现一个栈。
怎么让5先出去呢?队列是先入先出的,你转移到另一个队列也还是1 2 3 4 5的顺序,那么这回我们就让队列中只剩五,再出队列,不就可以先让5出去了?
然后再让1出栈,实现了出栈顶。
注意,一定要保证一个空的队列用于储存其他的元素。
代码:
typedef int QDataType;
typedef struct QlistNode
{
struct QlistNode* _next;
QDataType _data;
}QNode;
typedef struct Queue
{
QNode* _front;//队列头
QNode* _rear;//队列尾
}Queue;
QNode* BuyQueueNode(QDataType x)
{
QNode* cur = (QNode*)malloc(sizeof(QNode));
cur->_data = x;
cur->_next = NULL;
return cur;
}
void QueueInit(Queue* q)
{
q->_front = NULL;
q->_rear = NULL;
}
void QueuePush(Queue* q, QDataType data)
{
QNode* cur = BuyQueueNode(data);
if (q->_front == NULL)
{
q->_front = q->_rear = cur;
}
else//有节点就在队列最后尾插,让rear后移一位
{
q->_rear->_next = cur;
q->_rear = cur;
}
}
void QueuePop(Queue* q)
{
assert(q);//出队列就相当于链表的头删一样
if (q->_front == NULL)
return;
QNode* cur = q->_front->_next;
free(q->_front);
q->_front = cur;
}
int QueueEmpty(Queue* q)
{
if (q->_front == NULL)
return 1;
else return 0;
}
void QueueDestroy(Queue* q)
{
if (q->_front == NULL)
return;
while (q->_front)
{
QueuePop(q);
}
}
int QueueSize(Queue* q)
{
assert(q);
QNode* cur = q->_front;
int count = 0;
while (cur)
{
count++;
cur = cur->_next;
}
return count;
}
QDataType QueueFront(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->_front->_data;
}
QDataType QueueBack(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->_rear->_data;
}
//上方为队列(C语言无队列封装)
//
//
//
//
typedef struct {
Queue q1;//用两个队列实现栈
Queue q2;
}MyStack;
MyStack* myStackCreate()
{
MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&pst->q1);
QueueInit(&pst->q2);
return pst;
}
void myStackPush(MyStack* obj, int x)
{
//给非空队列入队
if (QueueEmpty(&obj->q1) == 1 && QueueEmpty(&obj->q2) == 1)//如果队列确实是空的就入队
{
QueuePush(&obj->q1, x);
}
else if (QueueEmpty(&obj->q2) != 1)
{
QueuePush(&obj->q2, x);
}
else
{
QueuePush(&obj->q1, x);
}
}
int myStackPop(MyStack* obj)
{
//把非空队列的除最后一个元素之前的元素全都移动到另一个队列
Queue* pEmpty = &obj->q1, * pNonEmpty = &obj->q2;
if (QueueEmpty(&obj->q1) != 1)
{
pEmpty = &obj->q2;
pNonEmpty = &obj->q1;//判断空队列
}
while (QueueSize(pNonEmpty) > 1)
{
QueuePush(pEmpty, QueueFront(pNonEmpty));
QueuePop(pNonEmpty);//把非空队列其余元素移动到空队列
}
int top = QueueFront(pNonEmpty);
QueuePop(pNonEmpty);
return top;
}
int myStackTop(MyStack* obj) {
//栈顶就是非空队列的队尾
if (QueueEmpty(&obj->q1) != 1)
{
return QueueBack(&obj->q1);
}
else
{
return QueueBack(&obj->q2);
}
}
bool myStackEmpty(MyStack* obj)
{
return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
void myStackFree(MyStack* obj)
{
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}
3.循环队列
假如一个队列有40字节,那么我们储存10个数据就顶天了,这时候我们出队一个,还是不能入队了,因为队列已经满了,即使是没用的元素塞满了,你也不能再进去了。
我们采用数组的方式来实现这个循环队列:
这时候要是一般的队列,你就不能往下存了,可是我们是循环队列,我们要再存数据,就把tail往前移动就可以了。
继续存,什么时候存满呢?
当tail == head的时候就存满了,但是这和队列为空的条件冲突了,因此,我们要区分空和满,可以给他增加一个空间
这样,tail和head就会始终保持1距离,只有为空的时候才相等,满的时候,tail+1 == head。
具体还有一些细节需要说,直接先上代码:
代码:
typedef struct
{
int *a;
int front;
int tail;
int k;
} MyCircularQueue;
bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue * nq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
nq->a = malloc(sizeof(int)*(k+1));
nq-> tail = 0;
nq-> front = 0;
nq-> k = k;
return nq;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
//插入一个元素
if(myCircularQueueIsFull(obj))
{
return false;
}
else
{
obj->a[obj->tail] = value;
obj->tail++;
obj->tail %= (obj->k+1);
return true;
}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
return false;
else
{
++obj->front;
obj->front %= (obj->k+1);
return true;
}
}
int myCircularQueueFront(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
return -1;
return obj->a[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
return -1;
/*if(obj->tail==0)
return obj->a[k];
else
return obj->a[obj->tail-1];
*/
int i = (obj->tail+obj->k)%(obj->k+1);
return obj->a[i];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->front == obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
return obj->front == (obj->tail+1)%(obj->k+1);//为了防止tail转一圈回到前面,和front不一样
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
k就是容量,我们每次插入让tail指针加1的同时还会让他
obj->tail %= (obj->k+1);
这是为了防止特殊情况,假如tail到了数组的0位置,他的值不一定是0,可能是0+n*(k+1),所以我们采用取模的方法,让tail下标的范围一直在
数组的范围当中。同理,front指针也进行相应的操作
小结
这三道习题是帮助大家加深理解,有多种实现方法,尤其是循环队列(链表实现),建议大家敲一敲,锻炼一下。