栈和队列
栈和队列的定义和特点
- 栈和队列是两种常用的、重要的数据结构
- 栈和队列是限定插入和删除只能在表的“端点”进行的线性表
- 栈和队列是线性表的子集(是插入和删除位置受限的线性表)
栈——后进先出
栈的应用:
- 数制转换
- 括号匹配的检验
- 行编辑程序
- 迷宫求解
- 表达式求值
- 八皇后问题
- 函数调用
- 递归调用的实现
队列——先进先出
队列的应用:
- 脱机打印输出:按申请的先后顺序依次输出
- 多用户系统中,多个用户排成队,分时地循环使用CPU和主存
- 按用户的优先级排成多个队,每个优先级一个队列
- 实时控制系统中,信号按接收的先后顺序依次处理
- 网络电文传输,按到达的时间先后顺序依次进行
栈的定义和特点
栈(stack)是一个特殊的线性表,是限定仅在一端(通常是表尾)进行插入和删除操作的线性表。
又称为后进先出(Last In First Out)的线性表,简称LIFO结构。
-
栈的相关概念
栈是仅在表尾进行插入、删除操作的线性表。
表尾(即an端)称为栈顶Top;表头(即a1端)称为栈底Base
插入元素到栈顶(即表尾)的操作,称为入栈。
从栈顶(即表尾)删除最后一个元素的操作,称为出栈。
- 定义 限定只能在表的一端进行插入和删除运算的线性表(只能在栈顶操作)
- 逻辑结构 与同线性表相同,仍为一对一关系
- 存储结构 用顺序栈或链栈存储均可,但以顺序栈更常见
- 运算规则 只能在栈顶运算,且访问结点时依照后进先出(LIFO)的原则
- 实现方式 关键是编写入栈和出栈函数,具体实现依顺序栈或链栈的不同而不同
队列的定义和特点
队列(queue)是一种先进先出(Frist In Frist Out——FIFO)的线性表。在表一端插入(表尾),在另一端(表头)删除
- 队列的相关概念
- 定义 只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表(头删尾插)
- 逻辑结构 与同线性表相同,仍为一对一关系
- 存储结构 顺序队或链队,以循环顺序队列更常见
- 运算规则 只能在队首和队尾运算,且访问结点时依照**先进先出(FIFO)**的原则
- 实现方式 关键是掌握入队和出队操作,具体实现依顺序队或链队的不同而不同
栈的表示和实现
-
栈的抽象数据类型的类型定义
ADT Stack{ 数据对象: D={ai|ai∈ElemSet,i=1,2,...,n,n≥0} 数据关系: R1={<ai-1,ai>|ai-1,ai∈D,i=2,...,n} 约定an端为栈顶,a1端为栈底。 基本操作:初始化、进栈、出栈、取栈顶元素等 }ADT Stack
-
顺序栈的表示
#define MAXSIZE 100 typedef struct{ SElemType *base;//栈底指针 SElemType *top;//栈顶指针 int stacksize;//栈可用最大容量 }SqStack;
-
链栈:
typedef struct StackNode{ SelemType data; struct StackNode *next; }StackNode,*LinkStack; LinkStack S;
- 链表的头指针就是栈顶
- 不需要头结点
- 基本不存在栈满的情况
- 空栈相当于头指针指向空
- 插入和删除仅在栈顶处执行
-
InitStack(&S) 初始化操作
操作结果:构造一个空栈S。
-
顺序栈:
Status InitStack(SqStack &S){ //构造一个空栈 S.base=new SElemType[MAXSIZE];//或S.base=(SElemType*)malloc(MAXSIZE*sizeof(SElemType)); if(!S.base) exit (OVERFLOW);//存储分配失败 S.top=S.base;//栈顶指针等于栈底指针 S.stacksize=MAXSIZE; return OK; }
-
链栈:
void InitStack(LinkStack &S){ S=NULL; return OK; }
-
-
DestroyStack(&S) 销毁栈操作
初始条件:栈S已存在。
操作结果:栈S被销毁。
-
顺序栈:
Status DestroyStack(SqStack &S){ if(S.base){ delete S.base; S.stacksize=0; S.base=S.top=NULL } return OK; }
-
-
StackEmpty(S) 判定S是否为空栈
初始条件:栈S已存在。
操作结果:若栈S为空栈,则返回TRUE,否则FALSE。
-
顺序栈:
Status StackEmpty(SqStack S){ if(S.top==S.base) return TRUE; else return FALSE; }
-
链栈:
Status StackEmpty(LinkStack S){ if(S==NULL) return TRUE; else return FALSE; }
-
-
StackLength(S) 求栈的长度
初始条件:栈S已存在。
操作结果:返回S的元素个数,即栈的长度。
-
顺序栈:
int StackLength(SqStack S){ return S.top-S.base; }
-
-
GetTop(S,&e) 取栈顶元素
初始条件:栈S已存在且非空。
操作结果:用e返回S的栈顶元素。
-
链栈:
SElemType GetTop(LinkStack S){ if(S!=NULL) return S->data; }
-
-
ClearStack(&S) 栈置空操作
初始条件:栈S已存在。
操作结果:将S清为空栈。
-
顺序栈:
Status ClearStack(SqStack S){ if(S.base) S.top=S.base; return OK; }
-
-
Push(&S,e) 入栈操作
初始条件:栈S已存在。
操作结果:插入元素e为新的栈顶元素。
-
顺序栈:
Status Push(SqStack &S,SElemType e){ if(S.top-S.base==S.stacksize)//栈满 return ERROR; *S.top++=e; }
-
链栈:
Status Push(LinkStack &S,SElemType e){ p=new StackNode;//生成新结点p p->data=e;//将新结点数据域置为e p->next=S;//将新结点插入栈顶 S=p;//修改栈顶指针 return OK; }
-
-
Pop(&S,&e) 出栈操作
初始条件:栈S已存在且非空。
操作结果:删除S的栈顶元素an,并用e返回其值。
-
顺序栈:
Status Pop(SqStack &S,SElemType &e){ if(S.top==S.base) return ERROR; e=*--S.top; return OK; }
-
链栈:
Status Pop(LinkStack &S,SelemType &e){ if(S==NULL) return ERROR; e=S->data; p=S; S=S->next; delete p; return OK; }
-
栈与递归
-
递归的定义
- 若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的;
- 若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
-
以下三种情况常常用到递归方法
- 递归定义的数学函数
- 阶乘函数
- 2阶Fibonaci数列
- 具有递归特性的数据结构
- 二叉树
- 广义表
- 可递归求解的问题
- 迷宫问题
- Hanoi塔问题
- 递归定义的数学函数
-
递归问题——用分治法求解
**分治法:**对于一个较为复杂的问题,能够分解成几个相对简单的且解法相同或类似的子问题来求解
必备的三个条件
- 能将一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些处理对象是变化有规律的
- 可以通过上述转化而使问题简化
- 必须有一个明确的递归出口,或称递归的边界
分治法求解递归问题算法的一般形式:
void p(参数表){ if(递归结束条件) 可直接求解步骤;——基本项 else p(较小的参数);——归纳项 }
-
函数调用过程
调用前,系统完成:
- 将实参,返回地址等传递给被调用函数
- 为被调用函数的局部变量分配存储区
- 将控制转移到被调用函数的入口
调用后,系统完成:
- 保存被调用函数的计算结果
- 释放被调用函数的数据区
- 依照被调用函数保存的返回地址将控制转移到调用函数
-
递归的优缺点
优点:结构清晰,程序易读
缺点:每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,恢复状态信息。时间开销大。
递归->非递归
方法一:尾递归、单向递归->循环结构
方法二:自用栈模拟系统的运行时栈
借助栈改写递归
- 递归程序在执行时需要系统提供栈来实现
- 仿照递归算法执行过程中递归工作栈的状态变化可写出相应的非递归程序
- 改写后的非递归算法与原来的递归算法相比,结构不够清晰,可读性较差,有的还需要经过一系列优化
队列的表示和实现
-
队列的抽象数据类型定义
ADT Queue{ 数据对象:D={ai|ai∈ElemSet,i=1,2,...,n,n≥0} 数据关系:R={<ai-1,ai>|ai-1,ai∈D,i=2,...,n} 约定其中a1端为队列头,an端为队列尾 基本操作: InitQueue(&Q) 操作结果:构造空队列Q DestroyQueue(&Q)条件:队列Q已存在;操作结果:队列Q被销毁 ClearQueue(&Q) 条件:队列Q已存在;操作结果:将Q清空 QueueLength(Q) 条件:队列Q已存在;操作结果:返回Q的元素个数:即队长 GetHead(Q,&e) 条件:队列Q非空;操作结果:用e返回Q的队头元素 EnQueue(&Q,e) 条件:队列Q已存在;操作结果:插入元素e为Q的队尾元素 DeQueue(&Q,&e) 条件队列Q非空;操作结果:删除Q的队头元素,用e返回值 ......还有将队列置空、遍历队列等操作...... }ADT Queue
-
队列的顺序表示——用一维数组base[MAXSIZE]
#define MAXSIZE 100 //最大队列长度 Typedef struct{ QElemType *base;//初始化的动态分配存储空间 int front; //头指针 int rear; //尾指针 }SqQueue;
-
链队列的类型定义
#define MAXQSIZE 100//最大队列长度 typedef struct Qnode{ QElemType data; struct Qnode *next; }QNode,*QueuePtr; typedef struct{ QueuePtr front;//队头指针 QueuePtr rear;//队尾指针 }LinkQueue;
-
假上溢问题解决方法
-
将队中元素依次向队头方向移动。
缺点:浪费时间。每移动一次,队中元素都要移动。
-
将队空间设想成一个循环的表,即分配给队列的m个存储单元可以循环使用,当rear为maxqsize时,若向量的开始端空着,又可从头使用空着的空间。当front为maxqsize时,也是一样。
引入循环队列
base[0]接在base[MAXQSIZE-1]之后,若rear+1==M,则令rear=0;
实现方法:利用模(mod,C语言中:%)运算。
插入元素:Q.base[Q.rear]=x; Q.rear=(Q.rear+1)%MAXQSIZE;
删除元素:x=Q.base[s.front]; Q.front=(Q.front+1)%MAXQSIZE
循环队列:循环使用为队列分配的存储空间。
-
-
判断队空/队满
-
另外设一个标志以区别队空、队满
-
另设一个变量,记录元素个数
-
少用一个元素空间
队空:front==rear
队满:(rear+1)%MAXQSIZE==front
-
-
队列初始化
Status InitQueue(SqQueue &Q){ Q.base=new QElemType[MAXQSIZE]; //分配数组空间 //Q.base=(QElemType*)malloc(MAXQSIZE*sizeof(QElemType)); if(!Q.base) exit(OVERFLOW); //存储分配失败 Q.front=Q.rear=0; //头指针尾指针置为0,队列为空 return OK; } //链队 Status InitQueue(LinkQueue &Q){ Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode)); if(!=Q.front) exit(OVERFLOW); Q.front->next=NULL; return OK; }
-
求队列长度
int QueueLength(SqQueue Q){ return (Q.rear-Q.front+MAXQSIZE)%MAXQSIZE; }
-
循环队列入队
Status EnQueue(SqQueue &Q,QElemType e){ if((Q.rear+1)%MAXQSIZE==Q.front) return ERROR;//队满 Q.base[Q.rear]=e; //新元素加入队尾 Q.rear=(Q.rear+1)%MAXQSIZE; //队尾指针+1 return OK; } //链队 Status EnQueue(SqQueue &Q,QElemType e){ p=(QueuePtr)malloc(sizeof(QNode)); if(!p) exit(OVERFLOW); p->data=e; p->next=NULL; Q.rear->next=p; Q.rear=p; return OK; }
-
循环队列出队
Status DeQueue(SqQueue &Q,QElemType &e){ if(Q.front==Q.rear) return ERROR; //队空 e=Q.base[Q.front]; //保存队头元素 Q.front=(Q.front+1)%MAXQSIZE; //队头指针+1 return OK; } //链队 Status DeQueue(SqQueue &Q,QElemType &e){ 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; delete p; return OK; }
-
取队头元素
SElemType GetHead(SqQueue Q){ if(Q.front!=Q.rear) //队列不为空 return Q.base[Q.front]; //返回队头指针元素的值,队头指针不变 } //链队 Status GetHead(LinkQueue Q,QElemType &e){ if(Q.front==Q.rear) return ERROR; e=Q.front->next->data; return OK; }
-
销毁链队列
Status DestroyQueue(LinkQueue &Q){ while(Q.front){ p=Q.front->next; free(Q.front); Q.front=p; } return OK; }