目录
3.1栈
重点:逻辑结构唯一
两种特殊的线性表:栈和队列
首先从数据结构的角度来看:栈和队列是操作受限的线性表。他们的逻辑结构都是一对一的关系。
3.1.1栈的逻辑结构
栈:限定仅在表尾进行插入和删除操作的线性表。
(a1, a2, ……, an)
空栈:不含任何数据元素的栈。
允许插入和删除的一端称为栈顶,另一端称为栈底。
栈的示意图
栈的插入:入栈,进栈,压栈
栈的删除:出栈,弹栈
栈的操作原则:后进先出原则
例题:例:有三个元素按a、b、c的次序依次进栈,且每个元素只允许进一次栈,则可能的出栈序列有多少种?
可以通过穷举所有可能性来求解:
① 1入1出, 2入2出,3入3出, 即123;
② 1入1出, 2、3入3、2出, 即132;
③ 1、2入,2出, 3入3出, 即231;
④ 1、2入,2、1出,3入3出, 即213;
⑤ 1、2、3入,3、2、1出, 即321;
合计有5种可能性。
例题2:提问:一个栈的输入序列是12345,若在入栈的过程中允许出栈,则栈的输出序列43512可能实现吗?12345的输出呢?
答:不可能43512中的12部分不可能实现,只可能是21.
12345可以实现,只要进去一个弹出一个即可。‘’
3.1.2栈的顺序存储结构及实现
顺序栈——栈的顺序存储结构
改造数组实现栈的顺序存储。
附设指针top指示栈顶指针的下一个位置。
进栈:top加一 栈空:top==base (base是指向栈底的指针)
出栈:top减一 栈满:top-base=stacksize
1.栈的定义:
#define stacksize 100;
typedef struct{
ElementType *top;
ElementType *base; //在栈构造之前和销毁之后base的值为NULL;
int stacksize;
}sqStack;
2.栈的初始化:
Status InitStack ( SqStack &S)
{ S.base=(SElemType *) malloc (STACK_INIT_SIZE *
sizeof (SElemType);
if (!S.base) exit (OVERFLOW);
S.top=S.base
S.stacksize= STACK_INIT_SIZE;
return OK;
}
初始化函数的几个点:
1.为s.base分配空间
2.因为现在栈为空,所以让s.top s.base指向同一个空间
3.规定栈的大小stacksize
3.获取栈顶元素
Status GetTop( SqStack S, SElemType &e)
{ //若栈不空,则获取S的栈顶元素,用e返回其值,
并返回OK;否则返回ERROR
if ( S.top == S.base ) return ERROR;
e=*(S.top-1)
return OK;
}
两个点:
1.判断栈是否为空。
2.利用s.top是指向栈顶元素下一块空间的特点。e=*(s.top-1);
4.入栈。
Status Push ( SqStack &S, SElemType e)
//如果栈满,需要重新申请空间,再插入元素e
{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;
} //if
*S.top++ = e;
return OK;
}//Push
三个点:
1.判满,重新分配空间
s.base=(ElementType *)malloc((s.stacksize+STACKINCREASEMENT)*sizeof(ElementType);
2赋值3.top指针加一
*s.top++=e; 先赋值后加
5.出栈
Status Pop ( SqStack &S, SElemType & e)
{ //若栈不空,则删除S的栈顶元素,用e返回其值,
并返回OK;否则返回ERROR
if ( S.top == S.base ) return ERROR;
e=*--S.top;
return OK;
}//Pop
唯一要注意:e=*--s.top;
总结提问:
顺序栈S不存在的条件:
S.base=NULL;
顺序栈为空 的条件 :
S.base==S.top;
顺序栈满的条件 :
S.top-S.base=S.stacksize;
3.1.3栈的链式存储结构及实现
链栈的结构
将哪一端作为栈顶?
将链栈头作为栈顶。
链栈需要加头结点吗?
链栈不需要附设头结点。
即:
栈顶 栈底
1.链栈定义:
typedef struct SNODE
{
SElemType data;
struct snode *link;
}SNODE,*LinkStack;
LinkStack top, p;
int m=sizeof (SNODE); 以头指针为栈顶,在头指针处插入或者删除!
2.入栈
void Push ()
{ p=(NODE*) malloc (m);
if (!p){上溢}
else { p->data=x;
p->link=top;
top=p;}
}
注意:
1.p->link=top; //给新节点分配空间后时其指向以前的头结点。
2.top=p //使头指针指向变更为新首元结点。
3.删除
void pop(linkstack &TOP,ElementType e){
if(top==NULL) //下溢
else{
linkstack p=top;
e=TOP->data;
top=top->link;
free(p);
}
}
讨论:顺序栈和链栈的比较
时间性能:
相同,都是常数时间O(1)。
空间性能:
总之,当栈的使用过程中元素个数变化较大时,用链栈是适宜的,反之,应该采用顺序栈。
3.2栈的应用举例
1.数制转换(例子中为10转2)
Void conversion(){
InitStack(S); Scanf(“%d”,n);
While(n){
Push(S,n%2)
n=n/2}
//当n不为零时 n除以2的余数入栈。
while(!StackEmpty(S)){
Pop(S,e);
Printf(“%d”,e); } //while
//当栈中不为空时,栈中元素出栈
}//conversion
2.表达式求值(算符优先法)
例如:3*(7 – 2 )
思路:规则是 从左到右 先乘除后加减 先算括号内部后算括号外
根据以上的规则:
根据上述三条运算规则,在运算的每一步中,对任意相继出现的算符q1和q2 要比较,优先 权关系。
比较过程略。
算法思想:
If 栈顶元素<操作符,压入OPTR栈
栈顶元素 =操作符且不为‘#’,脱括号(弹出左括号);
栈顶元素 >操作符,则退栈、计算,结果压入OPND栈; 。
3.3栈与递归的实现
此处略,提供两例题:
1.
int X(int n)
{
if(n<=3) return 1;
else return X(n-2)+X(n-4)+1
}
则计算 X(8)的结果是:_____。
A.8 B. 9 C.16 D. 18
b
2.
1. 一个栈的输入序列为123…n,若输出序列的第一个元素是n,输出第i(1<=i<=n)个元素是( )。
A. 不确定 B. n-i+1 C. i D. n-i
【中山大学 1999 一、9(1分)】
2. 若一个栈的输入序列为1,2,3,…,n,输出序列的第一个元素是i,则第j个输出元素是( )。
A. i-j-1 B. i-j C. j-i+1 D. 不确定的
bd
3.4队列
3.4.1队列的逻辑结构
队列:只允许在一端进行插入操作,而另一端进行删除操作的线性表。
空队列:不含任何数据元素的队列。
允许插入(也称入队、进队)的一端称为队尾,允许删除(也称出队)的一端称为队头。
队列的操作特性:先进先出FIFO
front rear
队头指针即为链表的头指针。
非空链队列
空链队列
如何判空队列?
3.4.2队列链式存储实现
队列的定义:
typedef struct QNode{
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct {
QueuePtr front; // 队头指针
QueuePtr rear; // 队尾指针
}LinkQueue; // 链队列
1.构造空队列
Status InitQueue (LinkQueue &Q)
{ // 构造一个空队列 Q
Q.front = Q.rear =
(QueuePtr) malloc (sizeof(QNode));
if (!Q.front) exit (OVERFLOW); //存储分配失败
Q.front->next = NULL;
return OK;
}
空队列的头指针和尾指针都指向头结点。
2.销毁队列
Status DestroyQuene ( LinkQuene &Q)
{ //销毁队列Q
while ( Q.front )
{
Q.rear = Q.front—>next;
free ( Q.front);
Q.front = Q.rear;
}
return OK;
}
思路:
利用while循环():
1:尾指针指向头指针指向结点的下一个。
2.free头指针所指内存。
3.使头指针指向为指针所示结点。
3.入队操作
Status EnQueue (LinkQueue &Q, QElemType e)
{ //插入元素e为Q的新的队列尾元素
p =(QueuePtr) malloc (sizeof (QNode));
if ( !p ) exit (OVERFLOW); // 存储分配失败
p->data = e; p->next = NULL;
Q.rear->next = p; // 修改尾结点的指针
Q.rear = p; // 移动队尾指针
return OK;
}
4.出队操作
Status DeQueue (Queue &Q, QElemType &e)
{ // 若队列不空,则删除Q的队头元素,用e返回其值,
并返回OK;否则返回ERROR
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); // 释放被删结点
return OK;
}//DeQueue
例题:
cdcd
例:
yhar
例:
利用堆栈做辅助,将队列中的数据元素进行逆置
链队列的总结讨论:
1.空队列的特征?
Q->front=Q->rear;
2.链队列是否会慢?
不会,除非 申请不到内存空间。
3.链队列的时间性能和空间性能。
时间性能o(1)空间性能o(n)
3.4.3队列顺序存储实现
如何改造数组实现队列的顺序存储?
答:要求队列中的元素存储在数组中的连续位置。需设置队头,队尾两个指针。
入队出队的流程示意图
同样的,出队的时间性能也为O(1);
顺序队列整体的移动有什么特点?
答:队列整体向数组下标增大的方向移动。(单项移动性)
答:会出现“假溢出”原因在于队列的后端空间用尽了,但是队列前端还有空间,这种现象叫“假溢出” 。
如何解决假溢出?
采用循环队列。
即将存储队列的数组头尾相接。
不存在物理的循环结构,用软件方法实现。
求模:rear=(rear+1)modMAXSIZE
例题:
顺序队列总结:
队空条件:
front=rear
队满条件:
front = (rear+1) % N (N=maxsize)
队列长度:
L=(N+rear-front)% N 重点
问1:左图中队列容量
maxsize N=6
问2:左图中队列长度L=?5
问3: 在具有n个单元的循环队列中,队满时共有多少个元素? n-1
问:为什么要设计队列?它有什么独特用途?
3. 简化程序设计。
线性表、栈与队的异同点
相同点:逻辑结构相同,都是线性的;都可以用顺序存储或链表存储;栈和队列是两种特殊的线性表,即受限的线性表(只是对插入、删除运算加以限制)。
不同点:
① 运算规则不同,线性表为随机存取,而栈是只允许在一端进行插入和删除运算,因而是后进先出表LIFO;队列是只允许在一端进行插入、另一端进行删除运算,因而是先进先出表FIFO。
② 用途不同,线性表比较通用;堆栈用于函数调用、递归和简化设计等;队列用于离散事件模拟、多道作业处理和简化设计等。