440 likes | 620 Views
数据结构 ( DATA STRUCTURE). 计算机科学与技术学院. 第三章 栈和队列. 栈 ( Stack ) 栈的应用 递归 队列 ( Queue ). 3.1 栈 ( Stack ). 1 )栈的定义 限定在表尾进行插入和删除操作的线性表 允许插入和删除 的一端称为 栈顶 ( top ) ,另一端称 为 栈底 ( bottom ) 特点 后进先出 ( LIFO ). 2) 栈的抽象数据类型. ADT Stack {
E N D
数据结构 (DATA STRUCTURE) 计算机科学与技术学院
第三章 栈和队列 • 栈 ( Stack ) • 栈的应用 • 递归 • 队列 ( Queue )
3.1栈 ( Stack ) 1)栈的定义 • 限定在表尾进行插入和删除操作的线性表 • 允许插入和删除 的一端称为栈顶 (top),另一端称 为栈底(bottom) • 特点 后进先出 (LIFO)
2) 栈的抽象数据类型 ADT Stack { 数据对象: D={ ai | ai∈ElemSet, i=1,2,...,n, n≥0 } 数据关系:R1={ <ai-1 ,ai >|ai-1 ,ai∈D, i=2,...,n } 基本操作: InitStack ( &S) : 栈的初始化 DestoryStack(&S) : 销毁栈S Push ( &S, e) :进栈 Pop (&S ) : 出栈 GetTop (S ): 取栈顶元素 IsEmpty (S): 判栈空否 … … }
3.1.1栈的顺序存储 —顺序栈 • 1) 存储特点: • 利用一组地址连续的存储单元依次存放栈元素 • 附设指针top指示栈顶元素在顺序栈中的位置 2) 顺序栈的表示 # define Stack_Init_Size 100; //存储空间初始分配量 # define StackIncrement 10; //存储空间分配增量 typedef XXX SElemtype; typedef struct { SElemtype *base; //栈底指针 SElemtype *top; //栈顶指针 int stacksize; //栈当前最大容量 }SqStack;
3) 基本操作的实现 • ①进栈(入栈) • 分析 • 首先判断栈满?(栈满:s.top-s.base>=s.stacksize) 若栈满,则再向系统申请追加 StackIncrement 大小空间 • 令 *(s.top)= x , s.top +1
算法 SqStackPush (SqStack S, SElemtype x) { if (S.top-S.base>=S.Stacksize) //栈满? { S.base = (SElemtype *) realloc (S.base, (S.Stacksize + StackIncrement) *sizeof(SElemtype)); if (!S.base) exit (OVERFLOW); //存储分配失败 S.top=S.base+S.StackSize; S.StackSize + = StackIncrement; } *S.top ++ = x; // 等同于 *s.top=x; s.top++; return S; }
②出栈(退栈) • 分析 • 首先判断栈空?(栈空:s.top=s.base) • 令 e = *(s.top-1) , s.top -1 • 算法 SqStackPop (SqStack S, SElemtype &e) { if (S.top=S.base) return ERROR;/*栈空*/ e = * --S.top; // 等同于 s.top--; e=*(s.top); return S; }
多栈处理 栈浮动技术 • n个栈共享一个数组空间V[m] • 设立栈顶指针数组 t [n+1]和栈底指针数组 b [n+1],t[i]和b[i]分别指示第 i 个栈的栈顶与栈底 • 各栈初始分配空间 s = m / n • 指针初始值 t[0] = b[0] = -1 b[n] = m-1 t[i] = b[i] = b[i-1] + s, i = 1, 2, …, n-1
3.1.2栈的链式存储 —链式栈 1)存储特点 • 是一种特殊形式的单链表(无表头结点;插入、删除操作限定在表头端进行) • 链式栈无栈满问题,空间可扩充
2)链式栈的表示:同单链表 typedef struct node { SElemtype data; struct node *next; } linkstack;
3)基本运算的实现 ① 进栈(入栈) • 分析 • 首先申请一个结点空间 (用 p 指向该结点) • 若链栈不为空,将*p 插入链表的第一个结点 之前: p->next =top; top=p; 否则:p->next =NULL; top=p;
② 出栈(退栈) • 分析 • 首先判断栈空? (栈空:top=NULL) • 若不空,判断 *top是否为栈中最后一个元素 如不是,则将*top从链表中删除: top=top->next 否则,令top=NULL • 释放栈顶元素空间;
3.1.3栈的应用举例 例1:简单应用:数制转换问题 • 问题:将十进制数N转换为r进制的数,其转换方法利用辗转相除法。 • 分析:所转换的r 进制数按低位到高位的顺序产生,而通常的输出是从高位到低位的,恰好与计算过程相反
例2 中缀表达式求值 • 问题:根据算符优先法对表达式求值,所讨论的算术运算符包括:+ 、- 、*、/、%、^(乘方)和括号()。运算规则为: • 运算符的优先级为: ()→ ^ →*、/、% → +、-; • 有括号时先算括号内的,后算括号外的,多层括号由内向外进行; • 乘方连续出现时先算最右面的;
分析:表达式作为一个串,如表达式“3+2*4-5”,其求值过程为:自左向右扫描表达式,当扫描到3*2时不能马上计算,因后面可能有更高的运算分析:表达式作为一个串,如表达式“3+2*4-5”,其求值过程为:自左向右扫描表达式,当扫描到3*2时不能马上计算,因后面可能有更高的运算 • 需要两个栈:对象栈OPND和算符栈OPTR; • 自左至右扫描表达式, 若当前字符是运算对象,入OPND栈; • 对运算符,若这个运算符比栈顶运算符高则入栈,继续向后处理,若这个运算符比栈顶运算符低则从OPND栈出栈两个数,从OPTR栈出栈一运算符进行运算,并将其运算结果入OPND栈,继续处理当前字符,直到遇到结束符。
例3 栈与递归 • 问题:栈的一个重要应用是在程序设计语言中实现递归过程。 n! = • 分析:根据定义可以很自然的写出相应的递归函数 int fact (int n) { if (n==0) return 1; else return ( n*fact(n-1)); } 1 n=0 /*递归终止条件*/ n*(n-1)! n>0 /*递归步骤*/
递归的概念 • 递归的定义 若一个对象部分地包含它自己, 或用它自己给自己定义, 则称这个对象是递归的;若一个过程直接地或间接地调用自己, 则称这个过程是递归的过程。 • 以下三种情况常常用到递归方法。 • 定义是递归的 • 数据结构是递归的 • 问题的解法是递归的
定义是递归的 例如,阶乘函数 • 求解阶乘函数的递归算法 • longFactorial (longn ) • { if ( n == 0 )return 1; • else returnn*Factorial (n-1); • }
数据结构是递归的 例如,单链表结构 • 搜索链表最后一个结点并打印其数值 • voidFind ( LinkList L ) { • if ( L→next == NULL ) • printf ( L→data); • else Find(L→next); • }
问题的解法是递归的 例如,汉诺塔(Tower of Hanoi)问题
递归过程与递归工作栈 • 递归过程在实现时,需要自己调用自己。 • 每一次递归调用时,需要为过程中使用的参数、局部变量等另外分配存储空间。 • 层层向下递归,退出时的次序正好相反: 递归次序 n! (n-1)! (n-2)! 1! 0!=1 返回次序 • 因此,每层递归调用需分配的空间形成递归工作记录,按后进先出的栈组织。
3.2队列 (Queue) 1) 定义 • 队列是只允许在一端删除,在另一端插入的线性表 • 允许删除的一端叫做队头(front),允许插入的一端叫做队尾(rear)。 • 特性 • 先进先出(FIFO, First In First Out)
2)队列的抽象数据类型 ADT Queue { 数据对象: D={ ai | ai∈ElemSet, i=1,2,...,n, n≥0 } 数据关系:R1={ <ai-1 ,ai >|ai-1 ,ai∈D, i=2,...,n } 基本操作: InitQueue ( &Q) : 初始化队列 DestoryQueue (&Q) : 销毁队列 EnQueue (&Q, e) :进队列 DeQueue (Q, &e): 出队列 GetHead (Q, &e): 取队头元素值 QueueEmpty (S): 判队列空否 … … }
3.2.1队列的链接存储—链式队列 1) 存储特点: • 队头在链头,队尾在链尾。 • 设置队头、队尾指针分别保存队头、队尾地址
2) 链队列表示: • 结点类型: typedef struct Qnode { QElemtype data; struct QNode *next; 定义一链队列 } Qnode,*Queueptr; LinkQueue Q; • 链队列类型 队头结点: typedef struct Q.front->next { Queueptr front; 队尾结点: Q.rear Queueptr rear; 队尾结点数据: }LinkQueue; Q.rear->next
3) 基本操作的实现 ① 入队 Queueptr EnQueue (LinkQueue Q, Qelemtype x) { p=(Queueptr) malloc(sizeof(QNode)); /*申请结点空间*/ if (!p) exit(overflow); p->data=x; p->next=NULL; Q.rear->next=p; Q.rear=p; return Q; }
② 出队 Queueptr DeQueue (LinkQueue Q, Qelemtype &e) { if (Q.front==Q.rear) return ERROR; /* 空队列*/ p=Q.fornt->next; e=p->data; /* p指向队头结点*/ Q.front->next= p->next; /* 删除队头结点*/ if (Q.rear==p) Q.rear= Q.front; /* p 是队中最后一个结点*/ free(p); return Q; }
3.2.2队列的顺序存储—循环队列 • 存储特点: • 利用地址连续的存储单元依次存放队列各元素。 • 设置两个指示器 front, rear分别指示队头、队尾元素的下标
5 rear F 4 0 E D 1 3 C 2 front • 存在问题: “假溢出” • 解决方法: 把队列存储空间看作首尾相连的环
5 rear F 4 0 E D 1 3 C 2 front 1)循环队列 (Circular Queue) • 存储队列的数组被当作首尾相接的表处理。 • 队头、队尾指针加1时从 MaxSize -1直接进到0,可用C语言的取模(余数)运算实现。 • 出队:队头指针进1 front = (front + 1) % MaxSize; • 入队:队尾指针进1 rear = (rear + 1) % MaxSize; • 队列初始化:front=rear= 0;
front == rear ; • 队空条件: • 队满条件: (rear + 1) % MaxSize == front ;
2)循环队列数据类型描述 typedef struct { QElemtyope *base; //data[MAXSIZE]数据的存储区 int front; //队头指针,指向对头元素 int rear; //队尾指针,指向对尾元素的下一个位置 }SqQueue; /*循环队列*/
3)基本操作的实现 ① 初始化(置空队)
算法 SqQueue Init_SqQueue () { SqQueue Q; Q.base=(QElemtype*)malloc(Maxsize * sizeof(QElemtype)); //申请存储空间 if ( !Q.base ) exit (overflow); Q.front = Q.rear = 0; //队列置空 return Q; }
算法 SqQueue EnSqQueue (SqQueue Q , QElemtype x) { if ((Q.rear + 1) % MaxSize == Q.front) return ERROR; /*队满不能入队*/ else { Q.base[Q.rear] = x; //插入元素存入队尾 Q.rear=(Q.rear+1) % MaxSize; /*修改尾指针,入队完成*/ } return Q; }
5 rear F 4 0 E D 1 3 C 2 front ③ 出队(删除)
算法 SqQueue DeSqQueue(SqQueue Q ,QElemtype &e) { if (Q.front == Q.rear) return Error; /*队空*/ else { e = Q.base[Q.front]; //取出队头元素 Q.front = (Q.front+1) % MaxSize; /*修改头指针,出队完成*/ } return Q; }
本章小结 • 栈 • 逻辑结构:后进先出 • 物理结构:顺序栈,链栈 • 数据类型描述 • 基本运算实现 • 栈空条件,栈满条件?(何时判断) • 队列 • 逻辑结构:先进先出 • 物理结构:链队列,循环队列(顺序队列) • 数据类型描述 • 基本运算实现 • 队空条件,队满条件?(何时判断)