栈的学习

栈的学习

1. 基本概念

栈是一种逻辑结构,是特殊的线性表。特殊在:

  • 只能在固定的一端操作

只要满足上述条件,那么这种特殊的线性表就会呈现一种“后进先出”的逻辑,这种逻辑就被称为栈。栈在生活中到处可见,比如堆叠的盘子、电梯中的人们、嵌套函数的参数等等。

img

由于约定了只能在线性表固定的一端进行操作,于是给栈这种特殊的线性表的“插入”、“删除”,另起了下面这些特定的名称:

  • 栈顶:可以进行插入删除的一端
  • 栈底:栈顶的对端
  • 入栈:将节点插入栈顶之上,也称为压栈,函数名通常为push()
  • 出栈:将节点从栈顶剔除,也称为弹栈,函数名通常为pop()
  • 取栈顶:取得栈顶元素,但不出栈,函数名通常为top()

基于这种固定一端操作的简单约定,栈获得了“后进先出”的基本特性,如下图所示,最后一个放入的元素,最先被拿出来:

img

2. 存储形式

栈只是一种数据逻辑,如何将数据存储于内存则是另一回事。一般而言,可以采用顺序存储形成顺序栈,或采用链式存储形成链式栈。

- 顺序栈

顺序存储意味着开辟一块连续的内存来存储数据节点,一般而言,管理栈数据除了需要一块连续的内存之外,还需要记录栈的总容量、当前栈的元素个数、当前栈顶元素位置,如果有多线程还需要配互斥锁和信号量等信息,为了便于管理,通常将这些信息统一于在一个管理结构体之中:

struct seqStack
{
    datatype *data; // 顺序栈入口
    int size;       // 顺序栈总容量
    int top;        // 顺序栈栈顶元素下标
};
img

- 链栈

链栈的组织形式与链表无异,只不过插入删除被约束在固定的一端。为了便于操作,通常也会创建所谓管理结构体,用来存储栈顶指针、栈元素个数等信息:

// 链式栈节点
typedef struct node
{
    datatype data;
    struct node *next;
}node;

// 链式栈管理结构体
struct linkStack
{
    node *top; // 链式栈栈顶指针
    int  size; // 链式栈当前元素个数
};

3.栈的顺序存储实现

顺序栈的初始化

//创建顺序栈并对顺序栈进行初始化
SeqStack_t * SeqStack_Create(unsigned int size)
{
	//1.利用calloc为顺序栈的管理结构体申请一块堆内存
	SeqStack_t *Manager = (SeqStack_t *)calloc(1,sizeof(SeqStack_t));

if(NULL == Manager)
{
	perror("calloc memory for manager is failed");
	exit(-1); //程序异常终止
}

//2.利用calloc为所有元素申请堆内存
Manager->Bottom = (DataType_t *)calloc(size,sizeof(DataType_t));

if (NULL == Manager->Bottom)
{
	perror("calloc memory for element is failed");
	free(Manager);
	exit(-1); //程序异常终止
}

//3.对管理顺序栈的结构体进行初始化(元素容量 + 最后元素下标)
Manager->Size = size;	//对顺序栈中的容量进行初始化
Manager->Top = -1;		//由于顺序栈为空,则最后元素下标初值为-1

return Manager;
}

入栈与出栈

//入栈
bool SeqStack_Push(SeqStack_t *Manager, DataType_t Data)
{
	//1.判断顺序栈是否已满
	if ( SeqStack_IsFull(Manager) )
	{
		printf("SequenceStack is Full!\n");
		return false;
	}

	//2.如果顺序栈有空闲空间,则把新元素添加到顺序栈顶
	Manager->Bottom[++Manager->Top] = Data;  //++必须写在前面
	//Manager->Top++;
	//Manager->Bottom[Manager->Top] = Data;
	return true;
}

