作战地图如下
:

栈:
一,栈是啥???:
栈是一种线性(每个数据的相对位置前后在空间或逻辑上连续)的数据结构,主要涉及压栈和出栈两种操作。压栈是指向一组数据的尾部添加新数据,出栈是指从一组数据的尾部移除数据。数据"后进先出"
二,模拟实现:
由于栈仅涉及对尾部的操作,因此选择用数组(其实链表也可)实现。
#include<stdio.h>
#include<stdlib.h> //使用realloc时要包含
#include<stdbool.h> //使用bool类型时要包含
#include<assert.h> //使用assert断言时要包含
typedef int datatype; //方便后续更改类型
typedef struct stack
{
datatype *arr;
int top;
int capacity;
}stack;
//初始化栈
void InitStack(stack *p)
{
assert(p);
p->capacity = 0;
p->top = -1;
p->arr = NULL;
}
//入栈
void PushStack(stack *p, datatype data)
{
{
assert(p);
if (p->capacity == (p->top + 1))
{
int newcapacity = p->capacity == 0 ? 4 : 2* p->capacity;
datatype* newp = (datatype*)realloc(p->arr, newcapacity* sizeof(datatype));
if (newp == NULL)
{
perror("realloc failed!");
return;
}
p->arr = newp;
p->capacity = newcapacity;
}
p->arr[++(p->top)] = data;
}
//出栈
void PopStack(stack *p)
{
assert(p);
assert(p->top >= 0);
--(p->top); //访问数组元素时使用top作为下标,减少top即可
}
//获取栈顶元素
datatype Stacktop(stack *p)
{
assert(p);
assert(p->top >= 0);
return p->arr[p->top];
}
//获取栈中有效元素个数
int Stacksize(stack *p)
{
assert(p);
assert(p->top >= 0);
return p->top+1;
}
//检查栈为空
bool Stackempty(stack* p)
{
assert(p);
return p->top==-1;
}
//销毁栈
void DestoryStack(stack* p)
{
assert(p);
free(p->arr);
p->arr = NULL;
}
三,模拟实现的细节补充:
① top的初始值:由于使用数组实现,第一个元素下标为0,而我们对习惯把对整形时初始化为0,这样就会出现歧义,如下图:

解决方法有两种:

② 保留功能单一,代码量少的函数:例如获取栈顶元素的函数,看似写成函数是多此一举,其实至少要考虑一下两点
- 如果在函数的声明和定义分离式,使用者不知明晰top的真实含义(即top是指向栈顶元素位置还是指向栈顶元素的下一个位置的下标),就会有越界访问的风险。
- 写成函数是可以添加必要的检查代码(如assert断言),可以防止诸如对空栈进行删除的操作
四、应用:20. 有效的括号 - 力扣(LeetCode)

栈接口的实现…………
bool isValid(char* s)
{
stack st;
InitStack(&st);
while(*s) //当字符串的走到\0是结束
{
if(*s == '(' || *s == '{' || *s == '[') //遇到左括号时入栈
{
PushStack(&st,*s);
}
else
{
if(Stackempty(&st))
{
return false; //处理字符串只有一个左括号的情况
}
datatype top_value = Stacktop(&st);
PopStack(&st);
if(top_value == '(' && *s != ')'
|| top_value == '{' && *s != '}'
|| top_value == '[' && *s != ']')
{
return false;
}
}
s++;
}
bool ret = Stackempty(&st); //处理字符串只有一个左括号的情况
return ret;
}
队列:
一、队列是啥???:
队列是一种线性的数据结构(每个数据的相对位置前后在空间或逻辑上连续),主要操作涉及入队列和出队列。入队列和栈类似,是在队尾添加数据,而出队列是在队头移除数据,正因如此才造成了队列有别于栈的特性----"先进先出"。
二、模拟实现
由于队列删除数据涉及第一个元素的修改,使用数组实现的话涉及挪动后面的元素,效率低下。所以用单链表(单向不带头不循环)实现(用双向链表会多消耗一个指针变量的内存,不推荐)。
typedef int datatype;
typedef struct queuenode
{
datatype data;
struct queuenode *next;
}qnode;
typedef struct ftnode
{
qnode* phead;
qnode* ptail;
int size;
}ftnode;
// 初始化队列
void InitQueue(ftnode* p)
{
assert(p);
p->phead = p->ptail = NULL;
p->size = 0;
}
//销毁
void Destory(ftnode * p)
{
assert(p);
qnode* cur = p->phead;
while (cur)
{
qnode* next = cur->next;
free(cur);
cur = next;
}
p->phead = p->ptail = NULL;
p->size = 0;
}
//队尾插入
void Push(ftnode* p,datatype x)
{
assert(p);
// 创建新节点
qnode* newnode = (qnode*)malloc(sizeof(qnode));
if (!newnode)
{
perror("malloc failed!!!");
return;
}
newnode->data = x;
newnode->next = NULL;
if (!(p->phead))
{
p->phead = p->ptail = newnode;
}
else
{
p->ptail->next = newnode;
p->ptail = newnode;
}
p->size++;
}
//队头删除
void Pop(ftnode *p)
{
assert(p);
if (p->size == 0) // 检查队列是否为空
{
fprintf(stderr, "Queue underflow!\n");
return;
}
qnode* next = p->phead->next;
free(p->phead);
p->phead = next;
if (p->phead == NULL) // 如果队列为空,更新 ptail
{
p->ptail = NULL;
}
p->size--;
}
//获取队头数据
datatype DataHead(ftnode *p)
{
assert(p);
return p->phead->data;
}
//获取队尾数据
datatype DataTail(ftnode* p)
{
assert(p);
return p->ptail->data;
}
三、模拟实现的细节补充:
①巧妙地避开二级指针:由于链表通过next指针寻找相邻节点的特性,每个节点都要定义为一个指针,声明和定义头结点时也是如此,因此进行函数传参时要使用二级指针来修改外部的头结点。这样不仅不易理解而且实际操作起来也比较繁琐 (二级指针同样使我难受
),为了解决这个问题,在定义好节点的结构体后,再把特殊的节点(头结点phead和尾结点ptail)以指针的形式用另一个结构体定义好,这样只用通过这个结构体变量的指针就能对实际的节点进行修改了(因为这时直接就能访问到地址,从而对修改节点)。
②更方便的获取元素个数:第一点中提到了把特殊节点进行封装的其中一个好处,不仅如此,因为phead和ptail这两个指针同时也代表了队列所关系的队头和队尾元素,所以每次插入或删除都会对他们有影响。那么在这个结构体中再加入一个size来表示元素的个数就会很方便,插入时顺便让size++,删除时顺便让size--,还省去了写一个计算元素个数的函数,何乐而不为?![]()
四、622. 设计循环队列 - 力扣(LeetCode)

typedef struct
{
int *arr;
int head;
int tail;
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue *obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->arr = (int*)malloc(sizeof(int)*(k+1));
obj->head = obj->tail = 0;
obj->k = k; //////////////
return obj;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) ;
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if(myCircularQueueIsFull(obj))
{
return false;
}
obj->arr[obj->tail] = value;
obj->tail++;
obj->tail %= obj->k+1;
return true;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return false;
}
obj->head++;
obj->head %= obj->k+1;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->arr[obj->head];
}
int myCircularQueueRear(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
else
return obj->tail == 0 ? obj->arr[obj->k] : obj->arr[obj->tail-1];
//return obj->arr[(obj->tail - 1 + obj->k + 1) % (obj->k + 1)];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->head == obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
return obj->head == (obj->tail+1) % (obj->k+1);
}
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->arr);
obj->arr = NULL;
free(obj);
obj = NULL;
}
“栈和队列的相互转换”:
一、232. 用栈实现队列 - 力扣(LeetCode)
①题意:使用两个栈实现一个“先进先出”的队列,使之能实现队头删除、队尾插入、获取队头元素以及队列判空等4个基本功能。
②思路分析:栈的队尾插入和队列类似,而判空则是两个栈同为空即可,并不特殊,所以着重分析队头删除和获取队头元素。如下图:


