数据结构之栈

是一种先进后出的数据结构,先存入的数据后取出来,后存入的数据先取出来。
栈分为以下两种:
1.动态栈。动态栈是基于链表的。
2.静态栈,静态栈是基于数组的。
本文着重讨论动态栈。用C语言去实现。
栈类似于箱子,顶部只有一个口子,栈的操作只能在从顶部操作。压栈和弹栈都是操作的栈顶。
压栈:压栈就是将数据放到栈的顶部。
弹栈:弹栈就是将数据从栈顶部取出。

废话不多说,直接看代码。
动态栈是基于链表的,压入的数据要存到链表的节点中,所以先定义一个节点的数据类型。

typedef struct node
{
	int data;			//节点的数据域
	struct node * pNext;//节点的指针域
}Node,*PNode;//定义节点类型和节点类型的指针类型
//Node 等效于 struct node		PNode 等效于 struct node *

空栈具有栈顶指针,栈底指针和一个无效节点。栈顶指针和栈底指针是指向节点类型的指针,无效节点类似于链表的头节点。不存放实际数据,它的存在只是为了方便操作。

typedef struct stack
{
	PNode pTop;		//指向栈顶的指针
	PNode pBottom;  //指向栈底的指针
	Node s;			//栈中无效的节点,为了方便栈操作而定义
}Stack,*PStack;//定义栈类型和栈类型的指针类型

数据类型定义好以后我们就开始写关于栈的API吧。
创建栈

/***********
	功能:创建栈
	参数:ps是指向栈的指针的指针 如果创建失败*ps的值无效
	返回值:创建成功返回true 创建失败返回false
***********/
bool create_stack(PStack * ps)
{
	*ps = (PStack)malloc(sizeof(Stack));//申请一个空栈所需要的内存
	if (*ps == NULL)
		return false;
	(*ps)->s.pNext = NULL;			//无效节点指针域清空 表示后边再无节点
	(*ps)->pBottom = &((*ps)->s);	//栈顶指针指向无效节点
	(*ps)->pTop = &((*ps)->s);		//栈底指针指向无效节点
	return true;
}

创建栈就是创建一个空栈。
创建过程:空栈包括一个栈顶指针,一个栈底指针,和一个无效节点。申请空栈所需要的内存,无效节点指针域清空,因为它是最底部的节点。栈顶指针和栈底指针都指向无效节点。
注意参数 PStack * ps 是一个二级指针,也就是常说的指向指针的指针,ps的类型是PStack *类型也就是Stack * *类型。
压栈

/***********
	功能:压栈
	参数:ps是指向栈的指针的指针 value是压入的值
	返回值:压栈成功返回true 压栈失败返回false
***********/
bool push_stack(PStack  * ps,int value)
{
	PNode p = (PNode)malloc(sizeof(Node));//压栈时先申请节点所需的内存
	if (p == NULL)
		return false;
	p->data = value;//将压栈的值存入节点
	p->pNext = (*ps)->pTop;//将新节点的指针域指向栈顶指针(新节点和栈中最上边一个节点连起来)
	(*ps)->pTop = p;//栈顶指针指向新节点(栈顶指针上移)
	return true;
}

压栈就是将数据放入栈顶。
压栈的具体过程:创建一个新的节点,将要压入的数据放入节点。节点的指针域指向栈顶,栈顶指针指向新的节点(栈顶指针上移)。
判断栈是否为空

/***********
	功能:判断栈是否为空
	参数:ps是指向的栈的指针
	返回值:栈为空返回true 栈非空返回false
***********/
bool stack_is_empty(PStack ps)
{
	if (ps->pTop == ps->pBottom)
		return true;
	else
		return false;
}

这个很好理解,当栈顶指针和栈底指针指向同一个节点的时候,此时栈一定是空栈。其实是栈顶指针和栈底指针同时指向无效节点时,栈是一个空栈。
弹栈

