① 在函数中判断结构体指针是否为空时采用assert函数,而不是用if语句判断。
② 函数的命名规则遵循:操作+结构类型名 的规则。例如 InitSqList 与DestroySqList。
③ 严蔚敏老师一书中很多运用了C++的语法,而我们是用C语言来实现,因此编写规则与书上会有很多不同,但是思路是一样的。例如用malloc代替new,free代替delete,引用与指针的区别等。
④ 本篇判断栈满和栈空需要用到bool变量
栈是限定只在表尾插入和删除的数据结构,满足LIFO( Last In First Out )的规则。栈是操作受限的线性表,其特殊性在于栈的基本操作可以看成是线性表操作的子集。
栈的应用场景有:数制的转换,括号匹配的检验,表达式求值等场景。
在程序设计中,如果需要按照保存数据时相反的顺序来使用数据则可以利用栈来实现。
例如回文链表问题等。
剑指 Offer II 027. 回文链表
栈的实现可以用数组实现顺序栈,或者采用链式结构实现链栈。
void InitStack(Stack* s); 初始化
void DestoryStack(Stack* s); 摧毁
void PushStack(Stack* s,StackDataType i); 入栈
void PopStack(Stack* s); 出栈
bool IsEmptyStack(Stack* s); 判断栈空
bool IsFullStack(Stack* s); 判断栈满
StackDataType GetTopStack(Stack* s); 取栈顶元素
typedef int StackDataType;
#define IncNumStack 5
typedef struct Stack
{
StackDataType* data;
int top; //标识栈顶元素的下一个位置
int size;
int capacity;
}Stack;
top用于表示栈顶元素的下一个位置。当top = 0时,表示栈空,当top = capacity时,表示栈满。size表示当前栈存储的元素个数,capacity表示栈的总容量。
void InitStack(Stack* s)
{
assert(s);
s->data = (StackDataType*)malloc(sizeof(IncNumStack));
s->capacity = 0;
s->size = 0;
s->top = 0;//top是栈顶元素的下一个位置
}
定义一个IncNumStack用于每次容量不足的时候增容。
void DestoryStack(Stack* s)
{
assert(s);
free(s->data);
s->data = NULL;
s->capacity = 0;
s->size = 0;
s->top = 0;
}
bool IsEmptyStack(Stack* s)
{
assert(s);
return s->top == 0;
}
bool IsFullStack(Stack* s)
{
assert(s);
return s->top == s->capacity;
}
void PushStack(Stack* s, StackDataType i)
{
assert(s);
if (IsFullStack(s))
{
int newcapacity = s->capacity == 0 ? IncNumStack : (s->capacity + IncNumStack);
StackDataType* tmp = (StackDataType*)realloc(s->data, newcapacity * sizeof(StackDataType));
if (tmp == NULL)
{
printf("扩容失败!\n");
exit(-1);
}
else
{
s->data = tmp;
s->capacity = newcapacity;
}
printf("\n扩容成功\n");
}
s->data[s->top] = i;
s->top++;
s->size++;
}
void PopStack(Stack* s)
{
assert(s);
if (IsEmptyStack(s))
{
printf("\n栈为空!弹栈失败!\n");
return;
}
s->top--;
s->size--;
}
StackDataType GetTopStack(Stack* s)
{
assert(s);
if(!IsEmptyStack(s))
return s->data[s->top - 1];
else
{
printf("\n栈为空!获取栈顶元素失败!\n");
return -1;
}
}
这里注意要取top-1处的元素。
到这里顺序栈就介绍结束了。但是顺序栈和顺序表一样,受到最大空间容量的限制,因此如果不能预先估计工作场景下栈的容量大小,我们同样可以采用链式方式实现链栈。
void InitLinkStack(LinkStack** ppls); 初始化
void DestroyLinkStack(LinkStack** ppls); 摧毁
void PushLinkStack(LinkStack** ppls, LSDataType i); 入栈
void PopLinkStack(LinkStack** ppls); 出栈
LSDataType GetTopLinkStack(LinkStack** ppls); 取栈顶元素
bool IsEmptyLinkStack(LinkStack** ppls); 判断是否为空
typedef int LSDataType;
typedef struct LS
{
LSDataType data;
struct LS* next;
}LinkStack;
因为链栈不会满,所以只有判空函数。这里注意我们要传二级指针。使用时
LinkStack* ls;
InitLinkStack(&ls);
要像链表那样,定义一个指针,再传这个指针的地址,才能实现某些函数的功能,例如初始化。为了保证接口形式一致,我们所有函数都选择传二级指针。链表一篇我们采用了相同的方法,详情请移步单链表篇:
C语言实现数据结构——单链表
void InitLinkStack(LinkStack** ppls)
{
assert(ppls);
*ppls= (LinkStack*)malloc(sizeof(LinkStack));
if (!*ppls)
{
printf("初始化失败\n");
exit(-1);
}
(*ppls)->data = 0;
(*ppls)->next = NULL;
}
void DestroyLinkStack(LinkStack** ppls)
{
assert(ppls);
LinkStack* cur = *ppls;
while (cur)
{
LinkStack* next = cur->next;
free(cur);
cur = next;
}
ppls = NULL;
}
void PushLinkStack(LinkStack** ppls,LSDataType i)
{
assert(ppls);
LinkStack* newnode = (LinkStack*)malloc(sizeof(LinkStack));
if (newnode)
{
newnode->data = i;
newnode->next = *ppls;
*ppls = newnode;
}
else
{
printf("结点开辟失败\n");
return;
}
}
void PopLinkStack(LinkStack** ppls)
{
assert(ppls);
if (IsEmptyLinkStack(ppls))
{
printf("栈空,出栈失败\n");
return;
}
LinkStack* next = (*ppls)->next;
free(*ppls);
*ppls = next;
}
LSDataType GetTopLinkStack(LinkStack** ppls)
{
assert(ppls);
if (IsEmptyLinkStack(ppls))
{
printf("栈空,取栈顶元素失败\n");
return -1;
}
return (*ppls)->data;
}
bool IsEmptyLinkStack(LinkStack** ppls)
{
assert(ppls);
return (*ppls)->next == NULL;
}
顺序栈是顺序表的子集,链式栈相似的是链表的子集,栈这种数据结构能解决很多特定场景下的问题,是一种很优秀的数据结构。
学完了顺序表和链表之后,栈这两种形式敲起来得心应手。