顺序栈/链式栈

        栈是是一种限定性的线性表,它将线性表的插入和删除限定为仅在表的一端进行。将表中允许插入和删除的一端成为栈顶。所以栈顶的位置是不断动态变化的。它具有“后进先出”的特点。因为栈是由线性表实现的,所以,栈有两种存储结构:顺序存储和链式存储。对应的栈成为顺序栈和链式栈。下面,分别来介绍这两种栈的相关操作。

一,顺序栈

        它与顺序表类似,即用一组地址连续的空间存放栈中的元素。之前的顺序表是通过数组来实现,其中数组的长度必须图以前设置为一常数。当元素个数超过数组最大长度时,就会插入失败。

        下面,来实现可以扩容的顺序栈。即当元素个数超过设定的最大长度时,可以在申请更大的内存来存放元素。

1. 顺序栈的结构

        首先,因为该顺序栈要实现扩容,则不能用数组来实现。因此可以动态申请一块内存,将栈中元素放入其中。通过动态内存申请返回的指针来以数组的形式访问栈中元素。

        其次,该动态内存的大小初始时可以设置为已默认值,如果超过默认值时,可以重新申请更大的内存,从而达到扩容的目的。

        最后,需要知道栈中实际元素的个数。来记录顺序表中最后一个元素所在的位置下标。以及与默认长度进行对比。

所以,顺序栈的结构定义如下:

typedef char SeqStackType;                                                                                                            
//定义顺序栈的结构
typedef struct SeqStack
{
    SeqStackType* data;//动态申请顺序栈的内存空间
    int size;//顺序栈的实际长度
    int capacity;//顺序栈的最大长度
}SeqStack;

2. 初始化顺序栈

        顺序栈要存放元素,首先要有一块内存空间,在申请时要指定内存大小,初始时,先默认申请1000个char型的空间大小。该空间由动态申请而得,由栈中的成员data指向。从而对栈中元素进行访问。因为,初始时,栈中没有节点,所以,实际长度size为0。

代码如下:

//初始化顺序栈,stack为顺序栈的的指针
void InitSeqStack(SeqStack* stack)
{
    if(stack == NULL)
    {
        //非法输入
        return;
    }

    stack->size = 0;//初始时实际长度为0                                                                                               
    stack->capacity = 1000;//初始使顺序栈的长度设置为1000
    stack->data = (SeqStackType*)malloc(sizeof(SeqStackType)*(stack->capacity));//为顺序栈申请动态内存
}

         因为栈具有“后进先出”的特点,在出,入栈时,必须在栈的一端进行。如果在顺序表的头部进行入栈,则需要将栈中原有元素依次后移,此时需要遍历整个顺序栈。同理,出栈时,也需要遍历。

        所以,采取在顺序栈的尾部进行出,入栈。因为size-1可以用来表示顺序栈的最后一个元素所在的下标,所以在入栈时可以直接将下标为size的位置设置为指定元素。出栈时,只需将下标为size-1处的值删除即可。而无需遍历整个顺序栈。

3. 尾插入栈

(1)首先需要判定顺序栈的长度是否等于默认长度。如果相等,此时需要扩容后,方可入栈。如果不等,直接入栈。

(2)在扩容时,因为原内存空间有限,所以需要动态申请更大的空间,新空间的大小可以自定义。申请成功后,将原空间中内容拷贝到新空间中,然后原空间,最后使顺序栈中的data指向新空间即可。

(3)在入栈时,只需将下标为size的位置处的值设置为指定值,然后顺序栈长度加1即可。

代码如下:

//尾插入栈
void SeqStackPush(SeqStack* stack,SeqStackType value)
{
    if(stack == NULL)
    {
        //非法输入
        return;
    }
    if(stack->size >= stack->capacity)
    {
        //初始的顺序栈已满,此时需要扩容
        stack->capacity = stack->capacity*2 + 1;
        //申请扩容后的动态内存
        SeqStackType* new_data = (SeqStackType*)malloc(sizeof(SeqStackType)*(stack->capacity));
        //将原内存中的内容拷贝到新内存中
        int i = 0;
        for(;i < stack->size;i++)
        {
            new_data[i] = stack->data[i];
        }

        //释放原顺序栈中的内存
        free(stack->data);
        //将扩容后的内存保存在顺序栈的结构中
        stack->data = new_data;
    }                                                                                                                                 
    //尾插入栈
    stack->data[stack->size++] = value; 
    return;
}

4. 尾删出栈

(1)首先判定顺序栈中是否为空,为空则出栈失败

(2)若不为空,直接将顺序栈长度减1即可。此时,原尾元素已变为无效元素。

代码如下:

//尾删出栈
void SeqStackPop(SeqStack* stack)
{
    if(stack == NULL)
    {
        //非法输入
        return;
    }
    if(stack->size == 0)
    {
        //空顺序栈
        return;
    }
    //将尾元素设置为无效元素即可
    --stack->size;
}         

5. 取栈顶元素

