数据结构之栈(C语言)
- 栈
- 1 栈的概念与结构
- 2 栈的初始化和销毁
- 2.1 栈的初始化
- 2.2 栈的销毁
- 3 入栈函数与出栈函数
- 3.1 入栈函数
- 3.2 出栈函数
- 4 取栈顶数据,获取数据个数 和 判空函数
- 4.1 取栈顶数据与获取数据个数
- 4.1.1 取栈顶数据
- 4.1.2 获取数据个数
- 4.2 判空函数
- 5 真题实战(有效的括号)
栈
1 栈的概念与结构
栈是一种特殊的线性表,特殊在于其规定只允许在栈固定的一端进行数据插入和删除操作。数据插入和删除数据元素的一端叫做栈顶,另一端叫做栈底。栈中的元素遵从先进后出的原则。(我们可以把栈看成枪的弹夹,先压入的子弹后击发,后压入的子弹先击发)
压栈:栈的插入操作叫做压栈/进栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈,出数据也在栈顶。
本章使用数组来实现栈。
代码定义如下:
typedef int STDatatype;//对栈中存入的元素类型进行重命名,避免修改时误操作typedef struct Stack
{STDatatype* c;//所存入栈中的数据int top;//表示指向栈顶元素位置or栈顶数据的下一个位置int capacity;//表示栈的容量
}ST;
在上面代码中我们对在的每个节点中定义了三个变量,对于top这一项较为特殊,下文2.1中会对两种情况进行阐述。
2 栈的初始化和销毁
2.1 栈的初始化
我们应知在创建出一个新的栈后,这个新的栈应是一个空栈,如此也就意味着指向存储数据的指针STDatatype* c = NULL
,表示容量的int capacity = 0
。但对于top
我们不能轻率的认为top == 0表示空栈时top所指向的是栈顶数据,如果这么认为的话会对后面的操作造成误导(比如对栈判空)。因为top既然在等于0时表示栈为空栈,而且top还指向着栈顶数据,那么此时在索引为0处(即top == 0处 )就应当存放有具体数据,然而此时栈却是空的,所以当top == 0表示空栈时top就不能指向着栈顶数据,而是指向栈顶数据的下一个位置。 反之同理,若想让top指向栈顶数据,则top == -1时才可表示空栈。(鱼和熊掌不可兼得,表示空栈为鱼,指向栈顶数据为熊掌)
如图所示:
//栈的初始化
void STInit(ST* pst)
{assert(pst);pst->c = NULL;//top指向栈顶数据的下一个位置pst->top = 0;//top指向栈顶数据//pst->top = -1;pst->capacity = 0;
}
我们之所以选择top指向栈顶元素的下一个位置这种定义,一是因为在诸多程序中变量为零都是判空的条件,如此设置有助于代码的一致性与可读性。二是因为top
如此一来就可以等同于capacity
的作用,直接反映栈中元素数量。三是因为简化了栈的判空并且减少了对栈边界的检查,top == 0
时栈为空;最大容量为n时,top == n
表示栈满。无需额外检查。
2.2 栈的销毁
由于本文中的栈是使用数组完成实现,故栈的销毁与顺序表的销毁一致。首先使用assert
对数组断言(确定传参有效),然后释放掉动态内存,最后将to
p与capacity
均置为0。
代码如下:
void STDestroy(ST* pst)
{assert(pst);free(pst->c);//释放动态内存pst->c = NULL;//首地址置为空pst->top = pst->capacity = 0;
}
3 入栈函数与出栈函数
3.1 入栈函数
经过初始化函数对栈进行操作后,数组为空,容量为零。所以在将数据压入栈中之前,我们要先对数组的容量进行检测,检查容量是否已满。如果满了,我们就进行扩容操作;反之,我们就直接插入即可。
对于内存函数的选择方面,由于扩容时会存在原本栈容量已经满的情况,此时不仅要扩大内存块,还要保留栈中原本的数据,所以应当选择realloc
。
void STPush(ST* pst, STDatatype x)
{assert(pst);//扩容if (pst->c == pst->capacity){int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;STDatatype* tmp = (STDatatype*)realloc(pst->c, newcapacity * sizeof(STDatatype));if (tmp == NULL){perror("realloc fail !");return;}pst->c = tmp;pst->capacity = newcapacity;}//扩容完毕后插入数据pst->c[pst->top] = x;//将数据插入到栈顶//注意这里要做出区分//此时top == 0 这个位置就是栈顶//但是top的指向是栈顶数据的下一个位置pst->top++;
}
3.2 出栈函数
出栈的操作只要在逻辑结构上将出栈数据移除即可,也就是通过索引top的加减完成出栈操作。此时有人可能会有疑问,既然其在物理结构上还存在于数组的内存块中,那么不会对栈满的判断造成影响吗?
因为我们在对top定义时,将其定义为指向栈顶数据的下一个位置,如此top==0
就表示了空栈,top
就表示栈中有几个数据,所以不会对栈满的判断造成影响。此外,当我们进行一次出栈后,出栈的数据虽然仍存在于数组的物理结构中,但是在下一次进行压栈操作后,刚才出栈数据的位置就会分配给新压入栈的数据,出栈的数据被新压入栈的数据覆盖掉。
void STPop(ST* pst)
{assert(pst);assert(pst->top > 0);//top表示栈中数据个数//当top==0时,栈已经成空栈,不能再进行出栈操作pst->top--;
}
4 取栈顶数据,获取数据个数 和 判空函数
4.1 取栈顶数据与获取数据个数
4.1.1 取栈顶数据
因为top是指向栈顶数据的下一个位置,所以要想获得栈顶数据,索引应当等于top - 1。
代码如下:
STDatatype STTop(ST* pst)
{assert(pst);//确保传参有效assert(pst->top > 0);//确保栈不为空return pst->c[pst->top - 1];//top - 1为栈顶数据
}
4.1.2 获取数据个数
top
就代表着栈中的数据个数,所以直接返回top
即可。
代码如下:
int STsize(ST* pst)
{assert(pst);return pst->top;
}
4.2 判空函数
根据我们前面的定义,top == 0
时为空栈。所以判空过程中如果top等于零就是真(true),top不等于0就是假(false)。故我们将返回值类型设置为布尔类型,便于直观表现判空结果。
代码如下:
bool STEmpty(ST* pst)
{assert(pst);return pst->top == 0;
}
5 真题实战(有效的括号)
输出样例:
这个题当中我们之所以选择用栈来解答就是因为栈独特的后进先出的特性,根据题目要求我们可以得出每个右括号都要与最近的未闭合的左括号匹配,那么每当识别到左括号就将其压入栈中,当识别到右括号时就将刚压入栈的左括号出栈顶与之匹配,如果不匹配也就意味着字符串非有效。
按照上述思路我们来完成实现(前提是上文中栈的基本结构已经编写完毕):
初阶版:
//因输出结果为true与false,故返回值类型定位布尔类型
bool isValid(char* s) {ST st;STInit(&st);while(*s){//识别括号,是左括号入栈if(*s == '(' || *s == '[' || *s == '{'){STPush(&st , *s);}//不是左括号是右括号,刚进栈的左括号出栈顶与右括号匹配else{//取栈顶元素char top = STTop(&st);//出栈顶STPop(&st);//匹不匹配只需看不匹配情况即可,如果匹配就继续执行循环,不匹配直接结束函数if(top == '(' && *s != ')'|| top == '[' && *s != ']'|| top == '{' && *s != '}'){STDestroy(&st);//即使不匹配也要及时销毁栈,避免内存泄漏return false;}}//每完成一次压栈或出栈操作字符串数组索引向后移一位++s;}return true;
}
提交后发现给出的四个样例均可通过,但当字符串中只有一个"["
时,运行结果错误。
这是因为左括号数量比右括号多,当最后一个左括号被压入栈中之后,没有右括号与其匹配,左括号依然存在于栈中。而程序运行完并没有对栈中进行判空,所以结果出错。
修改后:
//因输出结果为true与false,故返回值类型定位布尔类型
bool isValid(char* s) {ST st;STInit(&st);while(*s){//识别括号,是左括号入栈if(*s == '(' || *s == '[' || *s == '{'){STPush(&st , *s);}//不是左括号是右括号,刚进栈的左括号出栈顶与右括号匹配else{//取栈顶元素char top = STTop(&st);//出栈顶STPop(&st);//匹不匹配只需看不匹配情况即可,如果匹配就继续执行循环,不匹配直接结束函数if(top == '(' && *s != ')'|| top == '[' && *s != ']'|| top == '{' && *s != '}'){STDestroy(&st);return false;}}//每完成一次压栈或出栈操作字符串数组索引向后移一位++s;}//匹配完如果栈不为空,说明左括号比右括号多,数量不匹配bool ret = STEmpty(&st);STDestroy(&st);return ret;
}
再次提交发现仍然存在报错,当字符串中只有一个"]"
时,运行结果错误。
由于没有左括号压入栈中,所以在取栈顶元素时就触发了assert
断言的报错。
故在取栈顶元素之前,也应对栈进行判空操作。
最终修改后:
//因输出结果为true与false,故返回值类型定位布尔类型
bool isValid(char* s) {ST st;STInit(&st);while(*s){//识别括号,是左括号入栈if(*s == '(' || *s == '[' || *s == '{'){STPush(&st , *s);}//不是左括号是右括号,刚进栈的左括号出栈顶与右括号匹配else{//如果栈为空,字符串只有一个右括号if( STEmpty(&st) ){STDestroy(&st);return false;}//如果栈不为空,进行匹配//取栈顶元素char top = STTop(&st);//出栈顶STPop(&st);//匹不匹配只需看不匹配情况即可,如果匹配就继续执行循环,不匹配直接结束函数if(top == '(' && *s != ')'|| top == '[' && *s != ']'|| top == '{' && *s != '}'){STDestroy(&st);return false;}}//每完成一次压栈或出栈操作字符串数组索引向后移一位++s;}//匹配完如果栈不为空,说明左括号比右括号多,数量不匹配bool ret = STEmpty(&st);STDestroy(&st);return ret;
}
提交通过,题目解答完毕。
全文至此结束!!!
写作不易,不知各位老板能否给个一键三连或是一个免费的赞呢(▽)(▽),这将是对我最大的肯定与支持!!!谢谢!!!(▽)(▽)