/***********
	功能:弹栈
	参数:ps是指向栈的指针的指针 pvalue指向的地址保存了弹出的值
	返回值:出栈成功返回true 出栈失败返回false
***********/
bool pop_stack(PStack * ps,int *pvalue)
{
	PNode p = NULL;
	if (stack_is_empty(*ps) == true)
		return false;		//弹栈失败返回false
	p = (*ps)->pTop;		//获取要弹出节点的地址
	*pvalue = p->data;		//保存要弹出的值
	(*ps)->pTop = p->pNext;	//栈顶指针指向下一个节点(栈指针下移)
	free(p);				//释放要弹出节点的内存
	return true;			//弹栈成功返回true
}

弹栈就是将数据从栈顶取出。
弹栈的过程:先获取栈顶节点的指针,将栈顶节点的数据域拷贝出来,栈顶指针向下移动。最后释放栈顶节点的内存。一定要释放内存,否则会造成内存泄漏。内存泄漏的后果就是程序运行一段时间堆中的内存被用光,应用程序申请不到内存,程序就无法正常运行了。
清空栈

/***********
	功能:清空栈 让栈变为空栈回到创建完成以后的状态
	参数:ps是指向栈的指针的指针
	返回值:无
***********/
void clear_stack(PStack * ps)
{
	int value = 0;
	while (stack_is_empty(*ps) != true)//如果栈不为空
	{
		pop_stack(ps,&value);//弹栈
	}
}

清空就很好办了,从栈顶开始将有效节点全部弹出就好了。清空以后的栈和刚创建完成的栈一模一样,栈中只有栈顶指针,栈底指针和一个无效节点。栈顶指针和栈底指针都指向栈中的无效节点。
销毁栈

/***********
	功能:销毁栈
	参数:ps是指向栈的指针的指针
	返回值:无
***********/
void destroy_stack(PStack * ps)
{
	int value = 0;
	while (stack_is_empty(*ps) != true)//如果栈不为空
	{
		pop_stack(ps, &value);//弹栈
	}
	free(*ps);//将创建空栈时动态申请的内存释放
	*ps = NULL;//将指向栈指针的指针赋值为NULL
}

销毁过程:将栈中的有效节点全部弹出,然后将空栈所需要的内存释放掉。此时栈已经不复存在。注意与清空栈的区别,清空栈只是将栈变成一个空栈,栈还是存在的。
遍历栈:

/***********
	功能:销毁栈
	参数:ps是指向栈的指针
	返回值:无
***********/
void traverse_stack(PStack ps)
{
	PNode p = ps->pTop;//获取栈顶的节点
	printf("遍历值:");
	while (p != ps->pBottom)//当前遍历的节点不是最后一个节点
	{
		printf("%d ",p->data);//打印当前所遍历节点的值
		p = p->pNext;//遍历节点的指针指向下一个节点(遍历指针下移)
	}
	printf("\n");
}

遍历栈就是从栈顶开始,把栈中有效节点挨个问候一遍。我这里是将它们输出。
遍历过程:先获取栈顶节点的地址,将栈顶节点的地址保存到一个临时指针变量中,这里不能移动栈顶指针。如果节点不是最后一个节点就将其输出,然后临时指针变量下移,再判断,如此往复。

下面演示一下栈的使用,只是演示栈的操作过程,并不是用栈解决实际问题。

	PStack ps = NULL;	//定义指向栈的指针
	int popvalue = 0;   //保存栈中弹出的值
	create_stack(&ps);  //创建栈

	push_stack(&ps, 5);
	push_stack(&ps, 9);
	push_stack(&ps, 6);
	push_stack(&ps, 3);
	traverse_stack(ps);

	pop_stack(&ps, &popvalue);//演示程序偷懒了,没有判断弹出是否成功
	printf("弹出的值:%d\n",popvalue);
	pop_stack(&ps, &popvalue);
	printf("弹出的值:%d\n", popvalue);
	traverse_stack(ps);

	clear_stack(&ps);
	traverse_stack(ps);
	printf("%p\n", ps);

	destroy_stack(&ps);
	printf("%p\n",ps);

运行结果如下

在这里插入图片描述
重点观察清空栈以后指向栈的指针保存的是一个有效值,当销毁栈以后指向栈的指针保存的是0了,无效的地址。因为销毁以后栈已经不复存在了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值