(1)判断顺序栈是否为空,为空则失败

(2)不为空,因为顺序栈的尾部为栈顶所在位置,所以只需将下标为size-1处的元素保存下来即可。

代码如下:

//取栈顶元素,返回值:-1代表出错返回,0代表成功返回
int SeqStackTop(SeqStack* stack,SeqStackType* value)
{
    if(stack == NULL || value == NULL) 
    {
        //非法输入
        return -1;
    }

    if(stack->size == 0)
    {
        //空顺序栈
        return -1;
    }

    *value = stack->data[stack->size - 1];
    return 0;
}          

6. 销毁顺序栈

        顺序栈在初始化前并没有申请的内存空间和元素,所以销毁后要将顺序栈恢复为初始化前的状态。即释放data指向的动态申请的内存,将有效长度和实际长度均置为0即可。

代码如下:

//销毁顺序栈
void Destory(SeqStack* stack)
{
    stack->size = 0;
    stack->capacity = 0;
    free(stack->data);
    return;
}

二,链式栈

        链式栈是通过单链表来实现的。每次入栈一个元素,向链表中添加一个节点,出栈一个元素,释放一个节点。因为栈具有“后进先出”的特点,如果每次在链表的尾部进行插入和删除,就要遍历整个链表来找到尾节点。而在头部进行插入和删除时,只需根据头指针即可找到链表的首元素结点。而无需遍历链表。所以链式栈的出,入栈通过对链表进行头删和头插来实现。

1. 链式栈的结点结构

        链式栈是有单链表来实现的,所以与单链表的结点结构相同。由数据域和指向下一个结点的next域组成。

typedef char LinkStackType;                                                                                                           
//定义链栈的节点结构
typedef struct LinkStackNode
{
    LinkStackType data;
    struct LinkStackNode* next;
}LinkStackNode;

2. 链式栈的初始化

        与单链表的初始化相同,可以通过查看博客“单链表的基本操作”来详细了解。

//初始化链栈
void LinkStackInit(LinkStackNode** pstack)
{
    if(pstack == NULL)
    {
        //非法输入
        return;
    }
    *pstack = NULL;
}

3. 链式栈的入栈操作

        链式栈的入栈是由单链表的头插来实现的。这里也不详细说明。

//创建节点
LinkStackNode* CreateNode(LinkStackType value)
{
    LinkStackNode* new_node = (LinkStackNode*)malloc(sizeof(LinkStackNode));
    new_node->data = value; 
    new_node->next = NULL;
    return new_node; 
}   

//头插入栈
void LinkStackPush(LinkStackNode** pstack,LinkStackType value)
{
    if(pstack == NULL)
    {
        //非法输入
        return;
    }   
    
    //创建节点
    LinkStackNode* new_node = CreateNode(value);
   //将新节点的next指向原来的首原节点来做为新的首原节点
    new_node->next = *pstack;
    //使头指针指向新的首原节点                                                                                                        
    *pstack = new_node;
    return;
}

4. 链式栈的出栈操作

        链式栈的出栈操作是通过单链表的头删来实现的。

(1)首先,判断链表是否为空,为空则出栈失败

(2)不为空,头指针指向第二个节点,使第二个节点作为新的首原节点

(3)释放原来的首原节点

代码如下:

//销毁节点
void DestoryNode(LinkStackNode* node)
{
    free(node);
}   

//头删出栈
void LinkStackPop(LinkStackNode** pstack)
{
    if(pstack == NULL)
    {
        //非法输入
        return;
    }   
    if(*pstack == NULL)
    {
        //空链栈
        return;
    }   
    //保存要删除的首原节点
    LinkStackNode* to_delete = *pstack;
    //使头指针指向第二个节点 
    *pstack = to_delete->next;
    //释放要删除的节点                                                                                                                
    DestoryNode(to_delete);
    return;
}

5. 取栈顶元素

        此时的栈顶位于链表的头部。

(1)如果链表为空,则失败

(2)若不为空,将首原节点的数据域保存下来即可。

代码如下:

//取栈顶元素
int LinkStackTop(LinkStackNode* stack,LinkStackType* value)
{
    if(stack == NULL)
    {
        //空链表
        return -1;
    }
    *value = stack->data;                                                                                                             
    return 0;

}

6. 链式栈的销毁

        初始化前链式栈只有一个头指针,所以销毁后要恢复到初始化前的状态。所以

(1)遍历链表将各个节点进行释放

(2)为避免头指针变成野指针,将头指针置空

代码如下:

//销毁链式栈
void LinkStackDetory(LinkStackNode** stack)                                                                                           
{
    if(stack == NULL)
    {   
        //非法输入
        return;
    }   
    //遍历链表各节点对其进行释放
    LinkStackNode* to_delete = *stack;
    while(to_delete != NULL)
    {   
        LinkStackNode* next_node = to_delete->next;
        free(to_delete);
        to_delete = next_node;
    }   
    //将头指针置空
    *stack = NULL;
    return;
}



















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值