//出栈
bool SeqStack_Pop(SeqStack_t *Manager)
{
	DataType_t temp=0;  //用于储存出栈的元素
	//1.判断顺序栈是否为空
	if ( SeqStack_IsEmpty(Manager) )
	{
		printf("SequenceStack is Empty!\n"); 
		return false;
	}
        
	//2.删除顺序栈的元素
	temp = Manager->Bottom[Manager->Top--];  //数据还残留在内存中,只是逻辑上被删除
	//Manager->Top--;
    return temp;	
}

摧毁与遍历

//遍历顺序栈的元素
void SeqStack_Print(SeqStack_t *Manager)
{
	for (int i = 0;i <= Manager->Top;i++)
	{
		printf("Stack Element[%d]=%d\n",i,Manager->Bottom[i]);
	}
}

//销毁顺序栈
void SeqStack_Destroy(SeqStack_t *Manager)
{
    //1.释放顺序栈中所有元素所占的堆内存
	free(Manager->Bottom);
	//2.释放顺序栈的管理结构体所占的堆内存
	free(Manager);
}

判断栈空间已满或空

//判断顺序栈是否已满
bool SeqStack_IsFull(SeqStack_t *Manager)
{
	return (Manager->Top + 1 == Manager->Size) ? true : false;
}

//判断顺序栈是否为空
bool SeqStack_IsEmpty(SeqStack_t *Manager)
{
	return (-1 == Manager->Top) ? true : false;
}

4.栈的链式存储实现(单向链表)

链栈的初始化

//指的是链式栈中的元素的数据类型,用户可以根据需要进行修改
typedef int  DataType_t;

//构造记录链式栈LinkStackStack各项参数
typedef  struct LinkStack
{
    DataType_t Data;//栈中元素的数据
    struct LinkStack *Next;//指向下一个元素的指针

}LLStack_t;

//构造函数,用于初始化链式栈

//创建一个空链表,空链表应该有一个头结点,对链表进行初始化
LLStack_t *CreateEmptyLinkedList(void)
{
	//1.创建一个头结点并为头结点申请内存
        LLStack_t *Head = (LLStack_t *)calloc(1,sizeof(LLStack_t));
        if(Head == NULL)
        {
            perror("Calloc memory for Head is Failed");
            exit(-1);
        }
	//2.对头结点进行初始化,头节点是不存储有效内容的
    Head->Next = NULL;

    //3.把头结点的地址返回即可S
    return Head;
}

//创建新的结点,并对新结点进行初始化(数据域+指针域)
LLStack_t *CreateNewLinkedList(DataType_t Data)
{
    //1.申请一个结点的内存空间
    LLStack_t *NewNode = (LLStack_t *)calloc(1,sizeof(LLStack_t));
    if(NewNode == NULL)
    {
        perror("Calloc memory for Head is Failed");
        return NULL;
    }
    //2.对新结点的数据域和指针域进行初始化
    NewNode->Data = Data;
    NewNode->Next = NULL;
    //3.返回新结点的地址
    return NewNode;
}

入栈与出栈

//入栈
bool LinStack_Push(LLStack_t *Head, DataType_t Data)
{
    //1.创建一个新结点
    LLStack_t *NewNode = CreateNewLinkedList(Data);
    if(NewNode == NULL)
    {
        perror("CreateNewLinkedList is Failed");
        return false;
    }
    //2.判断链表是否为空,如果为空,则新结点直接插入到链表的头部
    if(Head->Next == NULL)
    {
        Head->Next = NewNode;
        return true;
    }
    //3.如果链表不为空,则新结点插入到链表的头部
    NewNode->Next = Head->Next;
    Head->Next = NewNode;
    return true;
}

//出栈
bool LinStack_Pop(LLStack_t *Head)
{
    //1.判断链表是否为空,如果为空,直接返回
    if(Head->Next == NULL)
    {
        return false;
    }
    //2.判断链表是否只有一个结点,如果只有一个结点,则直接删除该结点
    if(Head->Next->Next == NULL)
    {
        free(Head->Next);
        Head->Next = NULL;
        return true;
    }
    //3.如果链表有多个结点,则删除第一个结点
    LLStack_t *Phead = Head->Next;
    Head->Next = Phead->Next;
    Phead->Next = NULL;
    return true;
}

