【資料結構】佇列&棧和佇列的面試題
佇列
只允許在一端(隊尾)進行插入資料操作(入佇列),在另一端(對頭)進行刪除資料操作(出佇列)的特殊線性表
對於順序佇列,若採用隊頭不動,出佇列要移動隊頭後的所有元素;若移動出佇列後向後移動隊頭,會造成“假溢位”
若採用取模迴圈法來實現迴圈佇列,又需要解決新的問題,如何區分佇列空滿的狀態
鑑於順序佇列實現起來比較麻煩,以下的佇列實現採用鏈式結構
儲存方式
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;
}