数据结构实例精讲栈.docx
《数据结构实例精讲栈.docx》由会员分享,可在线阅读,更多相关《数据结构实例精讲栈.docx(57页珍藏版)》请在冰豆网上搜索。
![数据结构实例精讲栈.docx](https://file1.bdocx.com/fileroot1/2023-4/18/97cce3d4-736c-4724-8f3d-536c7d54c834/97cce3d4-736c-4724-8f3d-536c7d54c8341.gif)
数据结构实例精讲栈
数据结构实例精讲:
栈
栈和队列是两种特殊的线性表,它们的逻辑结构和线性表相同,只是其运算规则较线性表有更多的限制,故又称它们为运算受限的线性表。
3.1栈的定义和基本运算实现
栈是一种特殊的线性表,是一种只允许在表的一端进行插入或删除操作的线性表。
表中允许进行插入、删除操作的一端称为栈顶。
表的另一端称为栈底。
栈顶的当前位置是动态的,对栈顶当前位置的标记称为栈顶指针。
当栈中没有数据元素时,称之为空栈。
栈的插入操作通常称为进栈或入栈,栈的删除操作通常称为退栈或出栈。
在生活中我们常见到这样的例子:
假设餐厅里有一叠盘子,如果我们要从中拿取盘子,只能从最上面一个开始拿,当我们要再放上一个盘子时也只能放在最上面。
栈的结构正是如此。
根据栈的定义,每次进栈的数据元素都放在原当前栈顶元素之前而成为新的栈顶元素,每次出栈的数据元素都是原当前栈顶元素。
这样,最后进栈的数据元素总是最先出栈,因此,栈具有“后进先出”(LIFO:
FirstInLastOut)的特性。
栈的基本操作有:
(1)InitStack(S):
构造一个空栈,即初始化一个栈。
(2)StackEmpty(S):
判栈空。
若S为空栈,则返回True,否则返回false。
(3)Push(S,X):
进栈。
若栈S不满,则将元素X插入S的栈顶。
(4)Pop(S):
出栈。
若栈S非空,则将S的栈顶元素删去。
出栈时,可以返回栈顶元素,也可以不返回栈顶元素。
(5)GetTop(S):
取栈顶元素。
若栈S非空,则返回栈顶元素,但不改变栈的状态。
图3-1是一个栈的动态示意图,图中箭头代表当前栈顶指针位置。
(a)表示一个空栈;(b)表示插入一个数据元素A以后的状态;(c)表示插入数据元素B、C以后的状态;(d)表示删除数据元素C以后的状态;(e)表示删除数据元素B、A以后的状态。
简言之,若进栈顺序为A、B、C,则退栈顺序为C、B、A。
图3-1显示的是一个顺序存储结构的栈,栈也可以用链式存储结构存储。
图3-1栈的动态示意图
栈的顺序存储结构简称为顺序栈,它是运算受限的顺序表。
栈的链式存储结构称为链栈,它是运算受限的单链表,其插入和删除操作仅限制在表头位置上进行。
1.顺序栈
顺序栈是利用一组地址连续的存储单元依次存放从栈底到栈顶的若干数据元素。
可以用一维数组来定义:
#defineMAXNUM;//栈能存放的最大元素数
typedefstruct{
ElemTypedata[MAXNUM];
inttop;
}SqStack;
其中,ElemType为栈的数据元素类型,top用来指示栈顶元素的当前位置。
top=-1,表示栈空;top=MAXNUM-1,表示栈满。
【实例3-1】顺序栈的基本操作实现。
定义一个顺序栈,并实现其5个基本操作:
初始化栈、判栈空、进栈、出栈、取栈顶元素。
(1)问题分析。
因为top指示栈顶元素的当前位置,因此栈空时,top=-1。
初始化空栈时,令top=-1即可。
进栈(插入元素x为新的栈顶元素)的步骤为:
①检查栈是否已满,若栈满,则进行“上溢”错误处理。
②将栈顶指针上移一个位置(即加1),使之指向一个空闲单元。
③将新元素赋值给栈顶单元。
出栈(若栈不空,则删除S的栈顶元素)的步骤为:
①检查栈是否为空,若栈空,则进行“下溢”错误处理。
②将栈顶指针下移一个位置(即减1)。
(2)源程序。
#include
usingnamespacestd;
#defineMAXNUM100//假定预分配的栈空间最多为100个元素
typedefintElemType;//假定栈元素的数据类型为整型
typedefstruct{
ElemTypedata[MAXNUM];
inttop;
}SqStack;
voidInitStack(SqStack&S)//置栈空
{
S.top=-1;
}
boolStackEmpty(SqStackS)//判栈空
{
returnS.top==-1;
}
voidPush(SqStack&S,ElemTypex)//入栈
{
if(S.top==MAXNUM-1)
{
cout<<"栈上溢"<return;
}
S.data[++S.top]=x;//栈顶指针加1后将x入栈
}
voidPop(SqStack&S)//出栈,不返回栈顶元素
{
if(StackEmpty(S))
{
cout<<"栈为空"<return;
}
S.top--;//将栈顶指针减1
}
ElemTypeGetTop(SqStackS)//取栈顶元素
{
if(StackEmpty(S))
{
cout<<"栈为空"<exit
(1);
}
returnS.data[S.top];
}
intmain()
{
SqStacks;
InitStack(s);
for(inti=1;i<=10;i++)
Push(s,i);
while(!
StackEmpty(s))
{
cout<Pop(s);
}
cout<return0;
}
当然,栈采用顺序存储时,也可以用动态数组来实现。
请读者参看顺序表的基本操作实现。
与顺序表不同的是,栈的操作受限。
2.链栈
链栈是栈的链式存储实现,可以用单链表来定义:
structSNode
{
ElemTypedata;
structSNode*next;
};
typedefSNode*LinkStack;
【实例3-2】链栈的基本操作实现。
定义一个链栈,并实现其5个基本操作:
初始化栈、判栈空、进栈、出栈、取栈顶元素。
#include
usingnamespacestd;
typedefintElemType;//假定栈元素的数据类型为整型
structSNode
{
ElemTypedata;
structSNode*next;
};
typedefSNode*LinkStack;
voidInitStack(LinkStack&S)//置栈空
{
S=NULL;
}
boolStackEmpty(LinkStackS)//判栈空
{
returnS==NULL;
}
voidPush(LinkStack&S,ElemTypex)//入栈
{
SNode*newnode;
newnode=newSNode;
if(newnode==NULL)
{
cout<<"存储分配失败!
"<exit
(1);
}
newnode->data=x;newnode->next=S;S=newnode;
}
voidPop(LinkStack&S)//出栈,不返回栈顶元素
{
SNode*top;
if(S!
=NULL)
{
top=S;S=S->next;deletetop;
}
else
cout<<"栈为空"<}
ElemTypeGetTop(LinkStackS)//取栈顶元素
{
if(StackEmpty(S))
{
cout<<"栈为空"<exit
(1);
}
returnS->data;
}
intmain()
{
LinkStacks;
InitStack(s);
for(inti=1;i<=10;i++)
Push(s,i);
while(!
StackEmpty(s))
{
cout<Pop(s);
}
cout<return0;
}
3.2栈的应用
栈的应用非常广泛,表达式求值、递归过程实现都是栈应用的典型例子。
【实例3-3】数制转换。
编写一个函数利用栈把一个十进制整数转换为二至十六之间的任一进制数。
(1)问题分析。
将十进制整数转换成R进制整数的规则是:
将十进制整数不断除以R,记下余数,直至十进制整数为0,将所得的余数逆序排列,即是对应的R进制数。
利用栈来保存除以R过程中得到的余数,除法过程结束后,依次将余数出栈,即得对应的R进制数。
由于题目要求R为2~16,因此采用字符串来存储得到的R进制数。
(2)函数程序代码。
stringMultibaseOutput(intnum,intn)
//num为要转换的10进制数,n是转换为多少进制
{
stringdigitchar(“0123456789ABCDEF”);//保存每位可能的字符
stringstrnum;
SqStackstkchar;
InitStack(stkchar);//栈初始化
while(num!
=0)
{
Push(stkchar,digitchar[num%n]);//余数入栈
num/=n;
}
while(!
StackEmpty(stkchar))
{
strnum+=GetTop(stkchar);//余数出栈,加入到结果字符串中
Pop(stkchar);
}
returnstrnum;
}
(3)函数应用的示例源程序。
#include
#include
usingnamespacestd;
#defineMAXNUM100//假定预分配的栈空间最多为100个元素
typedefcharElemType;//假定栈元素的数据类型为字符型
typedefstruct{
ElemTypedata[MAXNUM];
inttop;
}SqStack;
voidInitStack(SqStack&S)//置栈空
{
S.top=-1;
}
boolStackEmpty(SqStackS)//判栈空
{
returnS.top==-1;
}
voidPush(SqStack&S,ElemTypex)//入栈
{
if(S.top==MAXNUM-1)
{
cout<<"栈上溢"<return;
}
S.data[++S.top]=x;//栈顶指针加1后将x入栈
}
voidPop(SqStack&S)//出栈,不返回栈顶元素
{
if(StackEmpty(S))
{
cout<<"栈为空"<return;
}
S.top--;//将栈顶指针减1
}
ElemTypeGetTop(SqStackS)//取栈顶元素
{
if(StackEmpty(S))
{
cout<<"栈为空"<exit
(1);
}
returnS.data[S.top];
}
stringMultibaseOutput(intnum,intn)
//num为要转换的10进制数,n是转换为多少进制
{
stringdigitchar("0123456789ABCDEF");//保存每位可能的字符
stringstrnum;
SqStackstkchar;
InitStack(stkchar);//栈初始化
while(num!
=0)
{
Push(stkchar,digitchar[num%n]);//余数入栈
num/=n;
}
while(!
StackEmpty(stkchar))
{
strnum+=GetTop(stkchar);//余数出栈,加入到结果字符串中
Pop(stkchar);
}
returnstrnum;
}
intmain()
{
intnumber,base;
stringm;
cout<<"请输入一个十进制整数:
";
cin>>number;
cout<<"请输入需要转换的进制:
";
cin>>base;
if(number>0)
m=MultibaseOutput(number,base);
else
m="-"+MultibaseOutput(-number,base);
cout<<"转换结果为:
"<return0;
}
【实例3-4】括号是否匹配。
表达式中的括号有以下两对:
“(”和“)”、“[”和“]”,编写一个函数判别表达式中的括号是否配对出现。
(1)问题分析。
判断表达式中括号是否匹配,可通过栈。
简单说就是对表达式进行扫描,当遇到左括号时进栈。
当遇到右括号时,若栈不为空且栈顶元素是其对应的左括号,则出栈;若栈空或栈顶元素不是其对应的左括号,则结论是括号不配对,返回false。
如此下去,扫描表达式结束时,栈为空则正确,否则括号不匹配。
(2)函数程序代码。
boolBracketMatch(chara[])
{
inti=0;
SqStacks;
InitStack(s);
ElemTypex;
while(a[i]!
='\0')
{
switch(a[i])
{
case'(':
Push(s,a[i]);break;
case'[':
Push(s,a[i]);break;
case')':
if(StackEmpty(s))returnfalse;
x=GetTop(s);
if(x=='(')Pop(s);
elsereturnfalse;
break;
case']':
if(StackEmpty(s))returnfalse;
x=GetTop(s);
if(x=='[')Pop(s);
elsereturnfalse;
break;
default:
break;
}
i++;
}
if(!
StackEmpty(s))returnfalse;
returntrue;
}
(3)函数应用的示例源程序。
#include
#include
usingnamespacestd;
#defineMAXNUM100//假定预分配的栈空间最多为100个元素
typedefcharElemType;//假定栈元素的数据类型为字符型
typedefstruct{
ElemTypedata[MAXNUM];
inttop;
}SqStack;
voidInitStack(SqStack&S)//置栈空
{
S.top=-1;
}
boolStackEmpty(SqStackS)//判栈空
{
returnS.top==-1;
}
voidPush(SqStack&S,ElemTypex)//入栈
{
if(S.top==MAXNUM-1)
{
cout<<"栈上溢"<return;
}
S.data[++S.top]=x;//栈顶指针加1后将x入栈
}
voidPop(SqStack&S)//出栈,不返回栈顶元素
{
if(StackEmpty(S))
{
cout<<"栈为空"<return;
}
S.top--;//将栈顶指针减1
}
ElemTypeGetTop(SqStackS)//取栈顶元素
{
if(StackEmpty(S))
{
cout<<"栈为空"<exit
(1);
}
returnS.data[S.top];
}
boolBracketMatch(chara[])
{
inti=0;
SqStacks;
InitStack(s);
ElemTypex;
while(a[i]!
='\0')
{
switch(a[i])
{
case'(':
Push(s,a[i]);break;
case'[':
Push(s,a[i]);break;
case')':
if(StackEmpty(s))returnfalse;
x=GetTop(s);
if(x=='(')Pop(s);
elsereturnfalse;
break;
case']':
if(StackEmpty(s))returnfalse;
x=GetTop(s);
if(x=='[')Pop(s);
elsereturnfalse;
break;
default:
break;
}
i++;
}
if(!
StackEmpty(s))returnfalse;
returntrue;
}
intmain()
{
charexpress[80];
intresult;
cout<<"请输入一个表达式:
";
cin>>express;
if(BracketMatch(express))
cout<<"括号正确匹配!
"<else
cout<<"括号不能正确匹配!
"<return0;
}
【实例3-5】后缀表达式。
表达式的表示形式有中缀、前缀和后缀3种形式。
人们日常通用的书写形式是中缀表达式。
由于表达式的计算要按操作符的优先级进行,因而中缀表达式不方便进行计算机处理。
通常将中缀表达式转换为一个与之等价的后缀表达式(等价是指两个表达式的计算顺序和计算结果完全相同),以方便计算机处理。
后缀表达式中只有操作数和操作符(不会出现括号)。
操作符在两个操作数之后。
它的计算规则非常简单,严格按照从左到右的次序依次执行每一个操作。
每遇到一个操作符,就将前面的两个数执行相应的操作。
编写一个函数,用栈实现将一个书写正确的中缀表达式(为简单起见,设表达式中操作数或为单字母变量、或为小于10的整数,算符只有括号、加、减、乘、除五种)转换成后缀表达式。
例如表达式8-(3+5)*(5-6/2)的后缀表达式是:
835+562/-*-。
表达式a+((b*c-d)/e+f*g/h)+i/j的后缀表达式是:
abc*d-e/fg*h/++ij/+。
(1)问题分析。
中缀表达式exp1转为后缀表达式exp2的方法如下:
设操作符栈s,初始为空栈后,压入优先级最低的操作符‘#’。
对中缀表达式从左向右扫描,遇到操作数,直接写入exp2;若是操作符(记为w),分如下情况处理:
①w为一般操作符(“+”、“-”、“*”、“/”等),要与栈顶操作符比较优先级,若w优先级高于栈顶操作符,则入栈;否则,栈顶运算符退栈到exp2,w再与新栈顶操作符作上述比较处理,直至w入栈。
②w为左括号“(”,w入栈。
③w为右括号“)”,操作符栈退栈并进入exp2,直到碰到左括号为止,左括号退栈(不能进入exp2),右括号也丢掉,达到exp2中消除括号的目的。
④w为“#”,表示中缀表达式exp1结束,操作符栈退栈到exp2,直至碰到“#”,退栈,整个操作结束。
(2)函数程序代码。
BoolIsOperator(charc)
{
char*p=”#+-*/”;
while(*p){
if(*p==c)
returntrue;
p++;
}
returnfalse;
}
boolPrior(charc1,charc2)
{
charch[]=”#(+-*/”;
inti=0,j=0;
while(ch[i]&&ch[i]!
=c1)i++;
if(i==3)i--;//加和减可认为是同级别的运算符
if(i==5)i--;//乘和除可认为是同级别的运算符
while(ch[j]&&ch[j]!
=c2)j++;
if(j==3)j--;
if(j==5)j--;
if(i>=j)returntrue;
elsereturnfalse;
}
voidInversePolandExpression(charBuffer[])
{
SqStacks;
InitStack(s);
inti=0,j=0;
ElemTypee;
Push(s,’#’);
while(Buffer[i]!
=’\0’)
{
if(IsOperator(Buffer[i]))//是操作符
{
e=GetTop(s);
if(Prior(e,Buffer[i]))
{//当栈顶优先权高于当前序列时,退栈
Buffer[j]=GetTop(s);
Pop(s);
j++;
}
else
{
Push(s,Buffer[i]);
i++;
}
}
elseif(Buffer[i]==’(‘)//是左括号
{
Push(s,Buffer[i]);
i++;
}
elseif(Buffer[i]==’)’)//是右括号
{
while(GetTop(s)!
=’(‘)
{
Buffer[j]=GetTop(s);
Pop(s);
j++;
}
Pop(s);//丢掉左括号
i++;
}
else//是操作数
{
Buffer[j]=Buffer[i];
i++;
j++;
}
}
while(!
StackEmpty(s))
{
e=GetTop(