完整代码链接:DataStructure: 基本数据结构的实现。 (gitee.com)
目录
一、栈的概念:
栈是一种特殊的线性表,其只允许在固定的一端执行插入与删除操作。进行数据插入与删除的一端为栈顶,另一端为栈底。栈中的数据元素遵循LIFO(Last In First Out)原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈,出数据也在栈顶。
二、栈的实现:
栈的实现一般可以由数组或链表实现,但是相对而言数组的结构更优一些,因为数组在尾上插入的代价比较小。
1.栈顶与栈底的选择:
①.数组:
类比于顺序表,我们这里将栈里面的成员变量名size改为top(只是命名不一样,你也可以设为size) 。
我们将数组的头设为栈底(如果将数组的尾设为栈底,如果入栈的数据量大于当前栈的空间,需要进行扩容时,原来栈里面的数据要向后移动,影响效率),top指向有效数据的下一个位置(你也可以让top指向有效数据的最后一个位置,看各自的习惯),规定只能在栈顶执行插入与删除,这样就实现了一个栈。
假设以数组的头为栈顶:
②.单链表:
对于使用链表来实现栈,我们一般都是将链表的尾作为栈底。这样,在执行出栈操作时会比较方便(入栈就头插,出栈就头删)。
假设以链表的头为栈底:
我们会发现,当要执行出栈操作时,要删除栈顶结点,就必须要有栈顶结点的前一个结点的指针,这就需要去找它,这样效率就低了。
2.使用数组实现栈:
①.结构体设计:
静态栈:
typedef int StackDataType;
#define N 10
typedef struct Stack
{
StackDataType _a[N];
int size;
}Stack;
动态栈:
typedef int StackDataType;
typedef struct Stack
{
StackDataType* _a;//带_,以便区分成员变量与普通变量
int _top;//栈顶下标
int _capacity;
}Stack;
②.初始化:
void StackInit(Stack* pst)//传地址,不然外面不会发生变化
{
assert(pst);
pst->_a = (StackDataType*)malloc(sizeof(StackDataType) * 4);//最开始为栈分配4个单位空间
pst->_top = 0;
pst->_capacity = 4;
}
③.销毁:
void StackDestory(Stack* pst)
{
assert(pst);
free(pst->_a);
pst->_a = NULL;
pst->_top = 0;
pst->_capacity = 0;
}
④.入栈:
void StackPush(Stack* pst, StackDataType x)
{
assert(pst);
if (pst->_top == pst->_capacity)//空间不够,增容
{
pst->_capacity *= 2;
StackDataType* tmp = (StackDataType*)realloc(pst->_a, sizeof(StackDataType) * pst->_capacity);
if (tmp == NULL)//在堆上开辟空间失败
{
printf("内存不足\n");
exit(-1);
}
else
{
pst->_a = tmp;
}
}
pst->_a[pst->_top] = x;//入栈
pst->_top++;//栈顶指针+1
}
⑤.出栈:
void StackPop(Stack* pst)
{
assert(pst);
assert(pst->_top > 0);//当栈内没有有效数据时
--pst->_top;//栈顶指针-1
}
⑥.获取数据个数:
int StackSize(Stack* pst)
{
assert(pst);
return pst->_top;
}
⑦.判空:
bool StackEmpty(Stack* pst)
{
assert(pst);
return pst->_top == 0 ? 1 : 0;
//或者 return !pst->_top;
}
⑧.获取栈顶数据:
StackDataType StackTop(Stack* pst)
{
assert(pst);
assert(pst->_top > 0);
return pst->_a[pst->_top - 1];
}