1.栈的结构
栈是一种特殊的线性表,只能允许在固定的一端进行插入删除操作,在操作过程中,进行数据插入删除的一端就是栈顶,另一端即为栈底。栈中的数据遵循后进先出,即LIFO。
下面进行对栈的插入元素和删除元素的简单演示。
上图形象地展示出栈LIFO的特性。下文使用数组实现栈,相比之下单链表实现栈由于单链表操作的容易程度只适合头插头删,这样使得栈顶只能是链表表头。双向链表自然可以实现,但是操作不易,这里不考虑。介绍一下栈的代码结构。
typedef struct Stack{
int top;
int size;
STDataType *a;
}Stack;
其中三个结构体成员是栈栈顶元素,栈的当前数据个数,栈的首元素地址。
void STInit(ST* pst){
pst->size = 0;
pst->top= 0;
pst->a = NULL;
}
上述函数是栈的初始化,不多赘述。
void STDestroy(ST* pst){
assert(pst);
if(pst->a != NULL){
free(pst->a);
pst->a = NULL;
}
size = top = 0;
}
上述函数是对栈的销毁操作,如果不及时对栈进行销毁的话,可能会导致内存泄漏,内存泄漏是很严重的问题并且编译器不会对其“特殊照顾”报错,需要自行进行销毁。
2.栈的插入删除
对于一个经典的数据结构,插入和删除是必不可少的,对于实现LIFO功能的插入删除其实很简单。
2.1 插入
经典的问题就是插入需要判满,判满的操作已经出现过数次。其次就是这个栈初始化设置时的魔鬼细节。下面我用一幅图演示。在本文中Init函数给top初始赋值为0,如果是0的话,代码如下图相同,先赋值,再加一,完成一次赋值top指向的是当前元素的下一个位置;如果top赋值为-1,先加一,再赋值,如下图注释所示,此时top指向当前元素。
void STPush(ST* pst, STDataType x){
assert(pst);
if(pst->size == ps->top){
int newcapacity = (pst->capacity == 0) ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);
if (tmp == NULL) {
perror("realloc fail!");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
// pst->a[++pst->top] = x;
}
2.1 删除
对于栈的删除操作,首要判断就是栈是否空,以下为删除代码,只需将栈顶指针向下移位即可。
void STPop(ST* pst) {
assert(pst);
assert(pst->top > 0);
pst->top--;
}
3.栈的其余操作
1.判断栈空
bool STEmpty(ST* pst){
assert(pst);
return pst->top == 0;
}
2.取栈顶元素
取栈顶元素的操作在栈的使用中比较常见,由于top指向的元素是当前元素的下一个位置,故返回的元素即栈顶是top-1位置上的元素。
STDataType STTop(ST* pst) {
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
3.计算栈的大小
int STSize(ST* pst) {
assert(pst);
return pst->top;
}