数据结构与算法&&线性表的变异体——栈与队列
本文由个人笔记整理得出,材料主要来自《大话数据结构》和网络各博主
栈的定义:限定仅在表尾进行插入和删除操作的线性表。
栈的插入操作叫作进栈,也称压栈、入栈。
栈的删除操作,也叫出栈,弹栈。
栈底:下标为0的一端作为栈底。
ADT栈(stack) Data Operation InitStack(*S):初始化操作,建立一个空栈S; DestroyStack(*S):若栈存在,则销毁它; ClearStack(*S):清空栈 StackEmpty(S):若栈为空,返回ture,否则返回false GetTop(S,*e):若栈存在,用e返回S的栈顶元素 Push(*S,e):若栈存在,插入新的元素e到栈S中并成为栈顶元素 Pop(*S,*e):删除栈S中的栈顶元素,并用e返回其值 StackLength(S):返回栈S的元素个数 endADT
栈的结构定义:
typedef int SElemType; typedef struct { SElemType data[MAXSIZE];//定义数组 int top;//设置栈顶 }SqStack; Status Push(SqStack *S,SElemType)//进栈 { if(S->top == MAXSIZE-1)//栈已满 return ERROR; S->top++; S->data[S->top] = e; return OK; } Status Pop(SqStack *S,SElemType *e)//出栈 { if(S->top == -1) return ERROR; e= S->data[S->top]; S->top--; return OK; } typedef strcut//定义两个栈,占用同一个数组;由两边向中间靠拢 { SElemType data[MAXSIZE]; int top1; int top2; }SqDoubleStack; Status Push(SaDoubleStack *S,SElemType e,int stackNumber)//进栈,先判断要进哪一个栈;然后再进 { if(S->top+1==S->top2)//栈满 return ERROR; if(stackNumber==1)//判断是栈1 S->data[++S->top1] = e; else if(stackNumber == 2)//判断是栈2 S->data[--S->top2] = e; return OK; } Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber) { if(stackNumber == 1) { if(S->top == -1) return ERROR; *e = S->data[S->top1--]; else if(stackNumber == 2) { if(S->top2 == MAXSIZE) return ERROR; *e = S->data[S->top2++]; } return OK; } }
栈的链式存储结构及实现
上篇文章我们可以知道,线性表有顺序表和链表;那么对于栈也是一样的;上面介绍的栈只有数组,没有指针,属于顺序栈。一般情况下,栈只是用来做插入和删除操作,但是上面我们谈到的栈是有空间限制的;对比于上篇文章中写到的线性表,那么我们应该就不难理解链栈存在的意义了:最大的好处就是没有空间的限制,通过指针指向将结点像一个链子一样把结点链接。
先来个定义: typdef struct StackNode { SElemType data;//数据 struct StackNode *next;//指针 }StackNode,*LinkStackPtr; typdef struct LinkStack { LinkStackPtr top;//栈顶 int count; }LinkStack; Stauct Push(LinkStack *S,SElemType e)//插入元素e为新的栈顶元素 { LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));//创建新结点 s->data = e;//数据 s->next = s->top;//把当前的栈顶数据赋值给新结点的后继 s->top = s;//新结点s赋值给栈顶指针 s->count++; return OK; } Stauct Pop(LinkStack *S,SElemType *e)//若栈不空,则删除S的栈顶元素,用e返回其值; { LinkStackPtr p; if(StackEmpty(*S)) return ERROR; *e = S->top->data;//数据 p = S->top;//把当前的栈顶数据赋值给新结点的后继 S->top = S->top->next;//新结点s赋值给栈顶指针 S->count++; return OK; }
其实我们很容易就可以看出,栈的时间复杂度为O(1);对比顺序栈和链栈,顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费的问题,但它的优势是存取定位很方便,而链栈则要求每一个元素都有指针域,坏处是增加了一些内存的开销,但对于栈的长度,我们就可以不跟顺序栈一样死板定义了。
总结一下,从上篇文章我们知道,线性表的插入和删除是可以在表中、表头和表尾插入的;但栈不一样,只能删除和加入元素只能栈顶上操作;这个的本质意义上是简化了程序设计的问题,将数据的处理划分了不同的关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题。
补充:
递归(函数)
所谓递归就是一个直接或间接调用自己的函数。但是写递归最怕的就是陷入永不结束的无穷递归中,所以,每一个递归至少有一个条件,就是当满足条件再进行时,不再引用自身而是利用返回值退出。
如:
int Fbi(int i) { if(i<2) return i == 0?0:1;//问号运算符的标准格式:表达式1?表达式2:表达式3 若表达式1为真,则执行表达式2,为假,则执行表达式3 对于本题,若i=0,则返回值0,否则返回值1 return Fbi(i-1)-Fbi(i-2); } int main() { int i; for(int i = 0;i<40;i++) printf("%d",Fbi(i)); return 0; }
队列定义:值允许在一端进行插入,而在另外一端进行删除操作的线性表。
队列,其实就跟排队一样,讲究先来后到,先进先出,简称FIFO。允许插入的一端成为队尾,允许删除的一端称为队头。
实际应用典例:电脑键盘的字母输入
ADT Queue Data Operation InitQueue(*Q):建立一个空队列 DestroyQueue(*Q):如果队列存在,则销毁它 ClearQueue(*Q):清空队列 QueueEmpty(Q):判断队列是否为空,若为空,则返回true;否则返回false GetHead(Q,*e):若队列Q存在且位非空,用e返回队列Q的队头元素 EnQueue(*Q,e):删除队列Q中队头元素,并用e返回其值 QueueLength(Q):返回队列Q的元素的个数 endADT
循环队列:队列中头尾相接的顺序存储结构。
typedef int QElemType; typedef struct { QElemType data[MAXSIZE]; int front;//头 int rear;//尾 }SqQueue; Status InitQueue()//初始化一个队列 { Q->front; Q->rear = 0; return OK; } int QueueLength(SqQueue Q)//求队列的长度 { return (Q.rear-Q.front+MAXSIZE)%MAXSIZE; } Status EnQueue(SqQueue *Q,QElemType e)//若队列未满,则插入元素e为Q的新的队尾元素 { if((Q-rear+1)%MAXSIZE== Q->front)//已满 return ERROR; Q->data[Q->rear] = e;//把元素e赋值给队尾 Q->rear = (Q->rear+1)%MAXSIZE;//rear向后移一位置 return OK; } Status DeQueue(SqQueue *Q,QElemType *e)//若队列不空,则删除元素e,用e返回其值 { if(Q->front == Q->rear) return ERROR; *e = Q-data[Q-front]; Q-front = (Q-front+1)%MAXSIZE; return OK; }
队列的链式存储结构及实现
写到队列这里其实我们都不用讲了,链式?呵呵,不就是在结点再加指针吗?不就是方便删除和插入吗?这谁不会?对吧
typedef int QElemType; typedef struct QNode { QElemType data;//数据 struct QNode *next;//指针 }QNode,*QueuePtr; typedef struct { QueuePtr front,rear;//队头、队尾指针 }LinkQueue; Status EnQueue(LinkQueue *Q,QElemtype e)//插入元素e为Q的新的队列元素 { QueuePtr s = (QueuePtr)malloc(sizeof(QNode)); if(!s)//保险一点,加了防止分配内存不成功 { exit(0); } s->data = e; s-next = NULL; Q->rear->next = s;//把拥有元素e新结点s赋值给原队尾结点的后继,就是让队尾的后继指向s咯 Q-rear = s;//当前的s设置为队尾结点,rear指向s return OK; } Status DeQueue(LinkQueue *Q,QElemtype *e)//删除结点 { QueuePtr p; if(Q->front == Q->rear) return ERROR; p = Q-front->next;//把欲删除的队头结点暂存给p, *e = p->data;//把欲删除的队友结点的值赋值给e Q->front->next = p-next;//把原队头结点后继p->next赋值给头结点后继 if(Q-rear == p)//判断改链表是否只剩下一个元素,是的话则将rear指向头结点 Q-rear = 1cca8 Q-front; free(p); return OK; }
再总结一下:
栈:仅限定在表尾插入和删除操作的线性表;
队列:只允许在一端进行插入操作,而在另一端进行删除操作的线性表;
对于循环队列,相对于队列最大好处就是队头和队尾可以在数组中循环变化,解决了移动数据的时间损耗。使得本来的时间复杂度从O(n)变为了O(1)。
- 数据结构与算法专题之线性表——队列及其应用
- 数据结构与算法 ---- 线性表 及Java实现 顺序表、链表、栈、队列
- 3. C#数据结构与算法 -- 线性结构(线性表,栈,队列,循环队列)
- 数据结构与算法 ---- 线性表 及Java实现 顺序表、链表、栈、队列
- 【数据结构】数据结构总结之线性表、栈和队列
- 数据结构与算法 01 线性表的顺序存储结构
- 数据结构与算法(线性表_静态链表)
- 数据结构——线性结构(7)——链队列的实现
- 线性队列
- 数据结构与算法 2:双向链表,栈,队列
- 13. C#数据结构与算法 -- 线性结构
- 【算法和数据结构】_4_线性结构_队列
- 《数据结构与算法》课程笔记 第二章 2.1 线性表
- 数据结构全攻略--线性结构不攻自破之栈和队列
- 数据结构与算法-队列
- Stack栈类与、Queue队列与线性表的区别和联系
- 数据结构之线性表、栈、队列
- Java数据结构与算法之顺序表和链表实现栈和队列
- 数据结构——线性结构(6)——链队列的原理
- C#数据结构与算法揭秘二 线性结构