栈:
栈是限定仅在表尾进行插入和删除操作的线性表。遵循先进后出的原则。允许插入、删除的一端为栈顶top;另一端为栈底bottom。
栈,首先它是一个线性表,栈元素具有线性关系,即前驱后继关系。只不过它是一种特殊的线性表。特殊之处在于限制了这个线性表的插入和删除位置,它始终都是在栈顶进行。栈底是固定的,最先进入的只能在栈底。
1.栈的顺序存储结构:
对于栈只能一头插入、删除操作的线性表来说,用数组的下标为0的一端作为栈底,定义一个top指示栈顶元素在数组中位置,如果申请的数组长度为SIZE,则栈顶的位置必须小于SIZE,当栈中存在一个元素时,top = 0。栈满时,top = SIZE - 1;
定义栈的结构体:
1 typedef int datatype; 2 typedef struct 3 { 4 datatype data[SIZE]; 5 int top; 6 }sqstack;
栈的入栈操作:
1 int push(sqstack *s, datatype x) 2 { 3 if (s->top == SIZE - 1) 4 { 5 printf("stack full\n"); 6 return -1; 7 } 8 s->top++; 9 s->data[s->top] = x; 10 return 0; 11 }
栈的出栈操作:
1 int pop(sqstack *s) 2 { 3 int ret; 4 if (s->top == -1) 5 { 6 printf("stack empty\n"); 7 return -1; 8 } 9 ret = s->data[s->top]; 10 s->top--; 11 return 0; 12 }
2.两个栈共享空间
栈的顺序存储操作起来还是很方便的,毕竟它只需操作栈顶top进出元素,因此,也就不存在插入删除时需要移动大量元素的问题。但是它还是存在一个缺陷的,就是你必须先确定好数组存储空间的大小,如果不够用,还得通过软件编程去扩充数组的大小,这也带来了麻烦。
如果有两个相同类型的栈,我们各自为其开辟了空间,有可能是其中一个栈满了,而另一个栈还有很多的存储空间,想想,我们可以通过构建一个数组来存储这两个栈,其中一个栈的栈底在数组的下标0的位置,另一个栈的栈底在数组的下标为SIZE - 1的位置,这样当两个栈都进行入栈操作时,数据元素都向数组中间延伸。这是我们必须判断什么时候数组满和空的。我们假定其中一个栈的栈顶为top1,另一个为top2,当栈1为空时,top1 = -1,栈2为空时top2 = SIZE。栈满的情况①若栈1为空,即top1 = -1,栈2的top2 = 0时,数组满;②当栈2为空,即top2 = SIZE,栈1的top1 = SIZE - 1时,数组满。③当栈1和栈2都向入栈,最终数组满的情况是:top1 + 1 == top2,即两个栈的栈顶top相差1时,栈满。
定义两个栈共享空间结构:
1 typedef int datatype; 2 typedef struct 3 { 4 datatype data[SIZE]; 5 int top1; 6 int top2; 7 }sqstack;
创建操作:
1 void CreateStack(sqstack *s) 2 { 3 s->top1 = -1; //栈1为空 4 s->top2 = SIZE; //栈2为空 5 return ; 6 }
入栈操作:思路是判断是栈1还是栈2要入栈,如是栈1则先将栈1的top1+1后,将元素入栈;若是栈2入栈的话,则先将栈2的top2-1,在将元素入栈。
1 int push(sqstack *s, datatype x, int number) 2 { 3 if (s->top1 + 1 == s->top2) 4 { 5 printf("stack full\n"); 6 return -1; 7 } 8 switch (number) 9 { 10 case 1: 11 s->top1++; 12 s->data[s->top] = x; 13 break; 14 case 2: 15 s->top2--; 16 s->data[s->top] = x; 17 break; 18 default: 19 break; 20 } 21 return 0; 22 }
出栈操作:主要思路是判断要操作的栈是否为空,如果不为空,将其栈顶元素出栈,最后将栈1的栈顶top1-1,栈2的栈顶top2+1.
1 int pop(sqstack *s, datatype *x, int number) 2 { 3 switch (number) 4 { 5 case 1: 6 if (s->top1 == -1) 7 return -1; 8 *x = s->data[s->top1]; 9 s->top1--; 10 break; 11 case 2: 12 if (s->top2 == SIZE) 13 return -1; 14 *x = s->data[s->top2]; 15 s->top2++; 16 break; 17 default: 18 break; 19 } 20 return 0; 21 }
3.栈的链式存储结构
栈的链式存储结构也称链栈。对于链栈来说,基本上不存在栈满的情况。链栈为空时top = NULL;
链栈的结点和栈类型定义:
其中栈结点的类型,包含一个数据域和一个指针域,指针域用于保存下一结点的地址。而栈类型的结构定义包含一个栈结点类型的指针top和栈的元素个数number。
1 typedef int datatype; 2 typedef struct stacknode 3 { 4 datatype data; 5 struct stacknode *next; 6 }stacknode, *stackptr; 7 8 typedef struct linkstack 9 { 10 stackptr top; 11 int number; 12 }linkstack;
创建一个链栈:
申请一个内存地址,链栈为空时s->top = NULL,元素个数number = 0.返回一个栈类型的指针。
1 linkstack *CreateLinkStack() 2 { 3 linkstack *s = (linkstack *)malloc(sizeof(linkstack)); 4 if (s != NULL) 5 { 6 s->top = NULL; 7 s->number = 0; 8 } 9 return s; 10 }
链栈的入栈操作:入栈操作,①要申请一个新的结点sp(sp中包含了数据域data和指针域next),②将要入栈的元素的值赋值给sp->data,即放入新结点的数据域中。③将当前栈顶元素赋值给新结点的直接后继ps->next = s->top。④将新结点ps赋值给栈顶指针s->top = ps。⑤栈元素个数number+1.
1 int push(linkstack *s, datatyped e) 2 { 3 stackptr sp = (stackptr)malloc(sizeof(stacknode)); 4 if (sp == NULL) 5 return -1; 6 sp->data = e; 7 ps->next = s->top; 8 s->top = ps; 9 s->number++; 10 return 0; 11 }
链栈的出栈操作:①先判断链栈是否为空栈,s->top = NULL 时为空。②保存出栈值*e = s->top->data。③将栈顶结点赋值给ps。④使栈顶指针向下移动一位,指向后一结点s->top = s->top->next;⑤释放掉ps。⑥栈元素个数number-1.
1 int pop(linkstack *s, datatype *e) 2 { 3 stackptr sp; 4 if (s->top == NULL) 5 return -1; 6 *e = s->top->data; 7 ps = s->top; 8 s->top = s->top->next; 9 free(sp); 10 s->number--; 11 return 0; 12 }
返回栈顶元素:链栈不为空的条件下,返回栈顶元素。
1 int GetTop(linkstack *s, datatype *e) 2 { 3 if (s->top == NULL && s->number == 0 && e == NULL) 4 return -1; 5 *e = s->top->data; 6 return 0; 7 }
比较下,栈的链式存储和顺序存储,它们的时间复杂度都是O(1),从空间上考虑,顺序栈必须实现分配一个固定的长度,可能存在内存浪费的问题,但是顺序栈存取十分方便;链栈则要求每个元素都有指针域和数据域,这样也增加了内存的开销,但是链栈不受长度的限制。所以呢,在选择的时候,就要依实际情况来决定,如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好用链栈,反之,如果它的变化在可控制范围内,建议使用顺序栈会更好。
2013-1-26 15:58