一、栈
栈的概念及结构:
栈:
一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端
称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈(入栈):
栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:
栈的删除操作叫做出栈。出数据也在栈顶。
栈遵循后进先出的规则,即先放入栈的数据后出栈,后放入栈的数据先出栈。
栈的实现:
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的
代价比较小。
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;//栈顶,初始为0,表示栈顶下一位置的坐标
int capacity;
}ST;
//初始化
void StackInit(ST* ps);
//销毁栈
void StackDestroy(ST* ps);
//打印栈
void StackPrint(ST* ps);
//添加数据
void StackPush(ST* ps, STDataType x);
//删除数据
void StackPop(ST* ps);
//返回栈顶数据
STDataType StackTop(ST* ps);
//判断栈为空
bool StackEmpty(ST* ps);
//栈的大小
int StackSize(ST* ps);
//初始化
void StackInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
perror("malloc fail");
exit(-1);
}
ps->top = 0;
ps->capacity = 4;
}
//销毁栈
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
//打印栈
void StackPrint(ST* ps)
{
assert(ps);
int size = StackSize(ps);
for (int i = 0; i < size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//添加数据
void StackPush(ST* ps,STDataType x)
{
assert(ps);
//扩容
if (ps->top == ps->capacity)
{
STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
//先存放数据,后++;
ps->a[ps->top] = x;
ps->top++;
}
//删除数据
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));//栈不能为空
ps->top--;
}
//返回栈顶数据
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
//判断栈为空
bool StackEmpty(ST* ps)
{
assert(ps);
/*if (ps->top == 0)
{
return true;
}
else
{
return false;
}*/
return ps->top == 0; //一个判断条件
}
//栈的大小
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
二、队列
队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头
队列从队尾插入数据,从队头删除数据,即先进先出。
队列的实现
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* Head;
QNode* Tail;
int size;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
bool QueueEmpty(Queue* pq);
QDataType QueueHead(Queue* pq);
QDataType QueueTail(Queue* pq);
int QueueSize(Queue* pq);
设置头尾指针,方便插入删除数据。
void QueueInit(Queue* pq)
{
assert(pq);
pq->Head = NULL;
pq->Tail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->Head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->Tail = pq->Head = NULL;
pq->size = 0;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->Tail == NULL && pq->Head == NULL;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (pq->Tail == NULL)
{
pq->Tail =pq->Head = newnode;
}
else
{
pq->Tail->next = newnode;
pq->Tail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->Head->next == NULL)
{
free(pq->Head);
pq->Head = pq->Tail = NULL;
}
else
{
QNode* del = pq->Head;
pq->Head = pq->Head->next;
free(del);
}
pq->size--;
}
QDataType QueueHead(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->Head->data;
}
QDataType QueueTail(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->Tail->data;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
三、栈和队列的面试题
有效的括号:
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
解题思路:
观察本题不难看出,要左侧符号与右侧符号保证顺序闭合,运用栈后进先出的特性最为合适,首先,遍历整个字符串,将左侧符号入栈中,当遍历到右侧符号时,将栈顶元素取出并与之比较,比较后删除栈顶元素,然后接着遍历右侧符号并与栈顶元素对比。
此题会有以下三种情况需要考虑:
typedef char STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
perror("malloc fail");
exit(-1);
}
ps->top = 0;
ps->capacity = 4;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
void StackPush(ST* ps,STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
return ps->a[ps->top - 1];
}
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
bool isValid(char* s)
{
ST st;
StackInit(&st);
while(*s)
{
//将左侧符号入栈
if(*s =='('||*s=='['||*s=='{')
{
StackPush(&st,*s);
s++;
}
//右侧符号
else
{
//当左侧符号都入栈时,栈还为空
if(StackEmpty(&st))
{
StackDestroy(&st);
return false;
}
//遇到右侧符号与栈顶元素相比,并删除参与比较的栈顶元素
char top = StackTop(&st);
StackPop(&st);
//右侧符号与栈顶元素不等
if(*s==')' && top!='('||
*s==']' && top!='['||
*s=='}' && top!='{')
{
StackDestroy(&st);
return false;
}
//相等则继续比较
else
{
s++;
}
}
}
bool ret = StackEmpty(&st);
StackDestroy(&st);
return ret;
}
用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
解题思路:
可知栈为后进先出的特性,而队列为先进先出的性质,那如何用两个队列来实现栈的功能?
首先,一个队列肯定是无法实现栈,但两个队列,我们可以在两个队列之间倒数据来实现栈的功能,栈与队列插入数据的方法相同,不同的是出数据时删除的数据,如下,将数据1234插入到其中一个队列中,而队列中首先出的数据一定为队头数据即1,而依照栈的结构,出数据时删除的应为4,那么我们只需将有数据的队列中的数据依次删除,并将其插入到另一个空队列中,直到剩下一个数据,该数据即为栈结构应删除的数据,然后再删除改数据,这样又形成了一个非空队列和一个空队列的状态,然后循环往复,两个队列的状态始终为一个有数据一个为空。
typedef int QDatatype;
typedef struct QueueNode
{
QDatatype data;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDatatype x);
void QueuePop(Queue* pq);
bool QueueEmpty(Queue* pq);
QDatatype QueueHead(Queue* pq);
QDatatype QueueTail(Queue* pq);
int QueueSize(Queue* pq);
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* del = cur;
cur = cur->next;
free(del);
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDatatype x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (pq->tail == NULL)
{
pq->tail = pq->head = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* del = pq->head;
pq->head = pq->head->next;
free(del);
}
pq->size--;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
//Ϊȷ
return pq->head == NULL && pq->tail == NULL;
}
QDatatype QueueHead(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDatatype QueueTail(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
typedef struct {
Queue q1;
Queue q2;
} MyStack;//不需要再躲开辟两个队列的空间
bool myStackEmpty(MyStack* obj) {
assert(obj);
return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
MyStack* myStackCreate() {
MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&obj->q1);
QueueInit(&obj->q2);
return obj;
}
void myStackPush(MyStack* obj, int x) {
assert(obj);
//向非空的队列插入数据
if(!QueueEmpty(&obj->q1))
{
QueuePush(&obj->q1,x);
}
else
{
QueuePush(&obj->q2,x);
}
}
int myStackPop(MyStack* obj) {
assert(obj);
assert(!myStackEmpty(obj));
//分清非空队列与空队列
Queue* empty = &obj->q1, *nonempty = &obj->q2;
if(!QueueEmpty(&obj->q1))
{
empty = &obj->q2;
nonempty = &obj->q1;
}
//向空队列中导入非空队列的前n-1项,并将非空队列中的前n-1项删除
while(QueueSize(nonempty)>1)
{
QueuePush(empty,QueueHead(nonempty));
QueuePop(nonempty);
}
//取非空队列的队尾,即"栈顶"
int top = QueueHead(nonempty);
QueuePop(nonempty);
return top;
}
int myStackTop(MyStack* obj) {
assert(obj);
assert(!myStackEmpty(obj));
//取"栈顶"也就是队列尾
if(!QueueEmpty(&obj->q1))
{
return QueueTail(&obj->q1);
}
else
{
return QueueTail(&obj->q2);
}
}
void myStackFree(MyStack* obj) {
assert(obj);
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}
用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
解题思路:
通过观察,此题与上一题似乎有些相似处,基本原理不变,运用两个相同的数据结构来实现另一个结构,原理还是在两个栈之间倒数据来实现队列功能,但方法上略有不同,通过分析,我们可知两个栈,在其中一个栈导入数据,然后将此栈中的数据导出到另一个栈中,数据的顺序会发生变化,如下,将数据1234插入其中一个栈,然后将其导入到灵一个栈中,数据顺序自然颠倒,然后将其删除时,正好符合队列的特性(先进先出),这样我们就可以建立的两个栈,分别规定其作用,即一个只存入数据,一个倒数据并删除数据,需要注意的是,两个栈的状态始终为一个有数据一个没有数据,只有处于这种状态,我们才可以向入数据的栈中输入数据。
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPrint(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
bool StackEmpty(ST* ps);
int StackSize(ST* ps);
void StackInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
perror("malloc fail");
exit(-1);
}
ps->top = 0;
ps->capacity = 4;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
void StackPrint(ST* ps)
{
assert(ps);
int size = StackSize(ps);
for (int i = 0; i < size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
void StackPush(ST* ps,STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
typedef struct {
ST Pushst;
ST Popst;
} MyQueue;
//分为入数据栈和出数据栈
MyQueue* myQueueCreate() {
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&obj->Pushst);
StackInit(&obj->Popst);
return obj;
}
bool myQueueEmpty(MyQueue* obj) {
assert(obj);
return StackEmpty(&obj->Pushst) && StackEmpty(&obj->Popst);
}
int myQueuePeek(MyQueue* obj) {
assert(obj);
assert(!myQueueEmpty(obj));
//将入数据栈的数据导入出数据栈,此过程完成数据的逆置,直接可以在出数据栈删除数据
if(StackEmpty(&obj->Popst))
{
while(!StackEmpty(&obj->Pushst))
{
StackPush(&obj->Popst,StackTop(&obj->Pushst));
StackPop(&obj->Pushst);
}
}
return StackTop(&obj->Popst);
}
void myQueuePush(MyQueue* obj, int x) {
assert(obj);
StackPush(&obj->Pushst,x);
}
int myQueuePop(MyQueue* obj) {
assert(obj);
assert(!myQueueEmpty(obj));
int peek = myQueuePeek(obj);
//逆置后的数据可以直接删除
StackPop(&obj->Popst);
return peek;
}
void myQueueFree(MyQueue* obj) {
assert(obj);
StackDestroy(&obj->Pushst);
StackDestroy(&obj->Popst);
free(obj);
}
设计循环队列
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
解题思路:
一般情况下,我们总是用链表来实现队列,但根据此题并画图分析,用链表结构在实现取队尾数据时,由于是单链表,无法上一个节点的地址,只能遍历一遍链表,增加了程序的时间复杂度,并不建议采用。
故采用数组的方式来实现环形队列,从上图可以看出在rear位置空了一个空间没有存储数据,目的是来判断环形队列的状态,即空或满,用数组实现时亦采用这种方法。
当入下图这种情况时队列也是满
在使用malloc函数开辟空间时,我们需要为数组a多开辟一个空间来使用上述方法,剩下需要注意的是在获取尾部数据时,需要考虑以下这种情况:
typedef struct {
int* a;
int front;
int rear;
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//躲开辟一个空间用来判断环形队列的状态
obj->a = (int*)malloc(sizeof(int)*(k+1));
obj->front = obj->rear = 0;
obj->k = k;
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
assert(obj);
return obj->front == obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
assert(obj);
return (obj->rear+1) % (obj->k+1) == obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
assert(obj);
if(myCircularQueueIsFull(obj))
{
return false;
}
obj->a[obj->rear++] = value;
//循环
if(obj->rear == obj->k+1)
{
obj->rear = 0;
}
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
assert(obj);
if(myCircularQueueIsEmpty(obj))
{
return false;
}
obj->front++;
//循环
if(obj->front == obj->k+1)
{
obj->front = 0;
}
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
assert(obj);
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->a[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
assert(obj);
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
//特殊情况
int rear = obj->rear == 0 ? obj->k : obj->rear-1;
return obj->a[rear];
}
void myCircularQueueFree(MyCircularQueue* obj) {
assert(obj);
free(obj->a);
free(obj);
}