25计算机考研,数据结构知识点整理(内容借鉴了王道408+数据结构教材),还会不断完善所整理的内容,后续的内容也会不断更新(可以关注),若有错误和不足欢迎各位朋友指出!
目录
一.栈的基本概念
栈(stack)作为一种限定性线性表,是将线性表的插入和删除操作限制为仅在表的一端(表尾)进行,通常将表中允许进行插入、删除操作的一端称为栈顶(Top)(表尾),栈顶的当前位置是动态变化的,它由一个称为栈顶指针的位置指示器来指示;将表的另一端称为栈底(表头)。当栈中没有元素时称为空栈。栈的插入操作称为进栈或入栈,删除操作称为删栈或退栈。
根据上述定义,每次进栈的元素都被放在原栈顶元素之上而成为新的栈顶,而每次出栈的总是当前栈中“最新”的元素,即最后进栈的元素。在图 3.1(a)所示的栈中,元素是以,... ,,,...,的顺序进栈的,而退栈的次序却是,... ,,,。栈的操作是按后进先出的原则进行的,因此,栈又称为后进先出(last in first out,LFO)的线性表,简称为IFO表。在日常生活中也可以见到很多后进先出的例子,例如,手枪子弹夹中的子弹,子弹装入与子弹出膛均在弹夹的一端进行,先装人的子弹后发出,而后装入的子弹先发出;又如,如图3.1(b)所示的铁路调度站的表示,都是栈结构的实际应用。
栈的基本操作除了进栈(栈顶插入)、出栈(删除栈顶)外,还有建立(栈的初始化)、判空、判满及取栈顶元素等。下面给出栈的抽象数据类型定义。
ADT Stack{
数据对象:可以是任意类型的数据,但必须性质相同
结构关系:栈中数据元素之间是线性关系。
基本操作:
① InitStack(S)
操作前提:S为未初始化的栈。
操作结果:将S初始化为空栈。
②ClearStack(S)
操作前提:栈S已经存在。
操作结果:将栈S置成空栈。
③IsEmpty(S)
操作前提:栈S已经存在。
操作结果:判栈S是否为空栈。若S为空栈,则返回TRUE,否则返回FALSE。
④IsFull(S)
操作前提:栈S已经存在。
操作结果:判栈S是否为满栈。若S栈已满,则返回TRUE,否则返回FALSE。
⑤Push(S,x)
操作前提:栈S已经存在。
操作结果:在S的顶部插入(亦称入栈)元素x。若S栈未满,将x插入栈顶位置,并返回TRUE;若栈已满,则返回FALSE,表示操作失败。
⑥ Pop(S,x)
操作前提:栈S已经存在。
操作结果:删除(亦称出栈)栈S的顶部元素,并用x带回该值,返回TRUE;若栈为空,返回值为FALSE,表示操作失败。
⑦ GetTop(S,x)操作前提:栈S已经存在。
操作结果:取栈S的顶部元素赋给x所指向的单元,也称读栈顶。该操作与Pop(S,x)的不同之处在于,GetTop(S,x)不改变栈顶的位置。若栈为空,返回值为FALSE,表示操作失败。
}ADT Stack;
二.栈的表示和实现
栈作为一种特殊的线性表,在计算机中主要有两种基本的存储结构:顺序存储结构和链式存储结构。采用顺序存储的栈简称为顺序栈,采用链式存储结构的栈简称为链栈。
1.顺序栈(顺序存储结构)
顺序栈是用顺序存储结构实现的栈,一般同线性表的顺序存储结构完全相同,即利用一组地址连续放存储单元依次存放自栈底到栈顶的数据元素,同时由于栈操作的特殊性,还必须附设一个位置指针top(栈顶指针)来动态地指示栈顶元素在顺序栈中的位置,通常以top=-1或base=top表示空栈,但是为了方便操作,通常top指示真正的栈顶元素之上的下标地址;另设base指针,指示栈底元素在顺序栈中的位置。另外用stacksize表示栈可使用的最大容量,top-base==stacksize表示栈满。
顺序栈的存储结构可以用c语言中的一维数组来表示,定义如下。
#define MaxSize 50 /*设栈中元素个数为50*/
typedef struct
{
StackElementType elem[MaxSize]; /*elem是存放栈中元素的一维数组*/
int top; /*top用来存放栈顶元素的下标,top为-1表示空栈*/
}SeqStack;
栈顶指针:S.top,初始时设置S.top=-1,栈顶元素:S.data[S.top]。
进栈操作:栈不满时,栈顶指针先加1,再送值到栈顶
出栈操作:栈非空时,先取栈顶元素,再将栈顶指针减1。
栈空条件:S.top==-1;
栈满条件:S.top==MaxSize-1;
栈长:S.top+1。
另一种常见的方式是:
- 初始设置栈顶指针S.top=0;
- 进栈时先将值送到栈顶,栈顶指针再加1;
- 出栈时,栈顶指针先减1,再取栈顶元素;
- 栈空条件是S.top==0;
- 栈满条件是S.top==MaxSize。
顺序栈的入栈操作受数组上界的约束,当对栈的最大使用空间估计不足时,有可能发生栈上溢,此时应及时向用户报告消息,以便及时处理,避免出错。
栈和队列的判空、判满条件,会因实际给的条件不同而变化。
顺序栈的进栈和出栈过程如图3.2所示。
2.顺序栈的基本操作
2.1初始化
算法描述:
void InitStack(SeqStack *S)
{ /*构造一个空栈S*/
S->top=-1; //初始化栈顶指针
}
2.2判栈空
算法描述:
bool StackEmpty(SqStack S)
{
if(S.top==-1) //栈空
return true;
else //不空
return false;
}
2.3进栈
进栈时,首先判断当前栈是否已满,如果栈已满,则再进栈就会发生上溢。
栈满时的处理方法:
- 报错,返回操作系统
- 分配更大的空问,作为栈的存储空间,将原栈的内容移入新栈
算法描述:
int Push(SeqStack *S,StackElementType x)
{ /*将x置人S栈新栈顶*/
if(S->top==Stack_Size-1)
return(FALSE); /*栈已满*/
S->top++;/*修改栈顶指针*/
S->elem[S->top]=x; /*x进栈*/
return(TRUE);
}
2.4出栈
出栈时,首先判断当前栈是否为空,如果栈空,则再出栈就会发生下溢。
算法描述:
int Pop(SeqStack *S,StackElementType *x)
{
/*将S栈顶元素弹出,放到x所指的存储空间中带出*/
if(S->top==-1) /*栈为空*/
return(FALSE);
else
{
*x=S->elem[S->top]; /*栈顶元素赋给x*/
S->top--; /*修改栈顶指针*/
return(TRUE);
}
}
2.5读栈顶元素
int GetTop(SeqStack *S,StackElementType *x)
{ /*将S栈顶元素读出,放到x所指的存储空间中,栈顶指针保持不变*/
if(S->top==-1) /*栈为空*/
return(FALSE);
else
{
*x=S->elem[S->top];/*栈顶元素赋给x*/
return(TRUE);
}
}
注意:
- 在实现GetTop操作时,也可将参数说明SeqStack*S改为SeqStack S,也就是将传地址方式改为传值方式。传值比传地址更容易理解,但是传地址比传值更节约时间与空间。
- 读栈顶元素并没有出栈操作,原栈顶元素依然保留在栈中
3.共享栈(多栈共享技术)
栈的应用非常广泛,经常会出现一个程序中需要同时使用多个栈的情况。若使用顺序栈,会因为对栈空间大小难以准确估计而产生有的栈溢出、有的空间还很空闲的情况。为了解决这个问题,可以让多个栈共享一个足够大的数组空间,通过利用栈的动态特性来使其存储空间互相补充,这就是多栈的共享技术。
在顺序栈的共享技术中,最常用的是两个栈的共享技术,即双端栈。它主要利用了栈的“栈底位置不变,而栈顶位置动态变化”特性。首先申请一个共享的一维数组空间S[M],将两个栈的栈底分别放在一维数组的两端,初始化为-1、M。
两个栈的栈顶指针都指向栈顶元素,top0=-1时0号栈为空,top1=MaxSize时1号栈为空;仅当两个栈顶指针相邻(top1-top0=1)时,判断为满。当0号栈进栈时top0先加1再赋值,1号栈进栈时top1先减1再赋值;出栈时则刚好相反。
共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。其存取数据的时间复杂度均为O(1),所以对存取效率没有什么影响。
由于两个栈顶是动态变化的,这样可以形成互补,使得每个栈可用的最大空间与实际使用的需求有关。由此可见,采用双端栈要比两个栈分别申请M/2的空间利用率要高。
双端栈的数据结构定义如下:
#define M 100
typedef struct
{
StackElementType Stack[M]; /*Stack[M]为栈区*/
StackElementType top[2]; /*top[0]和 top[1]分别为两个栈顶指示器*/
}DqStack;
双端栈的示意图如图3.3所示。下面给出双端顺序栈的初始化、进栈和出栈操作的算法。
3.1初始化
void InitStack(DqStack *S)
{ /*S→top[0]为-1,标志0号栈为空;S→top[1]为M,标志1号栈为空*/
S->top[0]=-1;
S->top[1]=M;
}
3.2进栈
int Push( DqStack *S, StackElementType x, int i)
{ /*把数据元素x压人i号栈*/
if(S->top[0]+1==S->top[1]) /*栈已满*/
return(FALSE);
switch(i)
{
case0: /*0号栈*/
S->top[0]++;
S->Stack[S->top[0]]=x;
break;
case1: /*1号栈*/
S->top[1]--;
S->Stack[S->top[1]]=x;
break;
default: /*参数错误*/
return ( FALSE )
}
return(TURE);
}
3.3出栈
int Pop(DqStack *S,StackElementType *x , int i)
{ /*从i号堆栈中弹出栈顶元素并送到x中*/
switch(i)
{
case0: /*0号栈出栈*/
if(S->top[0]==-1)return(FALSE);
*x=S->Stack[S->top[0]];
S->top[0]--;
break;
case1: /*1号栈出栈*/
if(S->top[1]==M)return(FALSE);
*x=S->Stack[S->top[1]];
S->top[1]++;
break;
default:
return(FALSE);
}
return(TRUE);
}
4.链栈(链式存储结构)
链栈即采用链表(运算受限的单链表)作为存储结构实现的栈,链栈的优点是便于多个栈共享存储空间和提高其效率。为便于操作,这里采用带头结点的单链表实现栈由于栈的插入和删除操作仅限制在表头位置进行,所以链表的表头指针就作为栈顶指针如图 3.5 所示。
在图3.5中,top为栈顶指针,始终指向当前栈顶元素前面的头结点。若top->next==NULL,则代表栈空。采用链栈不必预先估计栈的最大容量,只要系统有可用空间,链栈就不会出现溢出。采用链栈时,栈的各种基本操作的实现与单链表的操作类似,在使用完毕时应释放相应链表的头指针就是栈顶空间。
- 链表的头指针就是栈顶。
- 不需要头结点
- 基本不存在栈满的情况
- 空栈相当于头指针指向空
- 插入和删除仅在栈顶处执行
链栈的结构用C语言来定义如下:
typedef struct node
{
StackElementType data ;
struct node *next;
}LinkStackNode;
typedef LinkStackNode *LinkStack;
4.1进栈
int Push(LinkStack top,StackElementType x)
/*将数据元素x压人栈top中*/
{
LinkStackNode *temp ;
temp=(LinkStackNode * )malloc(sizeof(LinkStackNode));
if(temp== NULL)
return(FALSE);/*申请空间失败*/
temp->data=x
temp->next=top->next;
top->next=temp; /*修改当前栈顶指针*/
return(TRUE);
}
4.2出栈
int Pop(LinkStack top,StackElementType *x)
{ /*将栈top的栈顶元素弹出,放到x所指的存储空间中*/
LinkStackNode *temp;
temp= top->next;
if(temp==NULL)/*栈为空*/
return(FALSE);
top->next=temp->next;
*x=temp->data;
free(temp);/*释放存储空间*/
return(TRUE);
}