栈:是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素的操作。进行数据插入和删除操作的一端称为栈顶,另一端为栈底。
栈中的数据元素遵守后进先出原则(LIFO)原则
压栈:栈的插入操作称为进栈/压栈/入栈,其位置在栈顶
出栈:栈的删除操作称为出栈,其位置也在栈顶
数组栈(推荐方式,因为在数组尾插代价更小)
链式栈:相较数组栈无优势,且一般将链表尾作为栈底,链表头作为栈顶(单链表情况下)
typedef int STElemType;
typedef struct Stack
{
STElemType *data; //动态栈
int top;
int capacity;
}ST;
void StackInit(ST *pt)
{
pt->data = (SElemType *)malloc(N*sizeof(SElemTyp e));
if (!pt->data)
{
perror("malloc");
exit(1);
}
pt->capacity = N; //N表示初始的最大容量
pt->top = 0; //此时top指向的是栈顶元素的下一个位置,也可以定义为pt->top=-1,这样,top就是指向栈顶元素
}
void StackDestroy(ST *pt)
{
free(pt->data);
pt->top=pt->capacity=0;
}
bool StackEmpty(ST *pt)
{
return pt->top==0; //若为真即栈为空,则返回1,否则返回0
}
void StackPush(ST *pt,SElemType x)
{
if(pt->top==pt->capacity) //如果容量已满
{
pt->capacity *= 2; //将容量扩为原来的两倍
ST* temp = realloc(pt->data, pt->capacity*sizeof(SElemType));
if(!temp)
{
perror("malloc");
exit(1);
}
pt->data = temp;
}
//入栈
pt->data[pt->top]=x;
pt->top++;
}
void StackPop(ST *pt)
{
assert(!stackEmpty(pt)); //栈不能为空
//出栈
pt->top--;
}
SElemType StackTop(ST *pt)
{
assert(!stackEmpty(pt)); //栈不能为空
return pt->data[pt->top-1];
}
int StackSize(ST *pt)
{
return pt->top;
}
void StackPrint(ST *pt)
{
assert(!stackEmpty(pt)); //栈不能为空
while(!StackEmpty(pt))
{
printf("%d ",StackTop(pt)); //遵循先入后出原则,从上往下取
pt->top--;
}
}
学习完栈的基本概念和相关操作后,你可以利用栈的特性做下面的OJ题:
有效括号序列题目解析
逆波兰表达式求值题目解析
删除字符串中的所有相邻重复项题目解析
包含min函数的栈题目解析
数组队列:由于出队列出的是队头元素,因此数组队列出数据的效率低下,不推荐使用
链队列:入和出数据的效率都很高,是队列常用的表示法
typedef int QDataType; //存储的数据类型
typedef struct QueueNode //链队列的节点
{
struct QueueNode *next;
QDataType data;
}QueueNode;
typedef struct Queue //定义存放指向队头,队尾指针的结构体
{
QueueNode *head; //指向队头
QueueNode *tail; //指向队尾
}Queue;
void QueueInit(Queue *pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
void QueueDestroy(Queue *pq)
{
QueueNode *cur = pq->head; //定义临时变量
while (cur)
{
pq->head = pq->head->next; //链表下滑
free(cur); //释放空间
cur = pq->head; //更新临时变量
}
pq->tail = NULL; //空间释放完毕后head已经为空,但tail成为了野指针,所以要置空
}
bool QueueEmpty(Queue *pq)
{
assert(pq);
return pq->head == NULL;
}
void QueuePush(Queue *pq,QDataType x)
{
assert(pq);
QueueNode *newnode=(QueueNode *)malloc(sizeof(QueueNode)); //创建新节点
if (NULL == newNode)
{
perror("malloc");
exit(1);
}
newnode->data=x;
newnode->next=NULL;
if(QueueEmpty(pq)) //如果队列为空
{
pq->head=newnode; //使队头、队尾指针同时指向新节点
pq->tail=newnode;
}
else
{
pq->tail->next=newnode; //使队尾指针的指向下一个节点的指针指向新节点
pq->tail=newnode; //更新队尾指针
}
}
void QueuePop(Queue *pq)
{
assert(pq);
assert(!QueueEmpty(pq)); //队列不能为空
QueueNode *cur=pq->head; //定义临时变量保存队头指针
pq->head=pq->head->next; //使队头指针指向下一个节点
free(cur); //释放原来的队头
if(pq->head==NULL)
pq->tail=NULL; //如果节点已经全部出队,则要将队尾指针置空,防止形成野指针
}
//返回队头元素
QDataType QueueFront(Queue *pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
//返回队尾元素
QDataType QueueBack(Queue *pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
int QueueSize(Queue *pq)
{
QueueNode *cur=pq->head;
int size=0;
while(cur)
{
size++;
cur=cur->next;
}
return size;
}
//也可以在队列结构体中增加size变量,每入队一个size就加一
队列常常被用来对一些复杂数据结构的广度优先遍历,但由于目前还未学习,故不作深入讨论
除了这种最基本的只能从队尾插入数据,从队头删除数据的队列外,其实还有循环队列、双端队列、单调队列等许多复杂但功能强大的队列结构,如果小伙伴们感兴趣,也可以看看:
循环队列
双端队列 & 单调队列
如果小伙伴们愿意挑战,也可以做一做滑动窗口的最大值题目解析
这里拿两道OJ题来进行说明:
用两个栈表示队列题目解析
用两个队列表示栈题目解析