前情提要:栈——线性结构的应用之一 | Akari的小站
堆栈:
堆栈(Stack):具有一定操作约束的线性表
- 只在一端(栈顶,Top)做插入、删除
- 插入数据:入栈(Push)
- 删除数据:出栈(Pop)
- 后入先出:Last In First Out(LIFO)
一、栈的链式存储实现
栈的链式存储结构实际上就是一个单链表,叫做链栈。插入和删除操作只能在链栈的栈顶进行。
#include <stdio.h> #include <stdlib.h> #include <stdbool.h>
typedef struct SNode{ int data; struct SNode * pNext; }SNODE, * PSNODE;
typedef struct Stack{ PSNODE pTop; PSNODE pBottom; }STACK, * PSTACK;
void Init(PSTACK); void Push(PSTACK, int); void Traverse(PSTACK); bool Pop(PSTACK, int *); bool IsEmpty(PSTACK) ; void Clear(PSTACK);
int main(void) { STACK S; Init(&S); Push(&S, 1); Push(&S, 2); Push(&S, 3); Push(&S, 4); Push(&S, 5); Traverse(&S); int val; if ( Pop(&S, &val) ){ printf("出栈成功!出栈的元素是:%d\n", val); } else{ printf("出栈失败!\n"); } Traverse(&S); Clear(&S); Traverse(&S); return 0; }
void Init(PSTACK pS){ pS->pTop = (PSNODE)malloc(sizeof(SNODE)); if ( NULL == pS->pTop ){ printf("动态内存分配失败!\n"); exit(-1); } pS->pBottom = pS->pTop; pS->pTop->pNext = NULL; }
void Push(PSTACK pS, int val){ PSNODE pNew = (PSNODE)malloc(sizeof(SNODE)); pNew->data = val; pNew->pNext = pS->pTop; pS->pTop = pNew; }
void Traverse(PSTACK pS){ PSNODE p = pS->pTop; while ( p != pS->pBottom ){ printf("%d ", p->data); p = p->pNext; } printf("\n"); }
bool IsEmpty(PSTACK pS){ if ( pS->pTop == pS->pBottom ) return true; else return false; }
bool Pop(PSTACK pS, int * pVal){ if ( IsEmpty(pS) ){ printf("栈为空!\n"); return false; } PSNODE r = pS->pTop; *pVal = r->data; pS->pTop = r->pNext; free(r); r = NULL; return true; }
void Clear(PSTACK pS){ if ( IsEmpty(pS) ){ return; } PSNODE r = pS->pTop; PSNODE q = NULL; while ( r != pS->pBottom ){ q = r->pNext; free(r); r = q; } pS->pTop = pS->pBottom; }
|
二、栈的顺序存储实现
栈的顺序存储结构通常由一个一维数组和一个记录栈顶元素位置的变量组。
#include <stdio.h> #include <stdbool.h> #define MaxSize 20
typedef struct SNode{ int data[MaxSize]; int Top; }STACK;
void Init(STACK *); bool Push(STACK *, int); void Traverse(STACK *); bool Pop(STACK *, int *); bool IsEmpty(STACK *) ; bool IsFull(STACK *); void Clear(STACK *);
int main(void) { STACK s; Init(&s); Push(&s, 1); Push(&s, 2); Push(&s, 3); Push(&s, 4); Push(&s, 5); Traverse(&s); int val; if ( Pop(&s, &val) ){ printf("出栈成功!出栈的元素是:%d\n", val); } else{ printf("出栈失败!\n"); } Traverse(&s); Clear(&s); Traverse(&s); return 0; }
void Init(STACK *s){ s->Top = -1; return; }
bool Push(STACK *s, int val){ if ( IsFull(s) ){ printf("栈已满!\n"); return false; } else{ s->Top++; s->data[s->Top] = val; return true; } }
void Traverse(STACK *s){ if ( IsEmpty(s) ){ printf("栈为空!\n"); return; } for ( int i = 0; i <= s->Top; i++ ){ printf("%d ", s->data[i]); } printf("\n"); return; }
bool Pop(STACK *s, int *pVal){ if ( IsEmpty(s) ){ printf("栈为空!\n"); return false; } else{ *pVal = s->data[s->Top]; s->Top--; return true; } }
bool IsEmpty(STACK *s){ if ( -1 == s->Top ){ return true; } else return false; }
bool IsFull(STACK *s){ if ( MaxSize - 1 == s->Top ){ return true; } else return false; }
void Clear(STACK *s){ s->Top = -1; return; }
|
1、函数的形参STACK *s
- 每个函数中形参都是STACK *s,有必要都用指针吗?因为这是以数组实现的栈
函数参数使用 STACK *s,即指向 STACK 结构体的指针。这是因为在 C 语言中,函数参数传递通常是通过值传递的——如果传递一个结构体,那么会复制整个结构体的内容,这可能引起性能上的开销。
虽然静态栈是基于数组实现的,而数组本身已经是一个指针,但传递 STACK *s 还是有好处的,因为可能需要在函数中修改 Top 指针,而不是只修改数组的内容。
2、指针的优劣
- 内存效率:
通过传递指针,避免了在函数调用时复制整个结构体的开销,尤其是当结构体很大时。
- 原地修改:
通过指针,可以在函数内部修改原始结构体,而不是修改传递给函数的副本。这对于栈这种需要修改状态的数据结构来说是很重要的。
- 一致性:
当使用指针传递结构体时,对结构体的修改会直接反映到调用者的结构体上,这有助于保持一致性。
- 复杂性: 指针的使用可能增加代码的复杂性,需要更小心地处理指针的引用、解引用等操作,以防止出现错误。
- 潜在的错误: 不正确使用指针可能导致内存错误,如空指针引用、越界等问题。