队列
只允许在一端(队尾)进行插入数据操作(入队列),在另一端(对头)进行删除数据操作(出队列)的特殊线性表
对于顺序队列,若采用队头不动,出队列要移动队头后的所有元素;若移动出队列后向后移动队头,会造成“假溢出”
若采用取模循环法来实现循环队列,又需要解决新的问题,如何区分队列空满的状态
鉴于顺序队列实现起来比较麻烦,以下的队列实现采用链式结构
存储方式
typedef int DataType;
typedef struct QNode
{
struct QNode *next;
DataType data;
}QNode;
typedef struct
{
QNode *Front;
QNode *Rear;
int size;
}Queue;
相关操作
void QueueInit(Queue *pQ)
{
assert(pQ);
pQ->Front = NULL;
pQ->Rear = NULL;
pQ->size = 0;
}
void QueueDestroy(Queue *pQ)
{
QNode *cur = pQ->Front;
QNode *next = NULL;
assert(pQ);
while (cur != NULL)
{
next = cur->next;
free(cur);
cur = next;
}
pQ->Front = NULL;
pQ->Rear = NULL;
pQ->size = 0;
}
static QNode *CreateNode(DataType data)
{
QNode *newNode = (QNode *)malloc(sizeof(QNode));
assert(newNode);
newNode->next = NULL;
newNode->data = data;
return newNode;
}
void InQueue(Queue *pQ, DataType data)
{
QNode *newNode = NULL;
assert(pQ);
newNode = CreateNode(data);
if (pQ->Front == NULL)
{
pQ->Front = newNode;
pQ->Rear = newNode;
}
else
{
pQ->Rear->next = newNode;
pQ->Rear = newNode;
}
pQ->size++;
}
void OutQueue(Queue *pQ)
{
QNode *del = NULL;
assert(pQ);
assert(pQ->Front != NULL);
if (pQ->Front == pQ->Rear)
{
free(pQ->Front);
pQ->Front = NULL;
pQ->Rear = NULL;
}
else
{
del = pQ->Front;
pQ->Front = pQ->Front->next;
free(del);
}
pQ->size--;
}
DataType QueueFront(const Queue *pQ)
{
assert(pQ);
assert(pQ->Front != NULL);
return pQ->Front->data;
}
int QueueEmpty(const Queue *pQ)
{
return pQ->Front == NULL ? 1 : 0;
}
int QueueSize(const Queue *pQ)
{
return pQ->size;
}
栈和队列的面试题
实现一个栈,要求实现出栈、入栈、返回最小值的时间复杂度为O(1)
栈本身的出入栈操作时间复杂度是O(1),只需实现时间复杂度为O(1)的返回最小值操作。以空间换时间的理念多创建一个栈,专门用于更新存放当前栈的最小值
入栈时基本栈正常入栈,最小栈永远只入当前栈的最小值,若需要压住的元素大于最小栈的栈顶元素(当前栈的最小值),则重复压入栈顶元素;出栈时两站分别出栈
存储方式
typedef struct MinStack
{
Stack Minstack;
Stack Norstack;
}MinStack;
相关操作
void MinStackInit(MinStack *ps)
{
ps->Norstack.top = 0;
ps->Minstack.top = 0;
}
void MinStackDestroy(MinStack *ps)
{
ps->Norstack.top = 0;
ps->Minstack.top = 0;
}
void MinStackPop(MinStack *ps)
{
assert(ps->Minstack.top > 0);
ps->Norstack.top--;
ps->Minstack.top--;
}
DataType MinStackTop(MinStack *ps)//返回栈顶元素
{
return ps->Norstack.arr[ps->Norstack.top - 1];
}
DataType MinStackMin(MinStack *ps)//返回栈的最小值
{
return ps->Minstack.arr[ps->Minstack.top - 1];
}
void PushMinStack(MinStack *ps, DataType data)
{
assert(ps->Minstack.top < MAX_SIZE);
ps->Norstack.arr[ps->Norstack.top++] = data;
if (StackEmpty(&ps->Minstack) || data < StackTop(&ps->Minstack))//注意最小栈为空时应该直接压栈
{
ps->Minstack.arr[ps->Minstack.top++] = data;
}
else
{
ps->Minstack.arr[ps->Minstack.top++] = StackTop(&ps->Minstack);
}
}
当然,最小栈当需压栈元素大于最小栈的栈顶元素时,也可不重复压栈,相应的在出栈的时候也需做相应变化
使用两个栈实现一个队列
一个结构体封装两个栈,栈1InQueue
只做压栈操作,栈2OutQueue
只做出栈操作,当栈2为空时,将push栈中的元素依次出栈再按出栈次序依次压入栈1
存储方式
typedef struct
{
Stack InQueue;
Stack OutQueue;
}_Queue;
相关操作
void QueueInit(_Queue *pQ)
{
assert(pQ);
StackInit(&pQ->InQueue);
StackInit(&pQ->OutQueue);
}
void QueueDestroy(_Queue *pQ)
{
assert(pQ);
StackDestroy(&pQ->InQueue);
StackDestroy(&pQ->OutQueue);
}
void InQueue(_Queue *pQ, DataType data)
{
assert(pQ);
StackPush(&pQ->InQueue, data);
}
void OutQueue(_Queue *pQ)
{
assert(pQ);
//将栈1中的元素分别倒入到栈2中去
if (StackEmpty(&pQ->OutQueue))
{
while (!StackEmpty(&pQ->InQueue))
{
StackPush(&pQ->OutQueue, StackTop(&pQ->InQueue));
StackPop(&pQ->InQueue);
}
}
//出栈2,即实现出队列操作
StackPop(&pQ->OutQueue);
}
DataType QueueFront(_Queue *pQ)
{
assert(pQ);
if (StackEmpty(&pQ->OutQueue))
{
while (!StackEmpty(&pQ->InQueue))
{
StackPush(&pQ->OutQueue, StackTop(&pQ->InQueue));
StackPop(&pQ->InQueue);
}
}
//队头元素在栈2不为空时,即为栈2顶元素
return StackTop(&pQ->OutQueue);
}
int QueueEmpty(_Queue *pQ)
{
return StackEmpty(&pQ->InQueue) && StackEmpty(&pQ->OutQueue);
}
int QueueSize(_Queue *pQ)
{
return StackSize(&pQ->InQueue) + StackSize(&pQ->OutQueue);
}
不难发现,返回队头操作与出队列操作基本一致,不同的是最后栈2不出栈,只是返回栈顶元素
两个队列实现一个栈
入栈的时候:如果两个队列均为空,默认一个队列入栈;如果一个队列为空,一个队列不为空,将该元素入到不为空的队列后面
出栈的时候:仍要判断那个队列为空,此时是则将不为空的(队列元素-1)个依次出队列,入到空队列里面,然后再出掉原来不为空队列里面的最后一个元素,即实现出栈操作
存储方式
typedef struct
{
Queue q1;
Queue q2;
}_Stack;
相关操作
void StackInit(_Stack *pS)
{
assert(pS);
QueueInit(&pS->q1);
QueueInit(&pS->q2);
}
void StackDestroy(_Stack *pS)
{
assert(pS);
QueueDestroy(&pS->q1);
QueueDestroy(&pS->q2);
}
void StackPush(_Stack *pS, DataType data)
{
assert(pS);
if (QueueEmpty(&pS->q1))
{
InQueue(&pS->q2, data);
}
else if (QueueEmpty(&pS->q2))
{
InQueue(&pS->q1, data);
}
else
{
InQueue(&pS->q2, data);
}
}
int StackEmpty(_Stack *pS)
{
return QueueEmpty(&pS->q1) && QueueEmpty(&pS->q2);
}
int StackSize(_Stack *pS)
{
return QueueSize(&pS->q1) + QueueSize(&pS->q2);
}
void StackPop(_Stack *pS)
{
assert(pS);
assert(StackSize(pS) != 0);
if (QueueEmpty(&pS->q1))
{
while (QueueSize(&pS->q2) != 1)
{
InQueue(&pS->q1, QueueFront(&pS->q2));
OutQueue(&pS->q2);
}
OutQueue(&pS->q2);
}
else
{
while (QueueSize(&pS->q1) != 1)
{
InQueue(&pS->q2, QueueFront(&pS->q1));
OutQueue(&pS->q1);
}
OutQueue(&pS->q1);
}
}
DataType StackTop(_Stack *pS)
{
DataType ret = 0;
assert(pS);
assert(StackSize(pS) != 0);
if (QueueEmpty(&pS->q1))
{
while (QueueSize(&pS->q2) != 1)
{
InQueue(&pS->q1, QueueFront(&pS->q2));
OutQueue(&pS->q2);
}
//保存后将该元素出队列再入到另一队列后面,保证该队列为空
ret = QueueFront(&pS->q2);
OutQueue(&pS->q2);
InQueue(&pS->q1, ret);
return ret;
}
else
{
while (QueueSize(&pS->q1) != 1)
{
InQueue(&pS->q2, QueueFront(&pS->q1));
OutQueue(&pS->q1);
}
ret = QueueFront(&pS->q1);
OutQueue(&pS->q1);
InQueue(&pS->q2, ret);
return ret;
}
}
上面实现返回栈顶元素的操作又与使用两个栈实现一个队列的操作十分相似,不同的是这里队列的操作倒完元素后还剩一个,出栈操作是直接将剩余元素出掉,那么返回栈顶元素后别忘了将最后一个元素再入到另一个列队后面,因为后面的出入队列操作还需要依赖两个队列的空状态
判断出入栈顺序的合法性
用一个字符数组存放需要判断的入栈元素序列,另一个数组来存放需要判断出栈顺序合法性的元素序列,然后用两个指针分别向后遍历两个数组
如果两个字符相等,指针同时向后走(省去入栈再出栈的操作)
如果不相等
- (1)栈为空并且栈顶元素与当前入栈元素数组不相等,judge数组元素压栈
- (2)否则,进行出栈操作
遍历完后如果judge数组剩下的字符串顺序与栈中元素的出栈顺序相同,则表明judge数组合法,否则则表示不合法
void JudgePopStackOrder(char *s1, char *s2, int sz)
{
Stack stack;
int ii = 0;
int io = 0;
StackInit(&stack);
assert(s1 && s2);
while (ii < sz)
{
if (s1[ii] == s2[io])
{
ii++;
io++;
continue;//跳出循环,继续判断下一对元素
}
if ((!StackEmpty(&stack)) && s2[io]==StackTop(&stack))
{
StackPop(&stack);
io++;
}
else
{
StackPush(&stack, s1[ii]);
ii++;
}
}
while (!StackEmpty(&stack))
{
if (StackTop(&stack) != s2[io])
{
printf("illegal\n");
return;
}
StackPop(&stack);
io++;
}
printf("legal\n");
}
一个数组实现两个栈
两种实现方式:
- 奇偶栈:一个栈存放在数组的奇数下标位置,另一个栈存放在数组的偶数下标位置。用动态数组实现在扩容时注意两个栈的判满条件不同
- 头尾栈:分别从数组的两头存放,用动态数组实现在扩容时注意元素的搬移,头栈的元素对应下标搬运到新开辟的空间,尾栈将对应元素分别搬移到新开辟空间的原数组下标+扩容空间大小的位置
存储方式
#define DEF_SIZE 2
#define EXP_SIZE 2
typedef int DataType;
typedef struct
{
DataType *arr;
int capcity;
int top1;
int top2;
}TwoStack;
相关操作
void StackInit(TwoStack *ps)
{
DataType *tmp = NULL;
assert(ps);
tmp = (DataType *)malloc(sizeof(DataType) * DEF_SIZE);
assert(tmp);
ps->arr = tmp;
ps->capcity = DEF_SIZE;
ps->top1 = 0;
ps->top2 = ps->capcity - 1;
}
void StackDestroy(TwoStack *ps)
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->capcity = 0;
ps->top1 = 0;
ps->top2 = 0;
}
static int IsExpand(TwoStack *ps)
{
int i = 0;
DataType *tmp = NULL;
assert(ps);
if (ps->top2 < ps->top1)
{
tmp = (DataType *)malloc(sizeof(DataType) * (ps->capcity + EXP_SIZE));
if (tmp == NULL)
{
return 0;
}
for (i=0; i<ps->top1; i++)
{
tmp[i] = ps->arr[i];
}
for (i=ps->top2+1; i<ps->capcity; i++)
{
tmp[i + EXP_SIZE] = ps->arr[i];
}
ps->top2 = ps->top2 + EXP_SIZE;
ps->capcity += EXP_SIZE;
free(ps->arr);//重新赋值前先释放掉原来开辟的空间
ps->arr = tmp;
return 1;
}
return 1;
}
void StackPush1(TwoStack *ps, DataType data)
{
assert(ps);
if (IsExpand(ps))
{
ps->arr[ps->top1++] = data;
}
}
void StackPush2(TwoStack *ps, DataType data)
{
assert(ps);
if (IsExpand(ps))
{
ps->arr[ps->top2--] = data;
}
}
void StackPop1(TwoStack *ps)
{
assert(ps);
ps->top1--;
}
void StackPop2(TwoStack *ps)
{
assert(ps);
ps->top2++;
}
DataType StackTop1(TwoStack *ps)
{
assert(ps);
assert(ps->top1 != 0);
return ps->arr[ps->top1 - 1];
}
DataType StackTop2(TwoStack *ps)
{
assert(ps);
assert(ps->top2 != ps->capcity-1);
return ps->arr[ps->top2 + 1];
}
int StackSize1(TwoStack *ps)
{
assert(ps);
return ps->top1;
}
int StackSize2(TwoStack *ps)
{
assert(ps);
return ps->capcity - ps->top2 - 1;
}
int StackEmpty1(TwoStack *ps)
{
assert(ps);
return ps->top1 == 0 ? 1 : 0;
}
int StackEmpty2(TwoStack *ps)
{
assert(ps);
return ps->top2 == ps->capcity - 1 ? 1 : 0;
}