结构之美学习二《栈和队列》
2012-03-26 14:57
127 查看
栈和队列其实是个抽象的数据类型。
对于他们的实现,既可以用数组,也可以用链表实现。
对于栈和队列,看书的时候可能仅仅只是了解的一个概念。
因为要在程序中真正运用上去,个人觉得又是一个高度的思想境界。
我只是看了书,了解基本概念。
一。栈
定义
栈是仅限在表尾进行插入和删除的操作线性表。
我们把允许插入和删除数据的一段称之为栈顶(top)。另一端称之为栈底(bottom)
不含任何数据元素的栈称为空栈。LIFO后进先出表。
1.栈的数组结构实现,也就是栈顺序存储结构定义。
栈的常见操作就是进栈push好出栈pop操作
对于栈的顺序结构实现,是有很多的局限性的。
之后有又提出一个概念是 共享栈。
两个栈共享空间。
栈的顺序存储结构就和线性表的顺序存储一样,同样存在对开辟的存储空间无法很好的确定。
如果此时有两个相同类类型的栈。他们各自用一个数组空间。此时极有可能其中一个栈想插入元素却要溢出了。
而另外一个栈还留有很多存储空间。此时可以稍微 改进下,让两个栈共享一个数组空间。
我们将数组的始端作为第一个栈的栈底,数组的末端最为另一个栈的栈底。两个栈都是像中间延伸。
2.栈的链式实现。
进栈和出栈操作
栈的应用。
在现实中,先进后出的式的栈应用:比如文件浏览工具就是一个典型。
其次比如我们程序的执行顺序也是可以理解为一个栈的形式 。 A 方法中调用了B,B中调用了C。
那么要执行完这端代码,我们先按调用的顺序将A,B,C依次压入栈中,然后C B A依次pop出执行操作。
因此我们可以联想到一个最常见的应用场景,就是递归。
递归中有个必定听到过的的内容,就是Fibonacci数列。
斐波那契的递归函数。
于是就产生一个递归的算法。
在递归中要注意的算法必须有跳出的条件。
从代码可以看出,递归用到的是选择结构。
显然递归代码很优雅,但是优雅是要有代价的,他会不断调用和建立函数副本,耗费大量的时间和内存。
对于斐波那契数列求第i相的算法有一种改进的做法,
因为这边我们假设计算 i项的数,
那么要先求 i-1和i-2项的数,
求i-1项的数则又需要求i-2和i-3项数;
到这边,我们就看出来了,在求i项时,我们去计算了 i-2项,在求i-1项时,我们有去求了i-2项。
也就是说,其实在计算第i项数,不停的递归时,很多计算都是重复的。
那么觉得可以考虑是否把每项的数在一次计算后就存起来,后面被调用到时在先去查找该项是否已经计算,有的话就直接取出来,不存在那就计算咯
抱歉我的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;
循环队列的顺序结构定义
循环队列的入队出队操作
2.队列的链式存储实现
链式队列的插入和删除
对于他们的实现,既可以用数组,也可以用链表实现。
对于栈和队列,看书的时候可能仅仅只是了解的一个概念。
因为要在程序中真正运用上去,个人觉得又是一个高度的思想境界。
我只是看了书,了解基本概念。
一。栈
定义
栈是仅限在表尾进行插入和删除的操作线性表。
我们把允许插入和删除数据的一段称之为栈顶(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; }
相关文章推荐
- (三)Android数据结构学习之队列
- [学习笔记]循环队列和队列的链式结构
- 学习队列结构
- 学习java数据结构基础知识之队列
- 非递归学习树结构(一)--栈和队列的实现
- 慕课网学习笔记之数据结构队列(C++)
- 学习笔记:数据结构(二)栈与队列
- 数据结构 学习笔记(三):线性结构:堆栈,队列,表达式求值,多项式加法运算
- 【嵌入式学习历程10】数据结构之队列
- 学习笔记--数据结构(之二)队列
- 数据结构——链队列学习
- 【数据结构与算法学习笔记】PART3 线性结构(除向量外,数组、栈、队列、链表)
- 【学习笔记----数据结构05-栈与队列】
- [算法学习笔记]数据结构之栈和队列
- 网络设备发送队列相关数据结构及其创建函数 (linux网络子系统学习 第十节 )
- 信管16数据结构:第三章栈和队列的课前翻转学习任务
- 数据结构与算法学习之队列及队列的相关操作
- Java之数据结构基础、线性表、栈和队列、数组和字符串,树—学习笔记
- 4Java学习笔记之数据结构——队列
- 数据结构与算法学习笔记——队列