栈:只允许在一端进行插入或删除操作的线性表。首先栈是一种线性表,但是限定这种线性表只能在某一端进行插入和删除操作。
栈顶:线性表允许进行插入和删除的那一端
栈底:固定的,不允许进行插入和删除的另一端
空栈:不含任何元素的空表。
最先进栈的元素,是不是就只能是最后出栈呢
答案是不一定,要看什么情况。栈对线性表的插入和删除的位置进行了限制,并没有对元素进出的时间进行限制,也就是说,在不是所有元素都进栈的情况下,进去的元素也可以出栈,只要保证是栈顶元素出栈就可以,那么变化就很多了,举例来说:
如果我们有3个整形数字元素1、2、3依次进栈,会有哪些出栈次序呢?
有没有可能是312这样的次序出栈呢,答案是不可能的,因为3先出栈,就意味着3曾经进栈,既然3进栈了,那也意味着1和2已经进栈了,此时,2一定是在1的上面,就是更接近栈顶,那么出栈只可能是321,不然不满足123一次进栈的要求,所以此时不会发生1比2先出栈的情况
InitStack(&S):初始化一个空栈S
StackEmpty(S):判断一个栈是否为空,若栈S为空返回true,否则返回false
Push(&S,x):进栈,若栈S未满,将x加入使之成为新栈顶
Pop(&S,&x):出栈,若栈S非空,弹出栈顶元素,并用x返回
GetTop(S,&x):读栈顶元素,若栈S非空,用x返回栈顶元素
ClearStack(&S):销毁栈,并释放栈S占用的存储空间。
(符号“&”是C艹特有的,用来宝石引用,有的采用C语言中的指针类型“*”,也可以达到传址的目的)
如果没有特殊要求,可以直接用这些基本的操作函数
栈的顺序存储称为顺序栈,它是利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶的位置。
顺序栈可以描述为:
#define MaxSize 50 //定义栈中元素的最大个数
typedef struct{
Elemtype data[MaxSize]; //存放栈中元素
int top; //栈顶指针
}SqStack;
利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一位数据空间,将两个栈的栈底分别设置在共享空间的两端,两个栈向共享空间的中间延伸。
两个栈的栈顶指针都指向栈顶元素,top0=-1时0号栈为空,top1=MaxSize时1号栈为空,当且仅当两个栈顶的指针相邻时,判断为占满。当0号栈进栈时top0先加1再复制,1号栈进栈时top1先减1再复制;出栈时刚好相反。
共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。其存取数据的时间复杂度均为O(1)所以对存取效率没有什么影响。
事实上使用遮掩的数据结构,通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况。就像买卖股票一样,你买入时,一定是有一个你不知道的人在做卖出操作。有人赚钱,就一定有人赔钱。这样使用两栈共享空间存储方法再有比较大的意义。
当然,这里指两个具有相同数据类型的栈,类型不同不适用这种方法。
采用链式存储的栈成为链栈,链栈的优点是便于多个栈共享存储空间和提高效率,且不存在在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。这里规定链栈没有头结点,Lhead指向栈顶元素。
栈的链式存储类型可以描述为
typedef struct Linknode{
ElemType data; //数据域
struct Linknode *next; //指针域
} *LinkStack; //栈类型定义
栈和队列具有相同的()
A. 抽象数据类型 B. 逻辑结构 C. 存储结构 D. 运算
设链表不带头结点且所有操作均在表头进行,则下列最不是和作为链栈的是()
A. 只有表头结点指针,没有表尾指针的双向循环链表
B. 只有表尾结点指针,没有表头指针的双向循环链表
C. 只有表头结点指针,没有表尾指针的单向循环链表
D. 只有表尾结点指针,没有表头指针的单向循环链表
向一个栈顶指针为top的链栈中插入一个x结点,则执行()
A. top->next=x B. x->next=top->next;top->next=x
C. x-next=top;top=x D. x->top->data;top=top->next
链栈执行pop操作,并将出栈的元素存在x中应该执行()
A. x=top;top=top->next B. x=top->data
C. top=top->next;x=top->data D. x=top->data;top=top->next
设栈S和队列Q的初始状态为空,元素e1,e2,e3,e4,e5和e6一次通过栈S,一个元素出栈后即进队列Q,若6个元素出栈的序列是e2,e4,e3,e6,e5,e1则栈的S的容量之多应该是()
A. 6 B. 4 C. 3 D. 2
若已知一个栈的入栈顺序是1、2、3、4.其出栈序列为P1、P2、P3、P4、,则P2、P4不可能是()
A. 2、4 B. 2、 1 C. 4 、 3 D. 3 、 4
线性表、栈和队列的逻辑结构都是相同的,都属于线性结构,只是它们对数据的运算不同,从而表现出不同的特点
通常栈的插入和删除在表头进行。对于选项C,插入和删除一个结点后,仍需要将其变为循环单链表,因此需要找到其尾结点,时间复杂度为O(n)。
若不做题干中的限制,则栈顶可取表头(带头结点的链表)或第二个结点(不带头结点的链表),栈指针的位置取投机诶单(带头结点的链表)或表头(不带头结点的链表)
链栈采用不带头结点的单链表表示时,进栈操作在首部插入一个结点x(即x->next=top),插入完后需要将top指向该插入的结点x。如果带头结点呢?
这里假设栈顶指针指向的是栈顶元素,所以选D;而A中首先将top指针赋给了x,错误;B中没有修改top指针的值;C为top指针指向栈顶元素的上一个元素时的答案。
本题将队列和栈结合起来,由于队列“先进先出”的特性,所以出队的序列与进队的序列是相通的,从而可以得到出栈的次序为2,4,3,6,5,1;再由栈“先进后出”的特性,进栈的次序为1,2,3,4,5,6,可见排在某个元素之后出栈却在它会前进栈的元素个数,表示之前栈内元素个数。因此,4进栈后,1和3在栈中;而后4和3出栈;6进栈后,5和1也在栈中。因此,栈的最小容量是3。
对于A,可能的顺序是1入栈,1出栈,2入栈,2出栈,3入栈,3出栈,4入栈,4出栈。
对于B可能的顺序是1234入栈,4321出栈
D可能的顺序是1入栈,1出栈,2入栈,3入栈,3出栈,2出栈,4入栈,4出栈。
而C却没有对应的序列。
假设以I和O 分别表示入栈和出栈操作。栈的初态和终态均为空,入栈和出栈的操作序列可表示为仅由I和O组成的序列,可以操作的序列称为合法序列,否则称为非法序列。
1)下面所示的序列中哪些是合法的?
A. IOIIOIOO B. IOOIOIIO C. IIIOIOIO D. IIIOOIOO
2)通过对1)的分析,写出一个算法,判定所给的操作序列是否合法。若合法,返回true否则返回false(假定被判定的操作序列已存入一维数组中)
设单链表的表头指针为L,结点结构由data和next两个域构成,其中data域为字符型。试设计算法判断该链表的全部n个字符是否中心对称。例如xyx、xyyx都是中心对称
设有两个栈s1、s2都采用顺序栈方式,并且共享一个存储区[0,…,maxsize-1],为了尽量利用空间,减少溢出的可能,可采用栈顶相向,迎面增长的存储方式。试设计s1、s2有关入栈和出栈的操作算法
1) AD
2)设被判定的操作序列已存入一维数组A中。
算法的基本设计思想:一次逐一扫描入栈出栈序列(即I和O组成的字符串)每扫描至任一位置均需检查出战次数(即O的个数)是否小于入栈次数(I的个数),若大于则为非法序列。扫描结束后,再判断入栈和出栈数是否相等,若不相等则不合题意,为非法序列。
int Judge(char A[]){
//判断字符数组A中的输入输出序列是否是和发序列。如果是返回true,否则返回false
int i =0;
int j=k=0; //i为下标,j和k分别为字母I和O的个数
while(A[i]!='0'){ //未到字符数组尾
switch(A[i]){
case 'I':j++ ;break; //入栈次数增1
case 'O';k++;
if (k>j){printf("序列非法\n", ); exit(0);}
}
i++; //不论A[i]是‘I’或‘O’,指针i均后移
} //while
if(j!=k){
printf("序列非法\n");
return false;
}
else{
printf("序列合法\n");
return true;
}
}
另解:入栈后,栈内元素个数加1;出栈后,栈内元素个数减1,因此,可以将判定一组出入栈序列是否合法,转化为一组+1、-1组成的序列,它的任一前缀子序列的累加和不小于0(每次出栈或入栈操作后判断),则合法;否则,非法
算法思想:使用栈来判断链表中的数据是否中心对称。将链表的前一半元素一次进栈。在处理链表的后一半元素时,当访问到链表的一个元素后,就从栈中弹出一一个元素,两个元素比较,若相等,则将链表中下一个元素与栈中再弹出的元素比较,直至链表到尾。这时若栈是空栈,则得出链表中心对称的结论;否则,当链表中的一个元素与栈中弹出元素不等时,结论为链表非中心对称,结束算法的执行
int dc(LinkList L,int n){
//L是带头结点的n个元素单链表,本算法判断你链表是否失中心对称
int i;
char s[n/2];
p=L->next; //p是链表的工作指针,指向待处理的当前元素
for(i=0;i2;i++){ //链表一半元素进栈
s[i]=p->data;
p=p->next;
}
i--; //回复最后的i值
if(n%2==1) //若n是技术,后移过中心节点
p=p->next;
while(p!=NULL&&s[i]==p->data){ //检测是否中心对称
i--; //i充当栈顶指针
p=p->next;
}
if(i==-1) //栈为空栈
return 1; //链表中心对称
else
return 0; //链表不中心对称
}
算法先将“链表的前一半”元素(字符)进栈。当n为偶数时,前一半和后一半的个数相同;当n为奇数时,俩你报中心节点字符不必计较,移动链表指针到下一个字符开始比较。比较过程中遇到不相等时,立即退出while循环,不再进行比较。
两个栈共享向量空间,将两个栈的栈底设在向量两段,初始时,s1栈顶指针为-1,s2栈顶指针为maxsize。两个栈顶指针相邻时为栈满。两个栈顶相向、迎面增长,栈顶指针指向栈顶元素。
#define maxsize 100 //两个栈共享顺序存储空间所能达到的最多元素数初始化为100
#define elemtp int //假设元素类型为整型
typedef struct{
elemtp stack[maxsize] //栈空间
int top[2]; //top为两个栈顶指针
}stk;
本题的关键在于,两个栈入栈和退栈时的栈顶指针的计算。s1栈时通常意义下的栈;而s2栈入栈操作时,其栈顶指针左移(-1),退栈时,栈顶指针右移(+1)
此外,对于所有栈的操作,都要注意“入栈判满,出栈判空”的检查
(1)
int push(int i ,elemtp x){
//入栈操作,i为栈号,i=0表示左边的s1栈,i=1表示右边的s2栈,x是入栈元素
//入栈成功返回1,否则返回0
if(i<0||i>1){
printf("栈号输入不对\n");
exit(0);
}
if(s.top[1]-s.top[0]==1){
printf("栈已满\n");
return 0;
}
switch(i){
case 0:s.stack[++s.top[0]]=x;return 1;break
case 1:s.stack[--s.top[1]]=x;return 1;
}
}
(2)
elemtp pop(int i ){
//退栈算法。i表示栈号,i=0时为s1栈,i=1时为s2栈
//退栈成功返回退栈元素,否则返回-1
if(i<0||i>1){
printf("栈号输入不对\n");
exit(0);
}
switch(i){
case 0:
if(s.top[0]==-1){
printf("栈空\n");
return -1;
}
else
return s.stack[s.top[0]--];
case 1:
if(s.top[1]==maxsize){
printf("栈空\n");
return -1;
}
else
return s.stack[s.top[1]++];
}//switch
}