C和指针 第17章 经典抽象数据类型 17.2 堆栈

17.2 堆栈 
    堆栈(stack)这种数据结构最鲜明的特点就是其数据是后进先出(Last-In First-Out,LIFO)的。参加聚会的人应该很熟悉堆栈。主人的车道就是一个汽车的堆栈,最后一辆进入车道的汽车必须首先开出,第一辆进入车道的汽车只有等其余所有车辆都开走后才能开出。
    17.2.1 堆栈接口
    基本的堆栈操作通常称为push和pop。push就是把一个新值压入到堆栈的顶部,pop就是把堆栈顶部的值移出堆栈并返回这个值。堆栈只提供对它的顶部值的访问。
    在传统的堆栈接口中,访问顶部元素的唯一方法就是把它移出。另一类堆栈接口提供3种基本的操作:push、pop和top。push操作和前面描述的一样,pop只把顶部元素从堆栈中移除,它并不返回这个值。top返回顶部元素的值,但它并不把顶部元素从堆栈中移除。
    提示:
    传统的pop函数具有一个副作用:它将改变堆栈的状态。它也是访问堆栈顶部元素的唯一方法。top函数允许反复访问堆栈顶部元素的值,而不必把它保存在一个局部变量中。这个例子再次说明了设计不带副作用的函数的好处。
    17.2.2 实现堆栈
    堆栈是最容易实现的ADT之一。它的基本方法是当值被push到堆栈时,把它们存储于数组中连续的位置上。我们必须记住最近一个被push的值的下标。如果需要pop操作,只需要简单地减少这个下标值就可以了。程序17.1的头文件描述了一个堆栈模块的非传统接口。
    提示:
    注意接口只包含了用户使用堆栈所需要的信息,特别是它并没有展示堆栈的实现方式。事实上,对这个头文件稍作修改,它可以用于3种实现方式。用这种方式定义结构是一种好方法,因为它可以防止用户以为它依赖于某种特定的实现方式。
    /*
    **一个堆栈模块的接口。 
    */ 
    #define STACK_TYPE int /*堆栈所存储的值的类型。*/
    
    /*
    **push
    **把一个新值压入到堆栈中。它的参数是需要被压入的值。 
    */
    void push( STACK_TYPE value );
    
    /*
    **pop
    **从堆栈弹出一个值,并将它丢弃。
    */
    void pop( void );
    
    /*
    **top
    **返回堆栈顶部元素的值,但不对堆栈进行修改。
    */
    STACK_TYPE top( void );
    
    /*
    **is_empty
    **如果堆栈为空,返回TRUE,否则返回FALSE。
    */
    int is_empty( void );
    
    /*
    **is_full
    **如果堆栈已满,返回TRUE,否则返回FALSE。
    */
    int is_full( void );
    程序17.1 堆栈接口    stack.h
    提示:
    这个接口的一个有趣特性是存储于堆栈中的值的类型的声明方式。在编译这个堆栈模块之前,用户可以修改这个类型以适合自己的需要。
    1.数组堆栈
    在程序17.2中,第一种实现方式是使用一个静态数组。堆栈的长度以一个#define定义的形式出现,在模块被编译之前,用户必须对数组长度进行设置。我们后面所讨论的堆栈实现方案就没有这个限制。
    提示:
    所有不属于外部接口的内容都被声明为static,这可以防止用于使用预定义接口之外的任何方式访问堆栈中的值。
    /*
    **用一个静态数组实现的堆栈。数组的长度只能通过修改#define定义
    **并对模块重新进行编译来实现。 
    */
    #include "stack.h"
    #include <assert.h>
    
    #define STACK_SIZE 100 /* 堆栈中值数量的最大限制。 */
    
    /*
    **存储堆栈中值的数组和一个指向堆栈顶部元素的指针。
    */
    static STACK_TYPE stack[ STACK_SIZE ];
    static int top_element = -1;
    
    /*
    **push
    */
    void 
    push( STACK_TYPE value ){
        assert( !is_full() );
        top_element += 1;
        stack[ top_element ] = value;
    } 
    
    /*
    **pop
    */
    void 
    pop( void ){
        assert( !is_empty() );
        top_element -= 1;
    }
    
    /*
    **top
    */
    STACK_TYPE
    top( void ){
        assert( !is_empty() );
        return stack[ top_element ];
    }
    
    /*
    **is_empty
    */
    int 
    is_empty( void ){
        return top_element == -1;
    }
    
    /*
    **is_full
    */
    int 
    is_full( void ){
        return top_element == STACK_SIZE - 1;
    }
    程序17.2 用静态数组实现堆栈        a_stack.c
    变量top_element保存堆栈顶部元素的下标值。它的初始值为-1,提示堆栈为空。push函数在存储新元素前先增加这个变量的值,这样top_element始终包含顶部元素的下标值。如果它的初始值为0,top_element将指向数组的下一个可用位置。这种方式当然也可行,但它的效率稍差一些,因为它需要执行一次减法操作才能访问顶部元素。
    一种简单明了的传统pop函数的指针的写法如下所示:
    STACK_TYPE pop( void ){
        STACK_TYPE temp;
        
        assert( !is_empty() );
        temp = stack[ top_element ];
        top_element -= 1;
        return temp;
    } 
    这些操作的顺序是很重要的。top_element在元素被复制出数组之后才减1,这和push相反,后者是在被元素复制到数组之前先加1。可以通过消除这个临时变量以及随之带来的复制操作来提高效率:
    assert( !is_empty() );
    return stack[ top_element-- ];
    pop函数不需要从数组中删除元素---只减少顶部指针的值就足矣,因为用户此时已不能再访问这个旧值了。
    提示:
    这个堆栈模块的一个值得注意的特性是,它使用了assert来防止非法操作,诸如从一个空堆栈弹出元素或者向一个已满的堆栈压入元素。这个断言调用is_full和is_empty函数而不是测试top_element本身。如果以后决定以不同的方法来检测空堆栈和满堆栈,使用这种方法显然要容易很多。
    对于用于无法消除的错误,使用断言是很合法的。但如果用户希望确保程序不会终止,那么程序向堆栈压入一个新值之前必须检测堆栈是否仍有空间。因此,断言必须只能够对那些用户自己也能检查的内容进行检查。
    2.动态数组堆栈 
    接下来的这种实现方式使用了一个动态数组,但首先需要在接口中定义两个新函数:
    /*
    **create_stack
    **创建堆栈。参数指定堆栈可以保存多少个元素。
    **注意:这个函数并不用于静态数组版本的堆栈。 
    */ 
    void create_stack( size_t size );
    
    /*
    **destroy_stack
    **销毁堆栈。它释放堆栈所使用的内存。
    **注意:这个函数也不用于静态数组版本的堆栈。
    */ 
    void destroy_stack( void );

    /*
    ** 2.动态数组堆栈 stack.h
    */
    /*
    **create_stack
    **创建堆栈。参数指定堆栈可以保存多少个元素。
    **注意:这个函数并不用于静态数组版本的堆栈。
    */
    void create_stack( size_t size );
    
    /*
    **destroy_stack
    **销毁堆栈。它释放堆栈所使用的内存。
    **注意:这个函数也不用于静态数组版本的堆栈。
    */
    void destroy_stack( void );
    第1个参数用于创建堆栈,用户向它创建一个参数,用于指定数组的长度。第2个函数用于删除堆栈,为了避免内存泄漏,这个函数是必需的。
    这些声明可以添加到stack.h中,尽管前面的堆栈实现并没有定义这两个函数。注意,用户在使用静态数组类型的堆栈时并不存在错误地调用这两个函数的危险,因为它们在那个模块中并不存在。
    提示:
    一个更好的方法是把不需要的函数在数组模块中以存根的形式实现。如此一来,这两种实现方式的接口将是相同的,因此从其中一个转换到另一个会容易一些。
    有趣的是,使用动态分配数组在实现上改动得并不多(见程序17.3)。数组由一个指针代替,程序引入stack_size变量保存堆栈的长度。它们在缺省情况下都初始化为零。
    create_stack函数首先检查堆栈是否已经创建,然后分配所需数量的内存并检查分配是否成功。destroy_stack在释放内存之后把长度和指针变量重新设置为零,这样它们可以用于创建另一个堆栈。
    模块剩余部分的唯一改变是在is_full函数中与stack_size变量进行比较而不是与常量STACK_SIZE进行比较,并且在is_full和is_empty函数中都增加了一条断言。这条断言可以防止任何堆栈函数在堆栈被创建前就被调用。其余的堆栈函数并不需要这条断言,因为它们都调用了这两个函数中的其中一个。

    /*
    **一个用动态分配数组实现的堆栈。 
    **堆栈的长度在创建堆栈的函数被调用时给出,该函数必须在任何其他操作堆栈的函数被调用之前调用。
    */
    #include "stack.h"
    #include <stdio.h>
    #include <stdlib.h>
    #include <malloc.h>
    #include <assert.h>
    /*
    **用于存储堆栈元素的数组和指向堆栈顶部元素的指针。 
    */ 
    static STACK_TYPE *stack;
    static size_t stack_size;
    static int top_element = -1;
    
    /*
    **create_stack
    */
    void
    create_stack( size_t size ){
        assert( stack_size == 0 );
        stack_size = size;
        stack = malloc( stack_size * sizeof( STACK_TYPE ) );
        assert( stack != NULL );
    } 
    
    /*
    **destroy_stack
    */
    void
    destroy_stack( void ){
        assert( stack_size > 0 );
        stack_size = 0;
        free( stack );
        stack = NULL;
    }
    
    /*
    **push
    */
    void
    push( STACK_TYPE value ){
        assert( !is_full() );
        top_element += 1;
        stack[ top_element ] = value;
    }
    
    /*
    **pop
    */
    void
    pop( void ){
        assert( !is_empty() );
        top_element -= 1;
    }
    
    /*
    **top
    */
    STACK_TYPE
    top( void ){
        assert( !is_empty() );
        return stack[ top_element ];
    }
    
    /*
    **is_empty
    */
    int 
    is_empty( void ){
        assert( stack_size > 0 );
        return top_element == -1;
    }
    
    /*
    **is_full
    */
    int
    is_full( void ){
        assert( stack_size > 0 );
        return top_element == stack_size - 1;
    }
    程序17.3 用动态数组实现堆栈        d_stack.c
    警告:
    在内存有限的环境中,使用assert检查内存分配是否成功并不合适,因此它很可能导致程序终止,这未必是我们希望看到的结果。一种替代方法是从create_stack函数返回一个值,提示内存分配是否成功。当这个函数失败时,用户程序可以用一个较小的长度再试一次。
    /*
    **create_stack
    */
    STACK_TYPE *
    create_stack( size_t size ){
        assert( stack_size == 0 );
        stack_size = size;
        stack = (STACK_TYPE *)malloc( stack_size * sizeof( STACK_TYPE ) );
        
        return stack;
    }
    3.链式堆栈
    由于只有堆栈的顶部元素才可以被访问,因此使用单链表可以很好地实现链式堆栈。把一个新元素压入到堆栈是通过在链表的起始位置添加一个元素实现的。从堆栈中弹出一个元素是通过从链表中移除第一个元素实现的。位于链表头部的元素总是很容易被访问。
    在程序17.4所示的实现中,不再需要create_stack函数,但可以实现destroy_stack函数,以用于清空堆栈。由于用于存储元素的内存是动态分配的,它必须予以释放以避免内存泄漏。
    /*
    **一个用链表实现的堆栈。这个堆栈没有长度限制。 
    */ 
    #include "stack.h"
    #include <stdio.h>
    #include <stdlib.h>
    #include <malloc.h>
    #include <assert.h>
    
    #define FALSE 0
    
    /*
    **定义一个结构以存储堆栈元素,其中link字段将指向堆栈的下一个元素。
    */
    typedef struct STACK_NODE{
        STACK_TYPE value;
        struct STACK_NODE *next;
    } StackNode;
    
    /*
    **指向堆栈中第1个节点的指针。
    */
    static StackNode *stack;
    
    /*
    **create_stack
    */ 
    void
    create_stack( size_t size ){
    }
    
    /*
    **destroy_stack
    */
    void destroy_stack( void ){
        while( !is_empty() ){
            pop();
        }
    }
    
    /*
    **push
    */
    void 
    push( STACK_TYPE value ){
        StackNode *new_node;
        new_node = malloc( sizeof( StackNode ) );
        assert( new_node != NULL );
        new_node->value = value;
        new_node->next = stack;
        stack = new_node;
    }
    
    /*
    **pop
    */
    void 
    pop( void ){
        StackNode *first_node;
        assert( !is_empty() );
        first_node = stack;
        stack = first_node->next;
        free( first_node );
    }
    
    /*
    **top
    */
    STACK_TYPE 
    top( void ){
        assert( !is_empty() );
        return stack->value;
    }
    
    /*
    **is_empty
    */
    int 
    is_empty( void ){
        return stack == NULL;
    }
    
    /*
    **is_full
    */
    int
    is_full( void ){
        return FALSE;
    }
    程序17.4 用链表实现堆栈    l_stack.c
    STACK_NODE结构用于把一个值和一个指针捆绑在一起,而stack变量是一个指向这些结构变量之一的指针。当stack指针为NULL时,堆栈为空,也就是初始时的状态。
    提示:
    destroy_stack函数连续从堆栈中弹出元素,直到堆栈为空。同样,注意这个函数使用了现存的is_empty和pop函数,而不是重复那些用于实际操作的代码。
    create_stack是一个空函数,由于链式堆栈不会填满,因此is_full函数始终返回假。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

weixin_40186813

你的能量无可限量。

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

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

打赏作者

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

抵扣说明:

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

余额充值