栈和队列其实是个抽象的数据类型。
对于他们的实现,既可以用数组,也可以用链表实现。
对于栈和队列,看书的时候可能仅仅只是了解的一个概念。
因为要在程序中真正运用上去,个人觉得又是一个高度的思想境界。
我只是看了书,了解基本概念。
一。栈
定义
栈是仅限在表尾进行插入和删除的操作线性表。
我们把允许插入和删除数据的一段称之为栈顶(top)。另一端称之为栈底(bottom)
不含任何数据元素的栈称为空栈。LIFO后进先出表。
1.栈的数组结构实现,也就是栈顺序存储结构定义。
typedef struct
{
ElemType data[MAX_SIZE];
int top;
}SqStack;
栈的常见操作就是进栈push好出栈pop操作
Status push(SqStack * s, ElemType e)
{
if(s->top == MAX_SIZE-1);//栈已经满了
return error;
//入栈
s->top++;
s->data[s->top] = e;
return ok;
}
Status pop(SqStack * s, ElemType *e){
if(s->top ==-1)
return error;
*e = s->data[s->top];
s->top--;
return ok;
}
对于栈的顺序结构实现,是有很多的局限性的。
之后有又提出一个概念是 共享栈。
两个栈共享空间。
栈的顺序存储结构就和线性表的顺序存储一样,同样存在对开辟的存储空间无法很好的确定。
如果此时有两个相同类类型的栈。他们各自用一个数组空间。此时极有可能其中一个栈想插入元素却要溢出了。
而另外一个栈还留有很多存储空间。此时可以稍微 改进下,让两个栈共享一个数组空间。
我们将数组的始端作为第一个栈的栈底,数组的末端最为另一个栈的栈底。两个栈都是像中间延伸。
Status Push(SqStack * s, ElemType e, int stackNumber)
{
if(s->top1+1 == s->top2);
return error;
if(stackNumber == 1)
{
s->data[++s->top1] = e;
}
else if(stackNumber == 2)
{
s->data[--s->top2] = e;
}
return ok;
}
2.栈的链式实现。
typedef struct StackNode
{
ElemType data;
struct StackNode *next;
}StackNode *LinkStackhead
typedef struct LinkStack
{
LinkStackhead top;//栈顶指针
int count;//元素个数
}
进栈和出栈操作
Status push(LinkStack * ls,ElemType e)
{
LinkStackhead l = (LinkStack)malloc(sizeof(StackNode));//开辟一块存储空间
l->data = e;//赋值
l->next = ls->top;
ls->top = l;
ls->count++;
return ok;
}
Status pop(LonkStack * ls ,ElemType *e)
{
LinkStakchead h;
if(ls->count == -1)
return error;
//*e = ls->top;
h = ls->top
ls->top = p->next;
free(h)
ls->count--;
return ok;
}
栈的应用。
在现实中,先进后出的式的栈应用:比如文件浏览工具就是一个典型。
其次比如我们程序的执行顺序也是可以理解为一个栈的形式 。 A 方法中调用了B,B中调用了C。
那么要执行完这端代码,我们先按调用的顺序将A,B,C依次压入栈中,然后C B A依次pop出执行操作。
因此我们可以联想到一个最常见的应用场景,就是递归。
递归中有个必定听到过的的内容,就是Fibonacci数列。
斐波那契的递归函数。
int Fbi(int i){
if(i<2)
return i == 0?0:1
return Fbi(i-1)+Fbi(i-2);
}
即,我要取得该数列中的第i个数,必须先知道位于第 i-2和i-1这两个数,
于是就产生一个递归的算法。
在递归中要注意的算法必须有跳出的条件。
从代码可以看出,递归用到的是选择结构。
显然递归代码很优雅,但是优雅是要有代价的,他会不断调用和建立函数副本,耗费大量的时间和内存。
对于斐波那契数列求第i相的算法有一种改进的做法,
因为这边我们假设计算 i项的数,
那么要先求 i-1和i-2项的数,
求i-1项的数则又需要求i-2和i-3项数;
到这边,我们就看出来了,在求i项时,我们去计算了 i-2项,在求i-1项时,我们有去求了i-2项。
也就是说,其实在计算第i项数,不停的递归时,很多计算都是重复的。
那么觉得可以考虑是否把每项的数在一次计算后就存起来,后面被调用到时在先去查找该项是否已经计算,有的话就直接取出来,不存在那就计算咯
private Map numsMap = new HashMap<Integer,Integer>();
public int Fbi(int i){
int x,y;
if(i<2)
return i == 0?0:1;
if(numsMap.get(Integer.valueOf(i-1)) !=null){
x = (Integer) numsMap.get(Integer.valueOf(i-1));
}else{
x=Fbi(i-1);
}
if(numsMap.get(Integer.valueOf(i-2)) !=null){
y = (Integer) numsMap.get(Integer.valueOf(i-2));
}else{
y=Fbi(i-2);
}
return x+y;
}
抱歉我的java和C的混写。因为C的话语法不是太熟,为了测试,熟练度java写起来比较快。
这也算是空间换时间的一种做法。
栈另外一个重要的的应用:四则运算表达式求值。
1.后缀表示法,(逆波兰):简单说就是遇到数字就进栈,遇到符号就栈顶两个数字出栈。
2.中缀表达式也就是我们平常书写的四则运算表达式。
后缀表达式虽简单,但是怎么把一个中缀表达式转成一个后缀呢?
同样的,从左往右遍历,若是数字就输出,若是符号,判断其于栈顶符号的优先级,若是右括号或是优先级低于栈内元素的,
则栈顶元素依次出栈并输出,将当前符号进栈。
二:队列
定义
队列是只允许在一端进行插入操作,在另一端进行删除操作。FIFO线性表。
1.队列顺序存储结构
相对于栈在插入和删除上操作时间为O(1),在队列插入元素只是在数组末端追加一个元素,时间也为O(1),
但是如果是出列一个元素,此时后面的元素都需要往前面移动一个位置,时间就是O(N)。
因此提出了队头队尾指针。front指向队头元素,rear指向队尾元素的下一个位置。
但此时还是会出现一个问题,就是不停的出队入队,front和rear一直在往后移动,前面的数组单元空着,但是指针已移动到数组末尾,
此时造成一个假溢出的现象。
于是又提出一个改进思路,循环队列。
队列头尾相接的顺序存储结构称之为循环队列。
此时当在数组最后位置插入元素后,rear指针移动到数组的0的位置,既形成一个循环。
但是又会出现一个问题,在我继续插入元素,rear指针往后移动,就会有rear等于front的时候。上面我们说这个情况的队列是空队列出现矛盾,既我们无法判断当rear等于front时,队列到底是满呢,还是空的?
方法一:设置一个标志位Tag,rear还没有循环到数组0时,tag为0,循环到tag设置为1.
方法二:当队列空时rear = front,,当队列满时,我们保留一个元素空间
计算队列的长度通用公式:(rear -front +QueueSize)%QueueSize;
队列满的判断 (rear -front +QueueSize)%QueueSize ==front;
循环队列的顺序结构定义
typedef struct{
ElemType data[MAX_SIZE];
int front;
int rear;
}SqQueue;
循环队列的入队出队操作
Status EnQueue(SqQueue *Q,ElemType e){
if((Q->rear -Q->front +MAXSZIE)%MAXSIZE ==Q->front)
return error;
Q->data[Q->rear] = e;
Q->rear = (Q->rear+1)%MAXSZIE;
return ok;
}
Status DeQueue(Sqqueue *Q,ElemType *e){
if(Q->rear == Q->front)
rerurn error;
*e = Q- >data[Q->front];
Q->front = (Q->front+1)%MAXSIZE;
return ok;
}
2.队列的链式存储实现
链队列的结构
typedef int Elemtype ;
typedef struct QNode{
ElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct{//队列的链表结构
Queueptr front,rear;
}LinkQueue;
Status EnQueue(LinkQueue * Q , ElementType e)
{
Queueptr p = (Queueptr) malloc(sizeof(QNode));
if(!s)//分配失败;
exit(OVERFLOW);
p->data = e ;
P->next = NULL;
Q->rear->next = p ;//在队尾插入
Q->rear = s ;//将p作为队尾节点
return ok ;
}
Status DeQueue(LinkQueue * Q , ElementType *e)
{
Queueptr p ;
if( Q-> front == Q-> rear) //空队列
return error;
p = Q->front->next;//保存要删除的元素
*e = p->data;
Q->front->next = p->next;
if(Q->rear == p)//若队头是队尾,删除后将队尾指向队头
Q->rear = Q->front;
free(p);
eturn ok;
}