栈声明和接口接口 略......
typedef struct
{
stack *push_stack; //专门用于入数据
stack *pop_stack; ///专门用于出数据
} MyQueue;
MyQueue* myQueueCreate()
{
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
obj->push_stack = (stack*)malloc(sizeof(stack));
obj->pop_stack = (stack*)malloc(sizeof(stack));
InitStack(obj->push_stack);
InitStack(obj->pop_stack);
return obj;
}
void myQueuePush(MyQueue* obj, int x)
{
PushStack(obj->push_stack,x);
}
int myQueuePeek(MyQueue* obj)
{
if(Stackempty(obj->pop_stack))
{
while(!Stackempty(obj->push_stack))
{
int mid = Stacktop(obj->push_stack);
PopStack(obj->push_stack);
PushStack(obj->pop_stack,mid);
}
}
return Stacktop(obj->pop_stack);
}
int myQueuePop(MyQueue* obj)
{
int ret = myQueuePeek(obj);
PopStack(obj->pop_stack);
return ret;
}
bool myQueueEmpty(MyQueue* obj)
{
return Stackempty(obj->pop_stack) && Stackempty(obj->push_stack);
}
void myQueueFree(MyQueue* obj)
{
InitStack(obj->push_stack);
InitStack(obj->pop_stack);
free(obj);
obj = NULL;
}
二、225. 用队列实现栈 - 力扣(LeetCode)
①题意:使用两个队列实现一个数据"先进后出"的栈,实现入栈、获取栈顶元素、删除栈顶元素以及判断是否为空栈等4个功能。
②思路分析:入栈只需和队列一样在尾部插入即可,两个队列都为空则栈为空。要着重分析获取栈顶元素和删除栈顶元素,如下图:


3253

被折叠的 条评论
为什么被折叠?



