栈的定义
首先栈也是线性表,它是一种操作受限的线性表,只允许在一端进行插入删除,允许插入删除的一端叫做栈顶,有一个栈顶指针,对于顺序栈,它指向栈顶元素在数组中位置标号的一个整型变量,对于链栈,它记录栈顶元素所在结点的地址,是动态变化的,另一端为栈底,栈底始终固定不变,有空栈,特点是“后进先出”
栈的数学性质:n个不同元素进栈,出栈元素不同排列个数为(1/n+1)Cn2n,此公式称为卡特兰数
栈的存储结构
顺序栈
利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附带一个指针提示当前栈顶元素
顺序栈的几种状态
栈的顺序存储类型
#define maxsize 50
typedef struct
{
int stack[maxsize];
int top;
}sqstack;
1.初始top=-1
2.进栈操作sqstack[++top];
3.出栈操作sqstack[top–];
4.栈空:top=-1;栈满:top=maxsize-1;栈长:top+1
注:以上4点均是初始栈顶指针top=-1下的情况,若top指针初始不指向-1,则会有所不同
顺序栈基本操作
初始化空栈
void initstack(sqstack &s)
{
s.top = -1;
}
判断栈是否为空
bool stackempty(stack s)
{
if (s.top == -1)
return true;
else
return false;
}
进栈与出栈
bool push(sqstack &s, ElemType x)
{
if (s.top == maxsize - 1)//不要忘记检查当前栈是否已满,否则可能造成“上溢”
return false;
s.data[++s.top] = x;
return true;
}
bool pop(sqstack &s, ElemType &x)//将出栈元素保存在x中
{
if (s.top == - 1)//不要忘记检查当前栈是否为空,否则可能造成“下溢”
return false;
x=s.data[s.top--];
return true;
}
读栈顶元素
bool GetTop(sqstack s, ElemType &x)
{
if (s.top == -1)//不要忘记判断当前是否为空栈
return false;
x = s.data[s.top];//此函数只要求读栈顶元素,并没有出栈,所以不用top--,原栈顶元素依然在栈中
return true
}
销毁栈
顺序栈的实现是基于静态数组的,不用手动销毁,由系统自动销毁
链栈
链栈优点是便于多个栈共享存储空间和提高其效率,不存在栈满上溢情况,通常用单链表实现,结点类型和单链表一模一样,规定所有操作均在表头进行,做题需注意题意是否有头结点
链栈的几种状态
链栈存储类型
typedef struct LNode
{
int data;
struct LNode *next;
}LNode,*PNode;
1.栈空:Lst->next==NULL
2.不存在栈满
3.元素出栈(出栈元素保存到x):s=p->next;x=s->data;p->next=s->next;free(s); //其实就是单链表删除操作
4.元素入栈(指针q指向此元素):q->next=p->next;p->next=q; //其实就是头插法的插入操作
链栈基本操作
初始化空栈
void initstack(LNode *&s)
{
s = (LNode *)malloc(sizeof(LNode));//链表头结点
s->next = NULL;
}
判断栈是否为空
int isempty(LNode *s)
{
if (s->next == NULL)
return 1;
else
return 0;
}
进栈与出栈
void push(LNode *s, int x)
{
lNode *newnode = (lNode *)malloc(sizeof(LNode));
newnode->next = NULL;//好习惯
//以下为头插法
newnode->data = x;
newnode->next = s->next;
s->next = newnode;
}
int pop(LNode *s, int &x)
{
if (s->next == NULL)//当前为空栈,无法出栈
return 0;
lNode *q = NULL;
//以下为单链表的删除操作
q = s->next;//要出栈的结点保存在q中
x = q->data;
s->next = q->next;
free(q);
return 1;
}
读栈顶元素
int ReadTop(LNode *s, int &x)//读栈顶元素,只是读,并没有出栈(删除)
{
if (s->next == NULL)
return 0;
x = s->next->data;
return 1;
}
销毁栈
int Destory(LNode *&s)//这里指的销毁是指所有结点(包括头结点)全部释放,由于头结点也要释放(s的指向要变),所以用&
{
LNode *q = NULL;//接收每一次要释放的结点
if (s == NULL)//当前链栈中无任何结点(包括头结点)
return 1;
if (s->next == NULL)//当前栈中只剩头结点了
{
q = s;
s = NULL;//s重新回到初始化栈之前的状态(只是一个没有指向任何结点的指针)
free(q);//释放头结点
return 1;
}
else//除了头结点还有其余结点
{
q = s->next;
while (q != NULL)//删除除头结点外的所有结点
{
s->next = q->next;
free(q);
q = s->next;//q在指向下一个要被删除的结点
}
//删除头结点
q = s;
s = NULL;
free(q);
return 1;
}
}
共享栈
将两个普通顺序栈栈顶相对放置,最左边为1号栈栈底,初始栈顶指针top1为-1(1号栈为空条件),最右边为2号栈栈底,初始栈顶指针top2为maxsize(2号栈为空条件),随后两个栈顶向共享空间延伸,当top1+1=top2时栈满
栈的经典应用
栈在括号匹配中的应用(非天勤代码,自己的写法(p64)
#include<stdio.h>
#include<string.h>
#define maxsize 50
int main()
{
char Str[maxsize] = {0};//存储用户输入的各种括号
printf("请输入任意“()”和“[]”的组合(最多输入49个字符):\n");
gets_s(Str, maxsize);//等价于fgets(Str, maxsize, stdin);stdin指从键盘输入
void Match(char[]);
Match(Str);
return 0;
}
void Match(char Str[])
{
int i;
char stack[maxsize] = { 0 };
int top = -1;
char ch, dh;
for ( i = 0; i <strlen(Str); i++)
{
ch = Str[i];
if (ch == '{' || ch == '(')
stack[++top] = ch;
else
{
if (top != -1)
{
dh = stack[top--];
if ((ch == '}'&&dh == '(') || (ch == ')'&&dh == '{'))
{
printf("匹配失败\n");
return;
}
}
else//若第一个为右括号
{
printf("匹配失败!\n");
return;
}
}
}
if((top==-1)&&(i==strlen(Str)))//注意限定条件
printf("匹配成功!\n");
}
/*
1.gets_s()函数解释:从键盘读入50个字符存入Str,换行符或者EOF作为结束标志,会将换行转成'\0'存入Str,因此要保证,你键盘输入
的字符(不包括回车)应该为49个,因为还要存储那个由换行变为的'\0',若你执意输入50字符则造成越界
2.注意fgets和gets的区别:前者最多只能读入maxzise-1个字符(包括\n,它跟gets_s不一样,它会读入\n),而最后结束后他会把\n
也读入,然后在末尾专门给你添加\0(gets_s是将\n转为\0),所以它最多只能读入maxsize-1个字符(包括\n),当个数<=maxsize-1
时,数组最终以\n\0结尾,导致用strlen(Str)的值会大1(\n),可以采用Str[strlen(Str)-1]='\0'去掉\n,当个数>maxsize-1时
Str以\0结尾
3.总之记住,用这两个函数输入字符最好都输入maxsize-1个字符(不包括回车),这样可以保证数组以\0结尾
算法思想:开一个栈,将左括号全部入栈(若第一个就为右括号直接匹配失败退出),出现右括号时出栈顶元素,判断二者是否匹配,不匹配则结束,匹配则什么也不做,继续处理下一个字符
最后栈空且全部字符都处理完的情况下匹配成功
*/
前中后缀表达式(p64)
中缀表达式是最常用的算术表达式,运算符在运算数中间,运算需要考虑运算符优先级和括号
后缀表达式是计算机容易运算的表达式,运算符在运算数后面,从左到右进行运算,无需考虑优先级,运算呈线性结构,只有操作数和运算符
前缀表达式自然是运算符在操作数的前面
注:中缀表达式转为前缀或者后缀表达式的结果并不一定唯一,后缀式和前缀式都只有唯一一种运算次序,而中缀式却不一定,正是因为运算次序的不一定,所以说前缀和后缀式是由中缀式的某一种运算次序生成的,所以一个中缀式可能对于多个前缀式或后缀式,例如a+b+c可以先算a+b再算b+c,这样就有两种后缀式与其对应,分别是ab+c+和abc++
构造表达式树
给定一个表达式的中缀形式:(4+1*(5-2))-6/3
首先将每个运算加上括号,区分优先级,得到(4+(1*(5-2)))-(6/3)
括号外的-优先级最低,作为根节点,(4+(1*(5-2)))作为左子树,(6/3)作为右子树;
递归的转换4+(1*(5-2)),+最为根节点,4是左子树,(1*(5-2))是右子树。*是右子树的根节点,1是左子树,(5-2)是右子树。最后计算(5-2),-是根节点,5是左子树,2是右子树。
通过中缀表达式求解前后缀表达式方法(手工)
1. 表达式树与表达式关系
表达式树的先序遍历对应前缀表达式
表达式树的中序遍历对应中缀表达式
表达式树的后序遍历对应后缀表达式
2. 括号法(王道p93)
步骤1:按照运算符优先级对所有运算单位加括号
步骤2:把运算符号移动到对应的括号前面(后面)
步骤3:把括号去掉,对应的前缀(后缀)表达式出现
通过中缀表达式手工求解前后缀表达式方法(代码)
①中缀表达式→后缀表达式
具体转换方式(用一个栈完成)
1.从左到右进行遍历
2.运算数,直接输出.
3.左括号,直接压入堆栈,(括号是最高优先级,无需比较)(入栈后优先级降到最低,确保其他符号正常入栈)
4.右括号,(意味着括号已结束)不断弹出栈顶运算符并输出直到遇到左括号(弹出但不输出)
5.运算符,将该运算符与栈顶运算符进行比较,
如果优先级高于栈顶运算符则压入堆栈(该部分运算还不能进行),
如果优先级低于等于栈顶运算符则将栈顶运算符弹出并输出,然后比较新的栈顶运算符.
(低于弹出意味着前面部分可以运算,先输出的一定是高优先级运算符,等于弹出是因为同等优先级,从左到右运算)
直到优先级高于栈顶运算符或者栈空,再将该运算符入栈.
6.如果对象处理完毕,则按顺序弹出并输出栈中所有运算符.
注:转换后的后缀表达式从左向右计算
#include<stdio.h>
#include<string.h>
#define maxsize 100
#define Not 1000//括号的prior值,此程序规定prior值越大优先级越低
typedef struct elem
{
char ch;//储存当前字符
int prior;//储存当前字符优先级
}element;
int main()
{
void convert(char[],element []);//为用户输入的字符串的每个字符赋予优先级
int convertmain(char[], element[], element[]);//核心函数
char str[maxsize];//储存用户输入的中缀表达式
element str1[maxsize];//储存convert函数转换后的每个字符带有优先级的中缀表达式
element result[maxsize];//储存转换后的后缀表达式,由于转换过程中()都会消失,所以它的最终长度由核心函数返回得到
printf("输入字符串(至多输入99个):\n");//本程序的局限性在于必须保证用户输入的中缀表达式正确,否则会出错
gets_s(str, maxsize);
convert(str,str1);//指向过后str1中就保存了带有每个字符优先级的原中缀表达式
int sum=convertmain(str,str1,result);//sum接收最后result数组的长度,result中存的即为后缀表达式
for (int i = 0; i < sum; i++)
printf("%c", result[i].ch);
putchar('\n');
return 0;
}
int convertmain(char str[], element str1[], element result[])//返回值为result数组元素个数
{
int i, j = 0;
//用一个栈来处理运算符的输出问题
element stack[maxsize];
int top = -1;
for (i = 0; i < strlen(str); i++)//对每个字符依次处理
{
if (str1[i].ch >= 48 && str1[i].ch <= 57)//为操作数则直接入result数组
{
result[j++] = str1[i];
}
else if (str1[i].ch == '(')//为'('直接入栈
stack[++top] = str1[i];
else if (str1[i].ch == ')')//当前处理字符为右括号则逐个出栈元素直至遇到左括号(但左括号不入result数组,采取top--方式跳过它)
{
while (top != -1 && stack[top].ch != '(')//当栈不为空且栈顶元素不是'('时出栈元素并存入result数组
{
result[j++] = stack[top--];
}
if (stack[top].ch == '(')//直到遇到'('停止,但'('不入数组,我认为在用户输入的中缀表达式正确的情况下不存在上一个while循环因为top等于1而退出,是否可以直接top--?
top--;
}
else if (str1[i].prior < stack[top].prior)//当前处理运算符的优先级大于栈顶元素优先级则压栈,这里2级小于1级
{
stack[++top] = str1[i];
}
else if (str1[i].prior >= stack[top].prior)//当前处理的运算符优先级小于等于栈顶元素优先级则一直出栈并入result数组,直到栈空或者优先级大于栈顶元素优先级后入栈
{
while ((str1[i].prior >= stack[top].prior) && top != -1)
{
result[j++] = stack[top--];
}
stack[++top] = str1[i];//上一个while循环退出条件是遇到了当前处理元素优先级大于栈顶元素,或者栈为空,很显然两种情况的下一步均为将此元素入栈
}
}
while (top != -1)//因为入栈的时候是当前处理元素优先级高于栈顶元素优先级才入栈的
//所以可以保证从栈顶到栈底优先级为递减,又因为后缀表达式的运算也是先高后低,所以理所应当将栈中剩余元素挨个入result数组,可以保证优先级高的元素在前面
{
result[j++] = stack[top--];
}
return j;
}
void convert(char str[],element str1[])
{
int i;
for (i = 0; i < strlen(str); i++)
{
if (!(str[i] >= 48 && str[i] <= 57))
{
if ((str[i] == '(') || (str[i] == ')'))
{
str1[i].ch = str[i];
str1[i].prior = Not;//括号优先级不管
}
else if ((str[i] == '-') || (str[i] == '+'))
{
str1[i].ch = str[i];
str1[i].prior = 2;//+-运算符设为2级
}
else if ((str[i] == '*') || (str[i] == '/'))
{
str1[i].ch = str[i];
str1[i].prior = 1;//*/运算符设为1级
}
}
else
{
str1[i].ch = str[i];
str1[i].prior = -1;//操作数不管优先级,它也永远不会入栈,随便设个值即可
}
}
}
/*
小结:
1.sizeof()和strlen()作用不一样,前者参数为一个类型(数组类型、指针类型、用户类型等),返回值是此类型占用的空间的字节大小(注意int a[10]若在主函数
中使用sizeof(a)则返回40,若a在形参位置上,系统会将其认为是一个指针变量而不是数组,所以返回指针变量在内存中所占字节数,也就是4),而后者,若有
const char str="abc",不论在主函数使用strlen(str)还是将str在形参位置,他都会返回3,因为strlen()参数本身就是一个字符串,系统会自动将其认为是一个
字符串进行字符串字符个数的统计,而不是像sizeof()一样,它的参数是一个类型,这就会牵扯到它在主函数可以认得a是数组类型,一旦a做了形参,系统自动将
数组a当做指针来处理,他就只认得a是个指针了
*/
②中缀表达式→前缀表达式
具体转换方式(用两个栈s1和s2完成)
1.从右到左进行遍历
2.运算数,直接压入s2
3.右括号,直接压入s1,(括号是最高优先级,无需比较)(入栈后优先级降到最低,确保其他符号正常入栈)
4.左括号,(意味着括号已结束)不断弹出栈顶运算符并压入s2直到遇到右括号(弹出但不压入s2)
5.运算符,将该运算符与栈顶运算符进行比较,
如果优先级高于或等于栈顶运算符则压入s1(该部分运算还不能进行),
如果优先级低于栈顶运算符则将栈顶运算符弹出并压入s2,然后比较新的栈顶运算符.
(低于弹出意味着前面部分可以运算,先输出的一定是高优先级运算符,等于弹出是因为同等优先级,从左到右运算)
直到优先级高于或等于栈顶运算符或者栈空,再将该运算符入s1.
6.如果对象处理完毕,则将s1剩余元素依次出栈入s2
注:转换后的前缀表达式从右向左计算
注:请格外注意上述①②中的粗体部分
中缀→前缀代码(待定)
表达式求值问题
后缀过程如下:
从左向右顺序扫描表达式每一项,遇到操作数直接入栈,遇到运算符时连续出栈两个元素y和x,然后计算 x运算符y并将结果再次入栈,当表达式所有项处理完后最终留在栈底的即为计算结果
注意上述过程中黑体字的顺序
int convert(char str[])//核心函数
{
int caculate(int, int, char);
int stack[maxsize];
int top = -1;
char ch;//用来接收运算符
int result = 0;//用来接收每次出栈的两个元素的计算结果
for (int i = 0; i < strlen(str); i++)
{
if (str[i] >= 48 && str[i] <= 57)//操作数直接入栈
stack[++top] = str[i]-'0';//注意点
else
{
ch = str[i];
int a1 = stack[top--];
int a2 = stack[top--];
result=caculate(a1,a2, ch);//不要写成result=caculate(stack[top--],stack[top--], ch);因为他会赋予ab相同的值,是同时进行的
stack[++top] = result;
}
}
return stack[top--];
}
int caculate(int b, int a, char ch)//计算表达式的值
{
switch (ch)
{
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
if (b == 0)//除数不能为0
{
printf("请检查后缀表达式是否正确\n");
exit(0);
}
return a / b;
}
}
前缀过程如下:
从右向左顺序扫描表达式每一项,遇到操作数直接入栈,遇到运算符时连续出栈两个元素y和x,然后计算 y运算符x并将结果再次入栈,当表达式所有项处理完后最终留在栈底的即为计算结果
中缀过程看强化专题p57:
不带头结点的链栈的几种基本操作(p66)
void initial(StackList *&head)//不带头节点的链栈初始化
{
head = NULL;
}
int Empty(StackList *head)//不带头节点的链栈判断是否为空
{
if (head == NULL)
return 1;
else
return 0;
}
void push(StackList * head,int x)//不带头节点的链栈进栈
{
StackList *newnode = (StackList*)malloc(sizeof(StackList));
newnode->data = x;
newnode->next = NULL;
//注意以下插入操作的写法,是自己当初没想到的,head指针逻辑上整体从右往左偏移
newnode->next = head;
head = newnode;
}
int pop(StackList *&head,int &x)//不带头节点的链栈出栈,出栈成功返回1,反之返回0
{
StackList *p = NULL;
if (head != NULL)//栈不为空情况下出栈
{
/*x = head->data;
head = head->next;注释的写法是错误的,栈顶结点没被释放内存,不算是出栈,根顺序栈不同,顺序栈只需要变一下指针即可*/
p = head;
//链表的删除节点操作
x = p->data;
head = head->next;
free(p);
return 1;
}
else
return 0;
}
队列的定义
队列依旧是操作受限的线性表,只允许在一端进行插入(队尾),另一端进行删除(队头),“先进先出的特点”(FIFO)
注:栈和队列是操作受限的线性表,并不是所有对线性表的操作都可以作为栈和队列的操作,比如不可以随便读取栈和队列中间的某个数据
队列的存储结构
顺序队
是指分配一块连续的存储单元存放队列中的元素,并附设两个指针front和rear,针对这两个指针的起始指向直接关乎与后面的进队出队操作的写法,是不固定的
顺序队的几种状态
顺序队的结构型
#define Maxsize 50
typedef struct
{
ElemType data[Maxsize];
int front, rear;
}SqQueue;
初始状态(队空条件):Q.front=Q.rear=0
进队操作:队不满时,先送值到队尾元素,再将rear+1
出队操作:队不空时,先取队头元素值,再将front+1
注:以上为最原始的队列的形式,可以发现队空状态下rear=front,那么能否说当Q.rear=Maxsize时队满呢?答案是不能,因为front在不断出队元素,最后即使Q.rear=Maxsize了,也会因为前面front出队过元素导致队列的“假溢出”,因此引出循环队列的概念
循环队列
循环队列才是考试中常用的形式,将普通队列臆想成一个环状可以保证几乎充分利用队列的每个空间,为什么说几乎呢,原因是需要牺牲掉一个存储空间来区别队空和队满的情况,具体看如下解释:
循环队列初始状态:Q.rear=Q.front
进队操作:队尾指针先加1在入队元素,Q.rear=(Q.rear+1)%Maxsize(Maxsize为数组最大存储元素个数)
出队操作:队首指针先加1在出队元素,Q.front=(Q.front+1)%Maxsize(Maxsize为数组最大存储元素个数)
队列长度:(Q.rear-Q.front+Maxsize)%Maxsize
队空条件:Q.rear=Q.front
队满条件:(Q.rear+1)%Maxsize=Q.front
循环队列的基本操作
初始化
void initial(SqQueue &Q)
{
Q.front = Q.rear = 0;
}
判队空
int Empty(SqQueue Q)
{
if (Q.rear == Q.front)
return 1;
else
return 0;
}
入队
int push(SqQueue &Q, ElemType x)
{
if ((Q.rear + 1) % Maxsize == Q.front)//队满报错
return 0;
Q.data[Q.rear] = x;
Q.rear = (Q.rear + 1) % Maxsize;
return 1;
}
出队
int pop(SqQueue &Q, ElemType &x)
{
if (Q.front==Q.rear)//队空报错
return 0;
x = Q.data[Q.front];
Q.front = (Q.front + 1) % Maxsize;
return 1;
}
链队
链队实际上是一个同时带有队头指针和队尾指针的单链表,头指针指向队头结点,尾指针指向队尾结点,即单链表的最后一个节点(注意与顺序存储不同)
链队的几种状态
链队的类型定义
#define Maxsize 50
typedef struct node//链队结点定义
{
int data;
struct node *next;
}LinkNode;
typedef struct//链队类型定义
{
LinkNode *front;
LinkNode *rear;
}LinkQueue;
队空状态:Q.front=Q.rear=NULL,出队前先判断队列是否为空
队满状态:不存在
入队操作、出队操作看下文
注:一般我们使用的链队是带头结点的单链表,因为这样可以统一插入和删除操作
链队的基本操作
初始化
void initial(LinkQueue &Q)
{
Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));//建立头节点
Q.front=NULL;
}
判队空
bool Empty(LinkQueue Q)
{
if (Q.front == NULL && Q.rear == NULL)//写成Q.front==Q.rear也可以
return true;
else
return false;
}
入队
void push(LinkQueue &Q, int x)
{
LinkNode *newnode= (LinkNode*)malloc(sizeof(LinkNode));
newnode->next = NULL;
newnode->data = x;
Q.rear->next = newnode;
Q.rear = newnode;
}
出队
int pop(LinkQueue &Q, int &x)
{
if (Q.rear == Q.front)//队空
return 0;
LinkNode *p = NULL;
p = Q.front->next;
x = p->data;
Q.front->next = p->next;
if (Q.rear == p)//处理原队列中只有一个元素的情况
Q.rear = Q.front;
free(p);
return 1;
}
双端队列
允许两端都可以进行入队和出队操作的队列,元素逻辑结构仍为线性结构,具体有以下三种形式
队列的经典应用
利用两个栈模拟队列
/*
假设:
push(ST,x):元素x入ST栈
pop(ST,&x):ST栈顶元素出栈赋给变量x
isEmpty(ST):判断ST栈是否为空
实现enQueue(元素入队列)、deQueue(元素出队列)、isQueueEmpty(判断队列是否为空,是返回1,否则返回0)
*/
#include<stdio.h>
#define maxsize 50
typedef struct
{
int top;
int stack[maxsize];
}sqstack;
int enQueue(sqstack &s1, sqstack&s2, int x)//s1栈模拟入队,s2栈模拟出队
{
int y;
if (s1.top == maxsize - 1)//证明s1栈满了,去看看s2是否为空
{
if (!isEmpty(s2))//s2不为空,则s1无法再入栈,意味着入队失败,返回0
return 0;
else //若s2为空,则先将s1所有元素退栈后入栈s2(入栈后的s2满足队列先进先出的特点),再将x压入s1
{
while (isEmpty(s1))
{
pop(s1, y);
push(s2, y);
}
push(s1, x);//实现了元素的入队,返回1
return 1;
}
}
else
{
push(s1, x);
return 1;
}
}
int deQueue(sqstack &s1, sqstack&s2, int &x)
{
int y;
if (!isEmpty(s2))//如果当前s2栈不为空,则出栈元素即可,这些元素已经满足了队列先进先出的规则
{
pop(s2, x);
return 1;
}
else //若当前s2栈为空
{
if (!isEmpty(s1))//但s1不为空
{
while (!isEmpty(s1))
{
pop(s1, y);
push(s2, y);
}
pop(s2, x);
return 1;
}
else//s1和s2都为空证明队列中无元素了
{
return 0;
}
}
}
int isQueueEmpty(sqstack s1, sqstack s2)//只有当s1栈和s2栈都为空时才证明队列为空
{
if (isEmpty(s1) && isEmpty(s2))
return 1;
else
return 0;
}