关于栈的学习
原创博文,转载请注明出处
栈(Stack)是一种线性表,但是只允许在一端进行插入或删除操作。
栈的顺序存储成为顺序栈,它是利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶的位置。
栈的顺序存储类型可描述为
1 #define MaxSize 50 2 typedef struct{ 3 Elemtype data[MaxSize]; 4 int top 5 }SqStack
初始时设置S.top=-1,栈顶元素:S.data[S.top]
栈空条件:S.top==-1;栈满条件:S.top==MaxSize-1;栈长:S.top+1
关于进栈和出栈的操作比较简单(只需要操作栈顶指针)在这里就不再描述。
利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一维数据空间,将两个栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸。在进行出栈和进栈操作时要注意栈顶指针的操作。
栈的链式存储结构:采用链式存储,便于多个栈共享存储空间和提高效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行。链栈的操作与链表类似。需注意对于带头结点和不带头结点的链栈之间的区别。
试题精选:
1、3个不同元素依次进栈,能得到( )种不同的出栈序列。
对于n个不同元素进栈,出栈序列的个数为 (1/n+1)Cn2n=(1/n+1)((2n)! / (n!)*(n!)) 本题答案为5。
2、若一个栈的输入序列是P1, P2, P3,...,Pn,其输出序列是1,2,3,...,n。若P3=1,则P1的值( )
A、可能是2 B、一定是2 C、不可能是2 D、不可能是3
由于 P3=1,即P1、P2、P3 连续入栈后,第一个出栈元素是P3,第二个出栈元素是2,而此时P1不是栈顶元素,因此P1的元素不可能是2。
3、设栈的初始状态为空,当字符序列“n 1 _”作为栈的输入时,输出长度为3,且可用做C语言标识符的序列有( )个。
标识符的第一个字符必须是大小写字母或下划线,而不能是数字。则符合规定的标识符有 n1_、n_1、_1n、_n1四种形式。根据 栈的操作特性,_n1这种情况是不可能出现的。
4、元素 a、b、c、d、e依次进入初始为空的栈中,若元素进栈后可停留、可出栈,知道所有元素都出栈,则在所有可能的出栈序列中,以元素d开头的序列个数是( )。
出栈序列必是 d_c_b_a,e的顺序不定,在任意一个“_”上都有可能。
5、设单链表的表头指针为h,结点结构由data和next两个域构成,其中 data 域为字符型。试设计算法判断该链表的前n个字符是否中心对称。例如 xyx、xyyx 都是中心对称。
思路:使用栈来判断表中的数据是否中心对称。将链表的前一半元素依次进栈。在处理链表的后一半元素时,当访问到链表的一个元素后,就从栈中弹出一个元素,两个元素比较,若相等,接着进行下一个元素的比较,一直到链表尾部。若这时栈是空栈,则证明是中心对称。
1 int dc( LinkList ,int n ){ 2 char s[n/2]; 3 p=L->next; 4 for( i=0;i<=n/2;i++ ){ 5 s[i]=p->next; 6 p=p->next; 7 } 8 i--; 9 if( n%2==1 ) 10 p=p->next; 11 while(p!=NULL&&s[i]==p->data){ 12 i--; 13 p=p->next; 14 } 15 if(i==-1) 16 return 1; 17 else 18 return 0; 19 }
6、设有两个栈s1、s2都采用顺序栈方式,并且共享一个存储区[0,...,maxsize-1],为了尽量利用空间,减少溢出的可能,可采用栈顶相向、迎面增长的存储方式。试设计s1、s2有关入栈和出栈的操作算法。
思路:
算法的关键在于两个栈入栈和退栈时栈顶指针的计算。s1栈与通常意义下的栈操作一样,而s2入栈时,其栈顶指针要减1(左移),退栈操作时 要加1 (右移)。
1 #define maxsize 2 typedef struct{ 3 ElemType stack[maxsize]; 4 int top[2]; //2个栈顶指针 5 }stk; 6 stk s;
(1) 入栈操作
1 int push(int i, int x){ 2 //i 是栈号,i=1表示s1栈,i=2表示s2栈。成功返回1,失败返回0 3 if(i<1||i>2){ 4 printf("栈号输入不对,请输入1或2\n"); 5 exit(0); 6 } 7 if(s.top[1]-s.top[0]==1){ 8 printf(" 栈已满\n ") 9 return 0; 10 } 11 switch(i){ 12 case 1: s.stack[++s.top[0]]=x;return 1;break; 13 case 2: s.stack[--s.top[1]]=x;return 1;break; 14 } 15 }
(2)退栈操作
1 int pop(int i){ 2 if(i<1||i>2){ 3 printf("栈号输入不对\n"); 4 exit(0); 5 } 6 switch(i){ 7 case 1: 8 if(s.top[0]==-1){ 9 printf("栈空\n"); 10 return -1; 11 } 12 else 13 return s.stack[s.top[0]--]; 14 calse 2; 15 if(s.top[1]==maxsize){ 16 printf("栈空\n"); 17 return -1; 18 } 19 else 20 return s.stack[s.top[1]++]; 21 } 22 }
7、假设一个算数表达式中包含圆括号、方括号、花括号三种类型的括号,编写一个算法来判别表达式中的括号是否配对,以字符“\0”作为算数表达式的结束符。
括号匹配是栈的一个经典应用,基本思想是扫描每个字符,遇到三种括号的左括号进栈,遇到三种括号的右括号时检查栈顶元素是否为相应的左括号,若是,退栈,否则配对错误。最后栈不为空也为错误。
1 bool check(char *str){ 2 InitStack(S);i=1; 3 Push(S,str[0]); 4 while(str[i]!='\0'){ 5 switch(str[i]){ 6 case '(' : Push(S,'(');break; 7 case '[' : Push(S,'[');break; 8 case '{' : Push(S,'{');break;
9 case ')' : Pop(S,e): 10 if (e!='(') return false; 11 break; 12 case ']' : Pop(S,e): 13 if (e!=']') return false; 14 break; 15 case '}' : Pop(S,e): 16 if (e!='}') return false; 17 break; 18 default; 19 break; 20 } 21 i++ 22 } 23 if (! isEmpty(s)){ 24 printf("括号不匹配\n"); 25 return false; 26 } 27 else{ 28 printf("括号匹配\n"); 29 return true; 30 } 31 }
8、在递归调用的过程中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储,递归次数过多容易造成栈溢出等。而其效率不高时因为递归调用过程中包含过多重复的计算。其优点就在于代码简单,容易理解。
利用一个栈实现以下递归函数的非递归计算:
思路:设置一个栈用于保存n和对应的Pn(x)值,然后边出栈边计算Pn(x),栈空后值就计算出来了。
1 double p(int n, double x){ 2 struct stack{ 3 int no; //保存n 4 double val; //保存Pn(x)的值 5 }st[maxsize] 6 int top=-1,i; 7 double fv1=1,fv2=2*x; //当n=0 和n=1时的初值 8 for(i=n;i>=2;i--){ 9 top++; 10 st[top].no=i 11 } 12 while(top>0){ 13 st[top].val=2*x*fv2-2*(st[top-1].no -1)*fv1; 14 fv1=fv2; 15 fv2=st[top].val 16 top--; 17 } 18 return fv2; 19 }
上面的这种方法 需要栈的长度至少为n,还有一种方法只需要栈的长度为3,st[2]的值是Pn(x)的值,而st[0],st[1]分别存储着Pn-2(x)和Pn-1(x)的值,这三个值会跟随递归运算而发生变化。但是这样只能得到三个结果。不能存储各个对应值。
由于栈的内容比较简单,所以就写这些吧。