堆栈(stack)这种数据结构最鲜明的特点就是其后进先出(Last-In First-Out,LIFO)的方式。
/**
* 所有的ADT都必须确定一件事情——如何获取内存来存储值。有三种可选方案:
* 静态数组、动态分配的数组和动态分配的链式结构。
*
* 静态数组要求结构的长度固定。而且,这个长度必须在编译时确定,但是,这个方案最为简单,而且最不容易出错。
* 动态数组,可以在运行时才决定数组的长度。而且,如果需要的话,你可以通过分配一个新的、
* 更大的数组、把原来数组的元素复制到新数组中,然后删除原先的数组,从而达到动态改变数组长度的目的。
* 在决定是否采用动态数组时,你需要在由此增加的复杂性和随之产生的灵活性(不需要一个固定的、预定确定的长素)
* 之间作一番权衡
* 链式结构提供最大程度的灵活性。每个元素在需要时才单独进行分配,所以除了不能超过机器可用的内存之外,这种方式对
* 元素的数量几乎没有限制。但是,链式结构的链接字段需要消耗一定的内存,在链式结构中访问一个特定元素的效率不如数组。
*/
利用静态数组实现的堆栈的头文件: [ stack.h ]
/**
* 堆栈是最容易实现的ADT之一。它的基本方法是当值被push到堆栈时把它们存储于数组中连续
* 的位置上,但必须记住最近一个被push的值的下标。如果需要执行pop操作,你只需要简单地减少
* 这个下标值就可以了。
*
* 提示:注意接口只包含了用户使用堆栈所需要的信息,特别是它并没有展示堆栈的实现方法,事实上,
* 对这个头文件稍做修改,它可以用于所有的三种实现方式。用这个方式定义接口是一种好方法。因为它防止
* 用户以为它依赖于某种特定的实现方式。
*/
/**
* 一个堆栈的接口
*/
#define STACK_TYPE int //堆栈所存储的值的类型
/**
* push
* 把一个新值压入到堆栈中,它的参数是需要被压入的值
*/
void push(STACK_TYPE value);
/**
* pop
* 从堆栈探出一个值,并将其丢弃。
*/
void pop();
/**
* top
* 返回堆栈顶部元素的值,但不对堆栈进行修改
*/
STACK_TYPE top();
/**
* is_empty
* 如果堆栈为空,返回TRUE,否则,返回FALSE
*/
int is_empty();
/**
* is_full
* 如果堆栈已满,返回TRUE,否则,返回FALSE
*/
int is_full();
静态数组实现堆栈的源程序文件: [ a_stack.c ]
/**
* 数组堆栈
* 使用静态数组。堆栈的长度以一个#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()
{
assert(!is_empty());
top_element -= 1;
}
/**
* 传统pop
*/
STACK_TYPE pop1()
{
assert(!is_empty());
return stack[top_element--];
}
/**
* top
*/
STACK_TYPE top()
{
assert(!is_empty());
return stack[top_element];
}
/**
* is_empty
*/
int is_empty()
{
return top_element == -1;
}
/**
* is_full
*/
int is_full()
{
return top_element == STACK_SIZE - 1;
}
其中还有一种简单明了的传统pop函数的写法如下所示:
STACK_TYPE pop()
{
STACK_TYPE temp;
assert(!is_empty());
temp = stack[!top_element];
top_element -= 1;
return temp;
}
/*
这些操作的顺序都很重要的。top_element在元素被复制之后才减1,这和push相反,后者是
在被元素复制到数组之前先加1.我们可以通过消除这个临时变量以及随之带来的复制操作来提高效率:
*/
STACK_TYPE pop()
{
assert(!is_empty());
return stack[top_element--];
}
利用动态数组实现的堆栈的头文件: [ stack.h ]
/**
* 这种实现方式使用了动态数组,但我们首先需要在接口中定义两个新函数
*/
#include <stdlib.h> //包含数据类型:size_t
/**
* 一个堆栈的接口
*/
#define STACK_TYPE int //堆栈所存储的值的类型
/**
* create_stack
* 创建堆栈,参数不指定堆栈可以保存多少个元素
*/
void create_stack(size_t size);
/**
* destroy_stack
* 销毁堆栈,它释放堆栈所使用的内存。
* 注意:这个函数也不用于静态数组的版本
*/
void destroy_stack();
/**
* push
* 把一个新值压入到堆栈中,它的参数是需要被压入的值
*/
void push(STACK_TYPE value);
/**
* pop
* 从堆栈探出一个值,并将其丢弃。
*/
void pop();
/**
* top
* 返回堆栈顶部元素的值,但不对堆栈进行修改
*/
STACK_TYPE top();
/**
* is_empty
* 如果堆栈为空,返回TRUE,否则,返回FALSE
*/
int is_empty();
/**
* is_full
* 如果堆栈已满,返回TRUE,否则,返回FALSE
*/
int is_full();
动态数组实现堆栈的源程序文件: [ b_stack.c ]
/**
* create_stack函数首先检查堆栈是否已经创建。
* 然后分配所需数量的内存并检查分配是否成功。
* destroy_stack在释放内存之后才把长度和指针重新设置为ling,
* 这样它们可以用于创建另一个堆栈
*/
/**
* 一个用动态分配数组实现的堆栈
* 堆栈的长度在创建堆栈的函数被调用时给出,该函数必须在任何
* 其他操作堆栈的函数被调用之前调用
*/
#include <stdio.h>
#include "stack.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 = (STACK_TYPE *)malloc(stack_size * (sizeof(STACK_TYPE)));
assert(stack != NULL);
}
/**
* destroy_stack
*/
void destroy_stack()
{
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()
{
assert(!is_empty());
top_element -= 1;
}
/**
* top
*/
STACK_TYPE top()
{
assert(!is_empty());
return stack[top_element];
}
/**
* is_empty
*/
int is_empty()
{
assert(stack_size > 0);
return top_element == -1;
}
/**
* is_full
*/
int is_full()
{
assert(stack_size > 0);
return top_element == stack_size - 1;
}
利用链式结构实现的堆栈的头文件: [ stack.h ]
/**
* 这种实现方式使用了动态数组,但我们首先需要在接口中定义两个新函数
*/
#include <stdlib.h> //包含数据类型:size_t
/**
* 一个堆栈的接口
*/
#define STACK_TYPE int //堆栈所存储的值的类型
/**
* create_stack
* 创建堆栈,参数不指定堆栈可以保存多少个元素
*/
void create_stack(size_t size);
/**
* destroy_stack
* 销毁堆栈,它释放堆栈所使用的内存。
* 注意:这个函数也不用于静态数组的版本
*/
void destroy_stack();
/**
* push
* 把一个新值压入到堆栈中,它的参数是需要被压入的值
*/
void push(STACK_TYPE value);
/**
* pop
* 从堆栈探出一个值,并将其丢弃。
*/
void pop();
/**
* top
* 返回堆栈顶部元素的值,但不对堆栈进行修改
*/
STACK_TYPE top();
/**
* is_empty
* 如果堆栈为空,返回TRUE,否则,返回FALSE
*/
int is_empty();
/**
* is_full
* 如果堆栈已满,返回TRUE,否则,返回FALSE
*/
int is_full();
链式结构实现堆栈的源程序文件: [ c_stack.c ]
/**
* 由于堆栈的顶部元素才可以被访问,所以使用单链表就可以很好地实现链式堆栈。把一个
* 新元素压入到堆栈是通过在链表的起始位置添加一个元素来实现的。从堆栈中弹出一个元素是
* 通过从链表中移除一个元素实现的。位于链表头部的元素总是很容易被访问。
*
* 本程序中:
* 不再需要create_stack函数,但可以实现destroy_stack函数用于清空堆栈。由于用于存储
* 元素的内存是动态分配的。它必须予以释放以避免内存泄漏。
*/
/**
* 一个用链表实现的堆栈。这个堆栈没有长度限制!
*/
#include <stdio.h>
#include "stack.h"
#include <assert.h>
/**
* 定义一个结构以存储堆栈元素,其中link字段将指向堆栈的下一个元素
*/
typedef struct STACK_NODE
{
STACK_TYPE value;
struct STACK_NODE *next;
} StackNode;
/**
* 指向堆栈中第一个节点的指针
*/
static StackNode *stack;
/**
* create_stack
*/
void create_stack(size_t size)
{
;
}
/**
* destroy_stack
*/
void destroy_stack()
{
while (!is_empty())
{
pop();
}
}
/**
* push
*/
void push(STACK_TYPE value)
{
StackNode *new_node;
new_node = (StackNode *)malloc(sizeof(StackNode));
assert(new_node != NULL);
new_node->value = value;
new_node->next = stack;
stack = new_node;
}
/**
* pop
*/
void pop()
{
StackNode *first_node;
assert(!is_empty());
first_node = stack;
stack = first_node->next;
free(first_node);
}
/**
* top
*/
STACK_TYPE top()
{
assert(!is_empty());
return stack->value;
}
/**
* is_empty
*/
int is_empty()
{
return stack == NULL;
}
/**
* is_full
*/
int is_full()
{
return 0; //因为链式堆栈不会填满,所以is_full函数始终返回假
}