堆棧

抽象数据类型

堆疊(stack)又稱為堆棧,是計算機科學中的一種抽象資料型別,只允許在有序的線性資料集合的一端(稱為堆疊頂端,top)進行加入數據(push)和移除數據(pop)的運算。因而按照後進先出(LIFO, Last In First Out)的原理運作,堆疊常用一維數組連結串列來實現。常與另一種有序的線性資料集合佇列相提並論。

「堆棧」的各地常用名稱
中國大陸堆棧、棧
臺灣堆疊
堆疊的簡單示意圖

操作

堆疊使用兩種基本操作:推入(壓棧,push)和彈出(彈棧,pop):

  • 推入:將資料放入堆疊頂端,堆疊頂端移到新放入的資料。
  • 彈出:將堆疊頂端資料移除,堆疊頂端移到移除後的下一筆資料。

上面就是棧最核心的兩個基本操作,可以在棧的可視化頁面中直觀理解這裡的操作。[1]

 
棧的操作可視化圖片

特點

堆棧的基本特點:

  1. 先入後出,後入先出。
  2. 除頭尾節點之外,每個元素有一個前驅,一個後繼。

抽象定義

以下是堆棧的VDM(Vienna Development Method英語Vienna Development Method):[2]

函數簽名:

  init: -> Stack
  push: N x Stack -> Stack
  top: Stack -> (N   ERROR)
  pop: Stack -> Stack
  isempty: Stack -> Boolean

此處的N代表某個元素(如自然數),而 表示集合求並。

語義:

  top(init()) = ERROR
  top(push(i,s)) = i
  pop(init()) = init()
  pop(push(i, s)) = s
  isempty(init()) = true
  isempty(push(i, s)) = false

軟件堆棧

堆棧可以用數組鍊表兩種方式實現,一般為一個堆棧預先分配一個大小固定且較合適的空間並非難事,所以較流行的做法是Stack結構下含一個數組。如果空間實在緊張,也可用鍊表實現,且去掉表頭

這裡的例程是以C語言實現的。

陣列堆疊

存儲結構

/* c3-1.h 栈的顺序存储表示 */
#define STACK_INIT_SIZE 10 /* 存储空间初始分配量 */
#define STACK_INCREMENT 2 /* 存储空间分配增量 */

typedef struct SqStack
{
	SElemType *base; /* 在栈构造之前和销毁之后,base的值为NULL */
	SElemType *top; /* 栈顶指针 */
	int stacksize; /* 当前已分配的存储空间,以元素为单位 */
}SqStack; /* 顺序栈 */

基本操作

/* bo3-1.c 顺序栈(存储结构由c3-1.h定义)的基本操作(9个) */
void InitStack(SqStack *S)
{	/* 构造一个空栈S */
	(*S).base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
	if(!(*S).base)
		exit(OVERFLOW); /* 存储分配失败 */
	(*S).top=(*S).base;
	(*S).stacksize=STACK_INIT_SIZE;
}

void DestroyStack(SqStack *S)
{	/* 销毁栈S,S不再存在 */
	free((*S).base);
	(*S).base=NULL;
	(*S).top=NULL;
	(*S).stacksize=0;
}

void ClearStack(SqStack *S)
{	/* 把S置为空栈 */
	(*S).top=(*S).base;
}

Status StackEmpty(SqStack S)
{	/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
	if(S.top==S.base)
		return TRUE;
	else
		return FALSE;
}

int StackLength(SqStack S)
{	/* 返回S的元素个数,即栈的长度 */
	return S.top-S.base;
}

Status GetTop(SqStack S,SElemType *e)
{ /* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
	if(S.top>S.base)
	{
		*e=*(S.top-1);
		return OK;
	}
	else
		return ERROR;
}

void Push(SqStack *S,SElemType e)
{	/* 插入元素e为新的栈顶元素 */
	if((*S).top-(*S).base>=(*S).stacksize) /* 栈满,追加存储空间 */
	{
		(*S).base=(SElemType *)realloc((*S).base,((*S).stacksize+STACK_INCREMENT)*sizeof(SElemType));
		if(!(*S).base)
			exit(OVERFLOW); /* 存储分配失败 */
		(*S).top=(*S).base+(*S).stacksize;
		(*S).stacksize+=STACK_INCREMENT;
	}
	*((*S).top)++=e;
}

Status Pop(SqStack *S,SElemType *e)
{	/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
	if((*S).top==(*S).base)
		return ERROR;
	*e=*--(*S).top;
		return OK;
}

void StackTraverse(SqStack S,void(*visit)(SElemType))
{	/* 从栈底到栈顶依次对栈中每个元素调用函数visit() */
	while(S.top>S.base)
		visit(*S.base++);
	printf("\n");
}

[3]

串列堆疊

存儲結構

/* c2-2.h 线性表的单链表存储结构 */
struct LNode
{
	ElemType data;
	struct LNode *next;
};
typedef struct LNode *LinkList; /* 另一种定义LinkList的方法 */

基本操作

/* bo3-5.c 链栈(存储结构由c2-2.h定义)的基本操作(4个) */
/* 部分基本操作是由bo2-8.cpp中的函数改名得来 */
/* 另一部分基本操作是由调用bo2-8.cpp中的函数(取特例)得来 */
typedef SElemType ElemType; /* 栈结点类型和链表结点类型一致 */
#include"c2-2.h" /* 单链表存储结构 */
typedef LinkList LinkStack; /* LinkStack是指向栈结点的指针类型 */
#define InitStack InitList /* InitStack()与InitList()作用相同,下同 */
#define DestroyStack DestroyList
#define ClearStack ClearList
#define StackEmpty ListEmpty
#define StackLength ListLength
#include"bo2-8.c" /* 无头结点单链表的基本操作 */