摧毁与遍历

//遍历链栈的元素
void LinStack_Print(LLStack_t *Head)
{
    //1.判断链表是否为空
    if(Head->Next == NULL)
    {
        printf("LinkStack is Empty\n");
        return;
    }
    //2.遍历链表
    LLStack_t *Phead = Head->Next;
    while(Phead != NULL)
    {
        printf("%d\n",Phead->Data);
        Phead = Phead->Next;
    }
}

//销毁链栈
void LinStack_Destroy(LLStack_t *Head)
{
    //1.判断链表是否为空
    if(Head->Next == NULL)
    {
        return;
    }
    //2.遍历链表,依次释放内存
    LLStack_t *Phead = Head->Next;
    while(Phead != NULL)
    {
        LLStack_t *Ptemp = Phead;
        Phead = Phead->Next;
        free(Ptemp);
    }
    //3.释放头结点的内存
    free(Head);
}

5.笔试题

设计一个进制转换程序,使用顺序栈设计一个把十进制数转换为十六进制数的接口,实现当通过键盘输入一个非负的十进制数,可以在终端输出对应的十六进制数。

/*****************************************************
  * @function_name  : DecimalToHexadecimal
  * @brief_         : 顺序栈实现十进制转十六进制
  * @param_         : 
  *                   SeqStack_t *Manager --> 顺序栈的管理结构体
  *            
  * @retval_        : 空
  * @date_          : 2024/04/25
  * @version_       : v1.0 
  * @note_          : None
  *******************************************************/
//10进制转16进制
void DecimalToHexadecimal(SeqStack_t *Manager)
{
	//输入十进制数
	printf("please input Decima number:");
    int Decimal=0;
	scanf("%d",&Decimal); 	
	int num;//记录每一位数
    //2.利用顺序栈进行10进制转16进制
	do
	{ 	
		num=Decimal%16;
		SeqStack_Push(Manager,num);	
	}while(Decimal=Decimal/16);

	//显示部分
		printf("0X");
		for (int i = Manager->Top;i >= 0;i--)
	{
		if((Manager->Bottom[i])>9)
		printf("%c",55+(Manager->Bottom[i]));
		else
		printf("%c",48+(Manager->Bottom[i]));

	}
}

通过键盘输入一个包括 '(' 和 ')' 的字符串string ,判断字符串是否有效。要求设计算法实现检查字符串是否有效,有效的字符串需满足以下条件:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号。
/*****************************************************
  * @function_name  : BracketMatc
  * @brief_         : 顺序栈实现括号匹配
  * @param_         : 
                      SeqStack_t *Manager --> 顺序栈的管理结构体
                      const char *str --> 输入的字符串
  
  * @retval_        : bool
  * @date_          : 2024/04/25
  * @version_       : v1.0  实现括号匹配
                      v1.1  简化代码
  * @note_          : None
  *******************************************************/
//括号匹配
 bool BracketMatc(SeqStack_t *Manager,const char *str)
 {
	char *Ptsr = str; //备份地址,防止地址丢失

	//1.循环遍历字符串,寻找左括号
	while(*Ptsr)		//while(*Ptsr!='\0')
	{
		//判断字符是否为左括号,是则入栈
		if('('==*Ptsr)
		{
			SeqStack_Push(Manager,*Ptsr);
		}  
		//判断字符是否为右括号,是则出栈
		if(')'==*Ptsr)
		{
			//判断栈是否为空,是则返回false
			if(SeqStack_IsEmpty(Manager))
			{
			    return false;
			}
			//判断栈顶元素是否为左括号,是则出栈,否则返回false
			SeqStack_Pop(Manager);
		}
		Ptsr++;
	}
	//2.判断栈是否为空,是则返回true,否则返回false
	return SeqStack_IsEmpty(Manager);
 }
posted @   sanjiudemiao  阅读(47)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示