(第四章)堆栈

目录

1.堆栈基本概念

2.堆栈的实现(线性存储方式)

3.堆栈的基本操作

(1)初始化堆栈

(2)销毁堆栈

(3)判栈空/满

(4)入栈

(5)出栈

(6)读栈顶元素的值


1.堆栈基本概念

堆栈是一种逻辑线性结构。其数据的操作是在一端进行的;其特点是:先进后出;堆栈具有“记忆性”,被广泛使用在操作系统软件、各种应用软件中,是一种非常常用且重要的数据结构。对于递归程序的非递归化,通常也是通过堆栈完成的。

 

如图所示:

                                            

malloc是在堆里面实现的

为了使堆栈的操作更加完整,更加符合系统堆栈的基本原则,不使用链的形式实现,依然选择数组来实现。

 

从上图可知,堆栈是由堆栈区域(存储区)和两个指针(栈顶指针、栈底指针)组成;如果用物理线性存储结构实现,则,栈顶和栈底指针实质上是数组下标。

 

对堆栈的数据操作只能在栈顶一端执行,且有两个操作:入栈、出栈。

堆栈的特点是先进后出,可见下图:

入栈过程:

                        

出栈过程:

明显的,B必须先出栈,A才能出栈;这就是先入后出!

2.堆栈的实现(线性存储方式)

typedef struct STACK{
    USER_TYPE *stack;
    int maxRoom;
    int top;
}STACK;

    

 

3.堆栈的基本操作

(1)初始化堆栈

考虑的问题:有没有参数、有几个参数、有没有返回值、返回值类型是什么

initStack()

 

问题1:形参STACk类型用*还是**

我们在定义了堆栈的数据结构之后,在主函数中引用堆栈时有两种方式:

 

initStack(STACK *head);

 

void main(void){
    STACK stack1;                //这种方式不符合以后各种语言的常态,Java所有类的对象都是指针。
 }

 

initStack(STACK **head);

void main(void){
     STACK *stack1 = NULL;        

    //别让它乱指,指向内存最开始的地方
    //这种方式的优势:想保护整个堆栈的方方面面,让思路和面向对象的思路保持一致,和C++很一致(以后都用这个

}

问题2:有无返回值

 

STACK *initStack(STACK **head);

 

void main(void){
     STACK *stack1 = NULL;        
                                 

     stack1 = initStack(&stack1);  //这种方式会造成内存泄露,stack1先指向一个指针,后面初始化又指向另外一个指针
     stack1 = initStack(&stack1); //则第一块无法被操作,会造成内存泄露
}

 

void initStack(STACK **head);   //以后用这种方式

 

void main(void){
     STACK *stack1 = NULL;        
                                 

     initStack(&stack1);
     initStack(&stack1);
}

问题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(...);

 

 

 

//经过各种考虑之后的最终代码

boolean initStack(STACK **head, int maxRoom){
    if(maxRoom <= 0 || *head != NULL){ //把下面那两个合并了
        return FALSE;
    }

/*
    if(*head != NULL){    //说明stack1这个指针已经指向一个堆栈了
        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 = NULL;        


    initStack(&stack1, 30);
}

 

 

(2)销毁堆栈

 

destroyStack(STACK *head);

 

void main(void){
    STACK *stack1 = NULL;       


    initStack(&stack1, 30);
    destroyStack(stack1);  //这样做可以释放stack1所指向的空间,但是stack1的值是不会变化的
}

 

destroyStack(STACK **head);

 

void main(void){
    STACK *stack1 = NULL;       


    initStack(&stack1, 30);
    destroyStack(&stack1);  //建议用这种形式
}

//经过各种考虑之后的最终代码

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;      //保证这个指针不再乱指
}

 

void main(void){
    STACK *stack1 = NULL;        


    initStack(&stack1, 30);

    //...各种操作

    destroyStack(&stack1); //建议用这种类型
}

 

 

 

 

(3)判栈空/满

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;
*/
}

 

(4)入栈

 

 

 

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;
}

 

 

 

(5)出栈

 

 

//正确形式

boolean pop(STACK *head, UESR_TYPE *valuePoint){  //我们是想通过这个操作取一个值

    if(head == NULL || isStackEmpty(*head)){  //把下面那两个合并了
        return FALSE;
    } 


    *valuePoint = head->stack[--head->top];

}

 

void main{

   STACK *stack1 = NULL;
    POINT point = {1, 1};

    initStack(&stack1, 30);
    push(stack1, point);    //把这个点放进堆栈里 push的时候把point的值复制一份,把复制本给value
    pop(stack1, &point);   //  目的:通过pop操作把这个点取出来
  }

 

//错误形式

boolean pop(STACK *head, UESR_TYPE valuePoint){  //我们是想通过这个操作取一个值

    if(head == NULL || isStackEmpty(*head)){  //把下面那两个合并了
        return FALSE;
    } 


    valuePoint = head->stack[--head->top];

}

 

void main{

   STACK *stack1 = NULL;
    POINT point = {1, 1};

    initStack(&stack1, 30);
    push(stack1, point);    //把这个点放进堆栈里  push的时候把point的值复制一份,把复制本给value
    pop(stack1, point);   //  目的:通过pop操作把这个点取出来
  }

 

说明为什么此处用UESR_TYPE *类型
假设用 pop(stack1, point); 
pop的时候如果不加&,则valuePoint的值是point值的复制本,这两个空间没有任何关系,所以取出来的值放到valuePoint里,根本就无法放进point里,故要通过传址的方式来进行。

 

//经过各种考虑之后的最终代码

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]; //把取出来的值放到valuePoint所指向的空间
    return TRUE;

}

 

 

 

 

(6)读栈顶元素的值

 

问题:考虑用什么返回值类型

 

USER_TYPE readTop(STACK head){

    if(栈非空){
        return head.stack[top-1];
    }
    if(栈为空){
        //一般思维是返回一个非正常值 但是该值无法确定用什么合适,故不用此种类型
    }
}

 

 

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];  
    //这样写也可以,因为形参里面传的是控制头的复制本,并不会改变原来控制头的值(机智如你)

}


/*
堆栈的基本操作:
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

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安安csdn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值