栈是限定仅在表尾进行插入插入和删除操作的线性表; 队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
栈是限定仅在表尾进行插入插入和删除操作的线性表。 允许插入和删除的一端称为栈顶(top),另一端称为栈底(button)。不含任何数据元素的栈称为空栈,栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。 栈是一个特殊的线性表,特殊之处在于限制了插入和删除的位置,它始终只在栈顶进行,这也就使得:栈底是固定的,最先进栈的只能在栈底。 栈的插入操作:叫做进栈,也称压栈、入栈;删除操作:叫做出栈,也叫做弹栈。
最先进栈的元素,不一定是最后出栈。在不是所有元素都进栈的情况下,事先进去的元素也可以出栈,只要保证是栈顶元素出栈就可以了。
简称为顺序栈。数组实现,下标为0的一端作为栈底。 栈的结构定义
#define MAXSIZE 20; typedef int SElemType; typedef struct{ SElemType data[MAXSIZE]; int top; //用于栈顶指针; }SqStack;两者的时间复杂度都是O(1),由于不存在线性表的插入和删除需要移动元素问题, 栈的顺序存储结构还是很方便的。但是有一个很大的缺陷:必须事先确定数组存储空间的大小。
两个相同类型的栈,可以最大限度第利用其事先开辟的存储空间来进行操作。关键思路:它们是在数组的两端,向中间靠拢。 空间结构代码:
//两栈共享空间结构 typedef struct { SElemType data[MAXSIZE]; int top1; //栈1的栈顶指针 int top2; //栈2的栈顶指针 }SqDoubleStack; //插入元素e为新的栈顶元素 Status Push(SqDoubleStack *S, SElemType 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; } //若栈不空,则删除S的栈顶元素,用e返回其值,并返回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; }两栈共享空间,适用于两个栈的空间需求具有相反关系时,即一个栈增长时另一个栈在缩短的情况。
简称为链栈。栈顶放在单链表的头部,对于链栈来说,不需要头指针的,用栈顶代替。 链栈的结构
//链栈的结构代码如下: typedef struct StackNode { SElemType data; struct StackNode * next; }StackNode, *LinkStackPtr; typedef struct LinkStack { LinkStackPtr top; int count; }LinkStack;时间复杂度都为O(1)。 对比顺序栈和链栈:
时间复杂度都为O(1);顺序栈需要事先确定一个固定的长度,存在空间浪费,但优势是存取定位很方便。链栈则要求每个元素都有指针,增加了内存开销,但对于栈的长度无限制如果栈的使用过程中元素变化不可预料,最后有链栈;反之,它的变化在可控范围内,建议使用顺序栈。栈的引入简化了程序设计的问题,划分了不同的关注层次,使得思考范围缩小,更加聚焦与我们要解决的问题核心。
f(n)=⎧⎩⎨0,1,F(n−1)+F(n−2),当 n = 0 时当n = 1 时当n>1 打印出前40位斐波那契数列 int main() { int i; int a[40]; a[0] = 0; a[1] = 1; printf("%d", a[0]); printf("%d", a[1]); for (i = 2; i < 40; i++){ a[i] = a[i-1] + a[i-2]; printf("%d", a[i]); } return 0; }
递归来实现
int main() { int i; int a[40]; a[0] = 0; a[1] = 1; printf("%d", a[0]); printf("%d", a[1]); for (i = 2; i < 40; i++){ a[i] = a[i-1] + a[i-2]; printf("%d", a[i]); } return 0; }把一个直接调用自己或通过一系列的调用语句间接调用自己的函数,称做递归函数。 每个递归定义必须至少有一个条件,满足时递归不再进行,即不在引用自身而是返回值退出。 迭代使用循环结构,递归使用的是选择结构。递归能使程序的结构更清晰、简单,但是大量的递归调用会建立函数的副本,会耗费大量的时间和内存。迭代不需要反复调用函数和占用额外的内存。应该视情况不同选择不同的代码。 编译器使用栈实现递归。
不需要括号的后缀表示法,称为逆波兰表示。是表达式的一种新的显示方式,非常巧妙地解决了程序实现四则运算的难题。 对于“9+(3-1)*3+10 / 2”,用后缀表示法式“ 9 3 1- 3* + 10 2/ +”
用后缀表示法式“ 9 3 1- 3* + 10 2/ +” 规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果。 下面推导如何让9+(3-1)*3+10 / 2”,用后缀表示法式“ 9 3 1- 3* + 10 2/ +”
平时用的标准四则运算表达式称中缀表达式。 中缀表达式9+(3-1)*3+10 / 2”,转化为后缀表达式“ 9 3 1- 3* + 10 2/ +” 规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级不高于栈顶符号则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。 要想让计算机具有处理标准表达式的能力:
将中缀表达式转化为后缀表达式;将后缀表达式进行运算得出结果。队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端是队尾,允许删除的一端称为队头。
插入队尾时间为O(1),删除队头时间为O(n)。
把队列的这种头尾相接的顺序存储结构称为循环队列。 引入两个指针front指针指向队头元素,rear指向队尾元素的下一个位置。若队列的最大尺寸为QueueSize,则队列慢的条件为(rear+1)%QueueSize == front 通用的计算队列长度公式为: (rear-front + QueueSize) % QueueSize
typedef int QElemType; typedef struct { QElemType data[MAXSIZE]; int front; //头指针 int rear; //尾指针,指向队尾元素的下一个位置 }SqQueue; //初始化一个空队列Q Status InitQueue(SqQueue *Q) { Q->front = 0; Q->rear = 0; } //返回Q的元素个数,即队列的当前长度 int QueueLength(SqQueue Q) { return (Q.rear - Q.front + MAXSIZE) % MAXSIZE; } //插入元素e为新的队尾元素 Status EnQueue(SqQueue *Q, QElemType e) { if ((Q->rear + 1) % MAXSIZE == Q->front) return ERROR; Q->data[Q->rear] = e; Q->rear = (Q->rear+1) %MAXSIZE; return OK; } //若队列不空,则删除Q中队头元素,用e返回其值 Status DeQueue(SqQueue *Q, QElemType *e) { if (Q->front == Q->rear) return ERRROR; *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;入队操作时,其实就是在链表尾部插入节点:
//插入元素e为Q的新的队尾元素 Status EnQueue(LinkQueue *Q, QElemType e) { QueuePtr s = (QueuePtr)malloc(sizeof(QNode)); if (!s) exit(OVERFLOW); s->data = e; s->next = NULL; Q->rear->next = s; Q->rear =s; return OK; }对于循环队列与链队列的比较,从时间上,它们的时间都为O(1)。空间上来说,循环队列必须有一个固定的长度,所以就有了存储元素个数和空间浪费的问题,而链队不存在这个问题,所以,链队列更加灵活。
