目录 |
1.堆栈基本概念堆栈是一种逻辑线性结构。其数据的操作是在一端进行的;其特点是:先进后出;堆栈具有“记忆性”,被广泛使用在操作系统软件、各种应用软件中,是一种非常常用且重要的数据结构。对于递归程序的非递归化,通常也是通过堆栈完成的。
如图所示: malloc是在堆里面实现的 为了使堆栈的操作更加完整,更加符合系统堆栈的基本原则,不使用链的形式实现,依然选择数组来实现。
从上图可知,堆栈是由堆栈区域(存储区)和两个指针(栈顶指针、栈底指针)组成;如果用物理线性存储结构实现,则,栈顶和栈底指针实质上是数组下标。
对堆栈的数据操作只能在栈顶一端执行,且有两个操作:入栈、出栈。 堆栈的特点是先进后出,可见下图: 入栈过程: 出栈过程: 明显的,B必须先出栈,A才能出栈;这就是先入后出! |
2.堆栈的实现(线性存储方式)
|
3.堆栈的基本操作(1)初始化堆栈考虑的问题:有没有参数、有几个参数、有没有返回值、返回值类型是什么 initStack()
问题1:形参STACk类型用*还是** 我们在定义了堆栈的数据结构之后,在主函数中引用堆栈时有两种方式:
问题2:有无返回值
问题3:initStack(stack1) / initStack(&stack1)
问题4:形参个数一个够吗? 老师说不够,要再加一个个数 initStack(&stack1, 30) 此处,在课本和各种实验指导书上是用宏定义一个最大个数 自己理解:用宏定义的话申请空间申请的可能太多,所以自己加参数,按照自己的需要来,可以尽量节省空间。
问题5:每次申请都会成功吗? 不一定 如果是这样的话返回值就用boolean类型 失败的情况:initStack(&stack1, -30) 理解:系统内部是会-30理解成一个很大的数,会按照那个很大的数申请一个很大的空间,故要对此进行控制 所以:boolean initStack(STACK **head, int maxRoom);
此处一个小知识点: boolean initStack(STACK **head, int maxRoom); 主函数中:initStack(...); 不用 ok = initStack(...);
|
(2)销毁堆栈
|
(3)判栈空/满
|
(4)入栈
|
(5)出栈
说明为什么此处用UESR_TYPE *类型
|
(6)读栈顶元素的值
问题:考虑用什么返回值类型
USER_TYPE readTop(STACK head){ if(栈非空){
|
/*
堆栈的基本操作:
1、初始化(构造)堆栈;
2、销毁(析构)堆栈;
3、判栈空;
4、判栈满;
5、入栈;
6、出栈;
7、读栈顶元素。
*/
#include<stdio.h>
#include<malloc.h>
typedef unsigned char boolean;
#define TRUE 1
#define FALSE 0
//typedef unsigned int size_t;
//定义USER_TYPE
typedef struct POINT{
int row;
int col;
}POINT, USER_TYPE;
//定义数据结构
typedef struct STACK{
USER_TYPE *stack;
int maxRoom;
int top;
}STACK;
boolean initStack(STACK **head, int maxRoom);
void destroyStack(STACK **head);
boolean isStackEmpty(STACK head);
boolean isStackFull(STACK head);
boolean push(STACK *head, USER_TYPE value);
boolean pop(STACK *head, USER_TYPE *valuePoint);
//USER_TYPE readTop(STACK head);
boolean readTop(STACK head, USER_TYPE *valuePoint);
void showStackElement(STACK head);
void showStackElement(STACK head){
int i;
if(isStackEmpty(head)){
return;
}
for(i = 0; i < head.top; i++){
printf("(%d,%d) ", head.stack[i].row, head.stack[i].col);
}
printf("\n");
}
boolean readTop(STACK head, USER_TYPE *valuePoint){
//此处不用做像前面的那样考虑是否为NULL,
//因为前面考虑是因为参数传入的是指向控制头的指针,我们判断其是否为空来判断是否初始化
//此处直接传的就是控制头,所以必定已经初始化了
if(isStackEmpty(head)){
return FALSE;
}
*valuePoint = head.stack[head.top - 1];
return TRUE;
//*valuePoint = head.stack[--head.top];
//这样写也可以,因为形参里面传的是控制头的复制本,并不会改变原来控制头的值(机智如你)
/*
USER_TYPE readTop(STACK head){
if(栈非空){
return head.stack[top-1];
}
if(栈为空){
//一般思维是返回一个非正常值 但是该值无法确定用什么合适,故不用此种类型
}
}
*/
}
boolean pop(STACK *head, USER_TYPE *valuePoint){ //我们是想通过这个操作取一个值
//关于boolean类型如何理解
//pop操作可能失败
if(head == NULL || isStackEmpty(*head)){ //把下面那两个合并了
return FALSE;
}
/*
if(head == NULL){ //说明没有初始化
return FALSE;
}
if(isStackEmpty(*head)){ //说明堆栈是空的,无法进行pop操作
return FALSE;
}
*/
*valuePoint = head->stack[--head->top];
return TRUE;
}
boolean push(STACK *head, USER_TYPE value){
//关于此处是用STACK head STACK *head STACK **head 如何考虑
//最根本的是要考虑需要修改哪一块的值,然后主函数传递该块的指针,子函数形参的指类就是该块
//此处首先要修改stack指针所指向的空间(因为要push数据),其次要修改top的值,所以主函数应该传递STACK类型的地址,而子函数应该是STACK *
if(head == NULL || isStackFull(*head)){ //把下面那两个合并了
return FALSE;
}
/*
if(head == NULL){ //说明没有初始化
return FALSE;
}
if(isStackFull(*head)){ //说明堆栈已经满了,无法进行push操作
return FALSE;
}
*/
head->stack[head->top++] = value;
return TRUE;
}
boolean isStackFull(STACK head){
return (head.top == head.maxRoom); //return (head.top >= head.maxRoom);
}
boolean isStackEmpty(STACK head){
return head.top == 0;
//return head.top <= 0; 怕写错了,可以写成这种形式,因为不一定每次都可以写成两个等于
//return !head.top; 这种形式应该也可以
/*
if(head.top == 0){
return TRUE;
}
return FALSE;
*/
}
void destroyStack(STACK **head){
if(*head == NULL || (*head)->stack == NULL){ //把下面那两个合并了
return;
}
//此处涉及短路运算
//若前面那个成立(即*head == 0),则不再进行后面的运算(如果还要操作的话,就会造成非法内存访问)
/*
if(*head == NULL){ //说明stack1哪都没有指向(说明没有初始化),那就不用释放了
return;
}
if((*head)->stack == NULL){ //说明控制头中的stack成员为空,没有指向,也就不用释放了
return;
}
*/
free((*head)->stack);
free(*head);
*head = NULL; //保证这个指针不再乱指
}
boolean initStack(STACK **head, int maxRoom){
if(maxRoom <= 0 || *head != NULL){ //把下面那两个合并了
return FALSE;
}
/*
if(*head != NULL){
return FALSE;
}
if(maxRoom <= 0){
return FALSE;
}
*/
(*head) = (STACK *)malloc(sizeof(STACK)); //申请控制头
(*head)->stack = (USER_TYPE *)malloc(sizeof(USER_TYPE) * maxRoom);
(*head)->maxRoom = maxRoom;
(*head)->top = 0;
return TRUE;
}
void main(void){
//STACK stack1; //这种方式不符合以后各种语言的常态,Java所有类的对象都是指针。
STACK *stack1 = NULL; //别让它乱指
//这种方式的优势:想保护整个堆栈的方方面面,让思路和面向对象的思路保持一致,和C++很一致
USER_TYPE point1 = {1, 1}; //push进去的值
USER_TYPE point2 = {2, 2}; //push进去的值
USER_TYPE point3; //读栈顶的值
USER_TYPE point4; //pop出来的值
initStack(&stack1, 30);
printf("初始化后堆栈里面的元素有:\n");
showStackElement(*stack1);
push(stack1, point1);
printf("push操作之后堆栈里面的元素有:");
showStackElement(*stack1);
push(stack1, point2);
printf("push操作之后堆栈里面的元素有:");
showStackElement(*stack1);
readTop(*stack1, &point3);
printf("栈顶的元素为:(%d,%d)\n", point3.row, point3.col);
pop(stack1, &point4);
printf("pop操作之后堆栈里面的元素有:");
showStackElement(*stack1);
destroyStack(&stack1); //建议用这种类型
}
//sizeof运算符返回的是一个无符号整型 系统定义了一个unsigned int 型的类型,叫size_t