栈结构的搭建
栈的定义
- 一种可以实现“先进后出”的存储结构
- 类似于一个箱子
- 分为静态栈和动态栈两种,区别在于其内核不一样,前者内核是数组,后者内核是链表,造成的结果就是前者的内存是固定的,后者的内存可以不断变化
图示
diy 过程
- 创建栈结构:
- 栈的核心就在于两个指针的使用,pTop 以及 pBottom,所有的算法都基于这两个指针
- 这两个指针实则就是两个链表节点的指针
\\ 定义链表节点结构体:
typedef struct Node{
int data;
struct Node * pNext;
}NODE, * PNODE;
\\ 定义栈结构体:
typedef struct stack{
PNODE pTop;
PNODE pBottom;
}STACK, * PSTACK;
- 栈的初始化:
-
结合图示结构来看:
-
实则就是为 pTop 和 pBottom 分配动态内存,使他们都指向“头结点” pHead ,注意 pHead 不存放有效数据,其作用是为了方便操作
-
void init(PSTACK pS);
int main(void)
{
PSTACK S;//定义一个栈结构指针
init(S);
return 0;
}
void init(PSTACK pS)
{
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if ( pHead == NULL )
{
printf("动态内存申请失败!程序终止!\n");
exit(-1);
}
else
{
pHead->pNext = NULL;
//初始化 pHead 后没有其他栈节点,即此时还没有有效值
pS->pTop = pHead;
pS->pBottom = pHead;
}
return;
}
- 压栈
- 压栈:将数据填入栈结构的操作,其特点是将新的节点放在头结点的后面
- 结合图示来看,其过程就是:创建一个新的节点,向其中填入有效值,将新节点的指针域指向pTop,再将 pTop 指向新的节点
void push(PSTACK pS, int val);
int main(void)
{
PSTACK S;//定义一个栈结构指针
init(S);//初始化栈
push(S,1);//压栈
return 0;
}
void push(PSTACK pS, int val)
{
//创建新节点
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if ( pNew == NULL )
{
printf("动态内存申请失败!程序终止!\n");
exit(-1);
}
//将新节点的指针域指向 pTop
pNew->pNext = pS->pTop;
//向新节点填入数据
pNew->data = val;
//将 pTop 指向新节点,相当于”箱子顶部“
pS->pTop = pNew;
return;
}
- 遍历栈
- 从”箱子顶部“按顺序取值,直到”箱子底部“
- 按照图示,其算法其实就是:创建一个指针p,将 pTop 的值赋给它,随后让它不断往pNext走,相当于它从箱子顶部出发,只要 (p != pBottom),就说明指针 p 还没有到达箱子底部,否则遍历结束
bool empty(PSTACK pS); //由于是遍历过程,所以需要判断是否是空栈
void traverse(PSTACK pS);
int main(void)
{
PSTACK S;//定义一个栈结构指针
init(S);//初始化栈
push(S,1);//压栈
traverse(S);//遍历
return 0;
}
bool empty(PSTACK pS)
{
//空栈的判断比较简单,直接看”箱子顶部“和”箱子底部“的值是不是相等
if ( pS->pTop == pS->pBottom )
{
return true;
}
else
{
return false;
}
}
void traverse(PSTACK pS)
{
if ( empty(pS) )
{
printf("空栈!!\n");
}
else
{
PNODE p = pS->pTop;
//创建一个指针
//为什么不直接用 pTop ?
//答:如果用 pTop,那么当 pTop 往 pNext 走的时候,栈的结构就被破坏了,再也找不到原来 pTop 的指针了
while ( p != pS->pBottom )
{
printf("%d ", p->data);
p = p->pNext;
}
printf("\n");
}
return;
}
- 出栈
- 出栈:将数据从栈中取出,相当于从箱子顶部取东西,然后箱子顶部这个位置要往下移
- 在图示中,它是与 push 相反的箭头(懒的另外画了 : )
bool pop(PSTACK pS, int *pVal);
//pop是取值,所以需要返回bool值,空栈可没法取值
int main(void)
{
PSTACK S;//定义一个栈结构指针
init(S);//初始化栈
push(S,1);//压栈
traverse(S);//遍历
if ( pop(S,&val) )
{
printf("出栈成功!出栈元素为 %d \n", val);
}
else
{
printf("出栈失败!\n");
}
return 0;
}
bool pop(PSTACK pS, int *pVal)
{
if ( empty(pS) )
{
return false;
}
else
{
//创建一个指针,与遍历类似
PNODE r = pS->pTop;
//将取出的值返回给用户
*pVal = r->data;
//将 pTop 下移,与遍历不同,此时pTop的位置要改变了,原来的箱子顶可不是现在的箱子顶了,箱子变矮了:(
pS->pTop = r->pNext;
//释放原来的箱子顶
free(r);
//虽然释放了,但是 r 里还有值,所以需要赋个 NULL
r = NULL;
return true;
}
}
- 清栈
- 顾名思义,清空这个栈,使得栈变为和初始化后的一样,重点在于需要释放原来栈里面的空间,不然遇到个大点的栈,内存可经不起折腾
- 算法就是遍历和出栈的结合
void clear(PSTACK pS);
int main(void)
{
PSTACK S;//定义一个栈结构指针
init(S);//初始化栈
push(S,1);//压栈
traverse(S);//遍历
if ( pop(S,&val) )
{
printf("出栈成功!出栈元素为 %d \n", val);
}
else
{
printf("出栈失败!\n");
}
clear(S);//清栈
return 0;
}
void clear(PSTACK pS)
{
if ( empty(pS) )
//先判断是不是个空栈,空栈就不用清啦,加快效率
{
return;
}
else
{
PNODE p = pS->pTop;
//不定义 p 也行,只是为了方便理解
//将这个算法理解成遍历和取值的结合就好,没取一个释放掉之后,将箱子顶往下移动一个单位
PNODE q;
while ( p != pS->pBottom )
{
q = p->pNext;
free(p);
p = q;
}
pS->pTop = pS->pBottom;
return;
}
}
总结
- 栈结构总体来说还是比较简单的,箱子的比喻其实还不是很恰当,当成一个积木塔更恰当,一块一块积木(栈节点)搭成一个塔
- 其实内核就是链表啦,比链表简单