Status GetTop(LinkStack S,SElemType *e)
{	/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
	return GetElem(S,1,e);
}

Status Push(LinkStack *S,SElemType e)
{	/* 插入元素e为新的栈顶元素 */
	return ListInsert(S,1,e);
}

Status Pop(LinkStack *S,SElemType *e)
{	/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
	return ListDelete(S,1,e);
}

void StackTraverse(LinkStack S,void(*visit)(SElemType))
{	/* 从栈底到栈顶依次对栈中每个元素调用函数visit() */
	LinkStack temp,p=S; /* p指向栈顶元素 */
	InitStack(&temp); /* 初始化临时栈temp */
	while(p)
	{
		Push(&temp,p->data); /* 由S栈顶到栈底,依次将栈元素入栈到temp栈 */
		p=p->next;
	}
	ListTraverse(temp,visit); /* 遍历temp线性表 */
}

鍊表基本操作

/* bo2-8.c 不带头结点的单链表(存储结构由c2-2.h定义)的部分基本操作(9个) */
#define DestroyList ClearList /* DestroyList()和ClearList()的操作是一样的 */
void InitList(LinkList *L)
{	/* 操作结果:构造一个空的线性表L */
	*L=NULL; /* 指针为空 */
}

void ClearList(LinkList *L)
{	/* 初始条件:线性表L已存在。操作结果:将L重置为空表 */
	LinkList p;
	while(*L) /* L不空 */
	{
		p=*L; /* p指向首元结点 */
		*L=(*L)->next; /* L指向第2个结点(新首元结点) */
		free(p); /* 释放首元结点 */
	}
}

Status ListEmpty(LinkList L)
{	/* 初始条件:线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */
	if(L)
		return FALSE;
	else
		return TRUE;
}

int ListLength(LinkList L)
{	/* 初始条件:线性表L已存在。操作结果:返回L中数据元素个数 */
	int i=0;
	LinkList p=L;
	while(p) /* p指向结点(没到表尾) */
	{
		p=p->next; /* p指向下一个结点 */
		i++;
	}
	return i;
}

Status GetElem(LinkList L,int i,ElemType *e)
{	/* L为不带头结点的单链表的头指针。当第i个元素存在时,其值赋给e并返回OK,否则返回ERROR */
	int j=1;
	LinkList p=L;
	if(i<1) /* i值不合法 */
		return ERROR;
	while(j<i&&p) /* 没到第i个元素,也没到表尾 */
	{
		j++;
		p=p->next;
	}
	if(j==i) /* 存在第i个元素 */
	{
		*e=p->data;
		return OK;
	}
	else
		return ERROR;
}

int LocateElem(LinkList L,ElemType e,Status(*compare)(ElemType,ElemType))
{ 	/* 初始条件:线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0) */
	/* 操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。 */
	/*           若这样的数据元素不存在,则返回值为0 */
	int i=0;
	LinkList p=L;
	while(p)
	{
		i++;
		if(compare(p->data,e)) /* 找到这样的数据元素 */
		return i;
		p=p->next;
	}
	return 0;
}

Status ListInsert(LinkList *L,int i,ElemType e)
{	/* 在不带头结点的单链线性表L中第i个位置之前插入元素e */
	int j=1;
	LinkList p=*L,s;
	if(i<1) /* i值不合法 */
		return ERROR;
	s=(LinkList)malloc(sizeof(struct LNode)); /* 生成新结点 */
	s->data=e; /* 给s的data域赋值 */
	if(i==1) /* 插在表头 */
	{
		s->next=*L;
		*L=s; /* 改变L */
	}
	else
	{	/* 插在表的其余处 */
		while(p&&j<i-1) /* 寻找第i-1个结点 */
		{
			p=p->next;
			j++;
		}
		if(!p) /* i大于表长+1 */
			return ERROR;
		s->next=p->next;
		p->next=s;
	}
	return OK;
}

Status ListDelete(LinkList *L,int i,ElemType *e)
{	/* 在不带头结点的单链线性表L中,删除第i个元素,并由e返回其值 */
	int j=1;
	LinkList p=*L,q;
	if(i==1) /* 删除第1个结点 */
	{
		*L=p->next; /* L由第2个结点开始 */
		*e=p->data;
		free(p); /* 删除并释放第1个结点 */
	}
	else
	{
		while(p->next&&j<i-1) /* 寻找第i个结点,并令p指向其前驱 */
		{
			p=p->next;
			j++;
		}
		if(!p->next||j>i-1) /* 删除位置不合理 */
			return ERROR;
		q=p->next; /* 删除并释放结点 */
		p->next=q->next;
		*e=q->data;
		free(q);
	}
	return OK;
}

void ListTraverse(LinkList L,void(*vi)(ElemType))
{	/* 初始条件:线性表L已存在。操作结果:依次对L的每个数据元素调用函数vi() */
	LinkList p=L;
	while(p)
	{
		vi(p->data);
		p=p->next;
	}
	printf("\n");
}

[3] 堆棧有時候也常用來指代堆棧段

硬件堆棧

架構層次上的堆棧通常被用以申請和訪問內存。

硬件支持

大多數CPU都有用作堆棧指針的寄存器。

堆疊的應用

參考文獻

  1. ^ 棧的可視化頁面: 在線可視化演示棧的各種操作
  2. ^ Jones: "Systematic Software Development Using VDM"
  3. ^ 3.0 3.1 高一凡. 《数据结构》算法实现及解析 2004年10月第2版. 西安: 西安電子科技大學出版社. ISBN 9787560611761 (中文). 

參見