本文仅使用了两种常见的实现方法——动态数组栈、链队列,如果要看更多的队列和栈的实现方法,可以看我的下面这篇文章中有关队列和栈的部分:图的五种接口的实现,
本文的有关队列和栈的实现均已上传码云,可以来关注呀~:我的Gitee star一下再走啊
栈是一种后进先出的结构。(LIFO)
对比栈的两种实现:
数组栈:以头做栈底,尾做栈顶,尾插尾删效率很高,空间浪费也无所谓。(优势比较大)
链式栈:如果以为做栈顶,那么尾插尾删效率比较低,需要实现双向链表(否则每次都要找到表尾);如果用头做栈顶,头插头删,效率就很高,虽然节约了空间,但是优势不明显了(入栈出栈的效率相较数组栈没有区别)。
因此我们选择实现一个动态数组栈。
我们用一个动态数组来存储栈中的元素,top表示指向当前栈顶的下一个元素的下标(当栈为空的时候top的值是0),capacity表示当前栈的大小。
typedef int StackDataType;
typedef struct {
int top;
StackDataType* arr;
int capacity;
}Stack;
void StackInit(Stack* ps);
void StackPush(Stack* ps, StackDataType e);
void StackPop(Stack* ps);
StackDataType StackTop(Stack* ps);
int StackSize(Stack* ps);
bool StackEmpty(Stack* ps);
void Stackdestroy(Stack* ps);
初始化栈就是把栈的top值置成0,capacity置成0,arr置成NULL。
void StackInit(Stack* ps)
{
assert(ps);
ps->capacity = 0;
ps->arr = NULL;
ps->top = 0;
//top指0意味着指向栈顶数据的下一个位置
//ps->top = -1;
//top指-1意味着它指向栈顶数据
}
由于是数组栈,我们每次入栈前可以查询栈的空间是否足够,并且用realloc扩容,然后在数组的top位置插入e,然后top++。
void checkcapacity(Stack* ps)
{
if (ps->capacity == ps->top)
{
int newcapacity = (ps->arr == NULL ? 4 : 2 * ps->capacity);
StackDataType* tmp = (StackDataType*)realloc(ps->arr, newcapacity*sizeof(StackDataType));
if (tmp == NULL)
{
printf("realloc fault\n");
exit(-1);
}
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
void StackPush(Stack* ps, StackDataType e)
{
assert(ps);
checkcapacity(ps);
ps->arr[ps->top] = e;
ps->top++;
}
如果top==0,则栈为空,否则栈不为空。
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
//return ps->top == -1;
}
首先判断栈是否为空,然后top–。
void StackPop(Stack* ps)
{
assert(ps && StackEmpty(ps) != true);
ps->top--;
}
首先检查一下栈是否为空,然后返回数组下标为top的元素。
StackDataType StackTop(Stack* ps)
{
assert(ps && StackEmpty(ps) != true);
return ps->arr[ps->top - 1];
}
返回top的值即可。
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
free掉申请来的arr,然后把arr置成NULL,top和capacity都置成0。
void Stackdestroy(Stack* ps)
{
assert(ps);
ps->top = 0;
ps->capacity = 0;
free(ps->arr);
ps->arr = NULL;
}
队列是一种先进先出的结构。(FIFO)
实现这种结构有很多方法,我们下面用C语言实现不带头的链式队列。
既然是链式队列,那么思路就是去仿照单链表,定义一个head指针和一个tail指针,入队列用头插,出队列用尾删,具体来说,我们的队列的结构和要实现的接口如下:
typedef int QDataType;
typedef struct QueueNode {
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct {
QNode* head;
QNode* tail;
}Queue;
//不带哨兵位的链队列
void QueueInit(Queue* pq);
bool QueueEmpty(Queue* pq);
void QueuePush(Queue* pq, QDataType e);
void QueuePop(Queue* pq);
QDataType Queuefront(Queue* pq);
QDataType Queueback(Queue* pq);
int QueueSize(Queue* pq);
void Queuedestroy(Queue* pq);
对于不带头结点的链式队列,初始化要完成的任务就是把head和tail都置空,表明当前队列为空,并且方便后续插入。
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;
}
与单链表的尾插类似,同样要特别注意单链表为空的情况。
void QueuePush(Queue* pq, QDataType e)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
printf("malloc fault\n");
exit(-1);
}
newnode->data = e;
newnode->next = NULL;
if (QueueEmpty(pq) == true)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
出队列类似单链表的头删,要注意的是删除队列最后一个元素时要同时把tail和head都置空,并且注意如果队列空了就不要再删除了。
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->tail == pq->head)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
}
这里有两种实现方式,一个可以在队列的结构体中加入一个无符号整形变量size用来表示队列大小,每次入队列size++,出队列size–;另一种则是我们下面的实现方式,遍历一遍队列,用计数器返回队列长度。
int QueueSize(Queue* pq)
{
assert(pq);
int count = 0;
QNode* cur = pq->head;
while (cur)
{
cur = cur->next;
count++;
}
return count;
}
这个简单,返回head和tail所指的元素就行了,注意检查队列是否为空。
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;
}
仿照单链表的销毁即可。
void Queuedestroy(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
这种括号匹配问题我们可以用栈来实现。
把我们有关栈的代码全部贴上(如果用cpp可以用STL库里头的栈)。
创建一个栈,
遇到左括号 入栈;
遇到右括号 出栈匹配;
最后看看栈是否为空;
如果一上来就遇到右括号 也就是说一上来就想出栈 栈为空;
那就return false
记得在每次return之前都要销毁我们的栈。
bool isValid(char * s)
{
//思路:遇到左括号 入栈
//遇到右括号 出栈匹配
//最后看看栈是否为空
//如果一上来就遇到右括号 也就是说一上来就想出栈 栈为空
//那就return false
Stack st;
StackInit(&st);
while(*s)
{
if(*s == '(' || *s == '[' || *s == '{')
{
StackPush(&st, *s);
s++;
}
if(*s == '}' || *s == ')' || *s == ']')
{
if(StackEmpty(&st)==true)
{
Stackdestroy(&st);
return false;
}
StackDataType x = StackTop(&st);
StackPop(&st);
if((*s == '}' && x != '{') || (*s == ']' && x != '[') || (*s == ')' && x != '('))
{
Stackdestroy(&st);
return false;
}
s++;
}
}
if (StackEmpty(&st) == true)
{
Stackdestroy(&st);
return true;
}
else
{
Stackdestroy(&st);
return false;
}
}
用两个队列来实现栈;
//思路:用两个队列来实现栈,当入栈的时候,就让元素加入一个队列;
//要出栈的时候,就把队列中除最后一个元素外都加入另一个队列中,在原本的队列中移除最后一个元素
//要查看栈顶元素就先把非空队列中除最后一个元素都加入到另一个队列中 然后返回最后一个元素
//然后把这个元素也加入另一个队列里头 使原队列为空
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* st = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&st->q1);
QueueInit(&st->q2);
return st;
}
void myStackPush(MyStack* obj, int x) {
Queue* emptyQ = &obj->q1;
Queue* inemptyQ = &obj->q2;
if(!QueueEmpty(&obj->q1))
{
emptyQ = &obj->q2;
inemptyQ = &obj->q1;
}
QueuePush(inemptyQ, x);
}
int myStackPop(MyStack* obj) {
Queue* emptyQ = &obj->q1;
Queue* inemptyQ = &obj->q2;
if(!QueueEmpty(&obj->q1))
{
emptyQ = &obj->q2;
inemptyQ = &obj->q1;
}
while (QueueSize(inemptyQ)>1)
{
QueuePush(emptyQ, Queuefront(inemptyQ));
QueuePop(inemptyQ);
}
int ret = Queuefront(inemptyQ);
QueuePop(inemptyQ);
return ret;
}
int myStackTop(MyStack* obj) {
if (QueueEmpty(&obj->q1) == true)
{
return Queueback(&obj->q2);
}
else
{
return Queueback(&obj->q1);
}
}
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&obj->q1) == true && QueueEmpty(&obj->q2) == true;
}
void myStackFree(MyStack* obj) {
Queuedestroy(&obj->q1);
Queuedestroy(&obj->q2);
free(obj);
}
用两个栈来实现队列,一个栈负责入队列操作,记为pushST,另一个栈负责出队列操作,记为popST。
typedef struct {
Stack pushST;
Stack popST;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* q = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&q->pushST);
StackInit(&q->popST);
return q;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->pushST, x);
}
int myQueuePop(MyQueue* obj) {
if (StackEmpty(&obj->popST))
{
while (!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST, StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
int ret = StackTop(&obj->popST);
StackPop(&obj->popST);
return ret;
}
int myQueuePeek(MyQueue* obj) {
if (StackEmpty(&obj->popST))
{
while (!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST, StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
int ret = StackTop(&obj->popST);
return ret;
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->pushST) && StackEmpty(&obj->popST);
}
void myQueueFree(MyQueue* obj) {
Stackdestroy(&obj->pushST);
Stackdestroy(&obj->popST);
free(obj);
}
循环队列的关键在于空出一个位置,以此来区分空队列和满队列,不然当满队列的时候由于是循环队列,队头和队尾指针会重合。然而初始化的时候,front和rear是重合的,这就无法区分队列为空和队列为满的情况。
数组实现循环队列的关键在于用%来使超过数组大小范围的front和rear值回到数组内。
typedef struct {
int* a;
int size;
int front;
int rear;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
cq->a = (int*)malloc((k+1)*sizeof(int));
cq->size = k;
cq->front = cq->rear = 0;
return cq;
}
bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))
{
return false;
}
obj->a[obj->rear] = value;
obj->rear++;
obj->rear %= (obj->size + 1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return false;
}
obj->front++;
obj->front %= (obj->size + 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;
}
//rear指的不是尾巴元素 是尾巴元素的后一个元素 需要处理一下
int i = (obj->rear + obj->size)%(obj->size + 1);
return obj->a[i];
//或者这样处理
//if (obj->rear == 0)
//{
// return obj->a[obj->size];
//}
//return obj->a[obj->rear - 1];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->rear == obj->front;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->rear + 1)%(obj->size + 1) == obj->front;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
typedef struct Node{
int data;
struct Node* next;
}Node;
typedef struct {
Node* front;
Node* rear;
Node* head;
int size;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
cq->size = k;
cq->front = (Node*)malloc((k+1)*sizeof(Node));
Node* tmp = cq->front;
for (int i = 1; i <= k; i++)
{
tmp->next = tmp + 1;
tmp = tmp->next;
}
tmp->next = cq->front;
cq->rear = cq->front;
cq->head = cq->front;
return cq;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (myCircularQueueIsFull(obj))
{
return false;
}
obj->rear->data = value;
obj->rear = obj->rear->next;
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return false;
}
obj->front = obj->front->next;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->front->data;
}
int myCircularQueueRear(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return -1;
}
Node* cur = obj->front;
while (cur->next != obj->rear)
{
cur = cur->next;
}
return cur->data;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return obj->rear->next == obj->front;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->head);
free(obj);
}
对比两种思路可以发现,单链表实现虽然结构和创建复杂了一些,但是的入队列操作、出队列操作、判断队列是否为空、判断队列是否满、返回队首元素、返回队尾元素都比数组实现要简单很多。