【数据结构】第三章 限定性线性表—栈与队列
1.基本概念
(1)栈
栈作为一种限定性线性表,是将线性表的插入和删除运算限制为仅在表的一端进行,栈顶的当前位置是动态变化的,它由一个称为栈顶指针的位置指示器指示。同时表的另一端被称为栈底。
后进先出的线性表。
有两种基本的存储结构:顺序存储结构(顺序栈)、链式存储结构(链栈)
(1)顺序栈
顺序栈是用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。top=-1表示空栈。
多栈共享技术:
使用顺序栈,会因为对栈空间大小难以准确估计,从而产生有的栈溢出、有的栈空间还很空闲的情况,通过使多个栈共享一个足够大的数组空间,通过利用栈的动态特性来使其存储空间互相补充,这就是多栈的共享技术。
在栈的共享技术中最常用的是两个栈的共享技术:它主要利用了栈“栈底位置不变,而栈顶位置动态变化”的特性
(2)链栈
链栈即采用链表作为存储结构实现的栈。栈的插入和删除操作仅限制在表头位置进行,所以链表的表头指针就作为栈顶指针
top为栈顶指针。若top->next=NULL,则代表栈空。采用链栈不必预先估计栈的最大容量,只要系统有可用空间,链栈就不会出现溢出。
多栈运算:
将多个链栈的栈顶指针放在一个一维指针数组中来统一管理,从而实现同时管理和使用多个栈
(3)栈与递归
1.栈非常重要的一个应用是在程序设计语言中用来实现递归。递归是指在定义自身的同时又出现了对自身的调用。如果一个函数在其定义体内直接调用自己,则称其为直接递归函数;如果一个函数经过一系列的中间调用语句,通过其它函数间接调用自己,则称其为间接递归函数。
2.二叉树、广义表等结构其本身均具有固有的递归特性,采用递归法进行处理。
3.递归算法的前提:
(1)问题具有类同自身的子问题的性质,被定义项在定义中的应用具有更小的尺度。
(2)被定义项在最小尺度上有直接解。
4.设计递归算法的方法:
(1)寻找方法,将问题化为原问题的子问题求解(例n!=n*(n-1)!)。
(2)设计递归出口,确定递归终止条件(例求解n!时,当n=1时,n!=1)。
5.当递归函数调用时,应按照“后调用先返回”的原则处理调用过程,因此上述函数之间的信息传递和控制转移必须通过栈来实现。系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为它在栈顶分配一个存储区,而每当从一个函数退出时,就释放它的存储区。显然,当前正在运行的函数的数据区必在栈顶。
6.消除递归的方法
(1)在简单情况下,将递归算法可简化为线性序列执行,可直接转换为循环实现。
(2)基于栈的方式,即将递归中隐含的栈机制转化为由用户直接控制的明显的栈,利用堆栈保存参数。(第6章,树)
(2)队列
队列(Queue)是另一种限定性的线性表,它只允许在表的一端插入元素,而在另一端删除元素,队列具有先进先出的特性.
与线性表类似,队列也可以有两种存储表示,即顺序表示和链式表示。
(1)链队列
采用带头结点的链表结构,并设置一个队头指针和一个队尾指针。队头指针始终指向头结点,队尾指针指向当前最后一个元素。空的链队列的队头指针和队尾指针均指向头结点。
(2)循环队列
循环队列是队列的一种顺序表示和实现方法。与顺序栈类似,在队列的顺序存储结构中,我们用一组地址连续的存储单元依次存放从队头到队尾的元素,如一维数组当rear=MAXSIZE时,认为队满。但此时不一定是真的队满,因为随着部分元素的出队,数组前面会出现一些空单元。由于只能在队尾入队,使得上述空单元无法使用。我们把这种现象称为假溢出,真正队满的条件是rear-front=MAXSIZE。
为了解决假溢出使得队列空间充分利用,将顺序队列的数组看成一个环状的空间,规定最后一个单元的后继为第一个单元,称之为循环队列。
只凭front=rear无法判别队列的状态是“空”还是“满”。对于这个问题,可有两种处理方法:
(1)指向的空单元的后继单元是队头元素所在的单元时,则停止入队。这样一来,队尾指针永远追不上队头指针,所以队满时不会有front=rear。现在队列“满”的条件为(rear+1)mod MAXSIZE=front。判队空的条件不变,仍为rear=front。
(2)增设一个标志量的方法,以区别队列是“空”还是“满”,这种方法不损失空间。
2.算法
(1)栈
<1>顺序栈
顺序栈存储结构:
#define Stack_Size 50
typedef struct
{
StackElementType elem[Stack_Size];
int top;
}SeqStack;
- 初始化顺序栈
void InitStack(SeqStack *S)
{
S->top=-1;
}
- 进栈
算法思想:进栈时,判断当前栈是否已满
int Push(SeqStack *S, StackElementType x)
{
if(S->top==Stack_Size) return -1;
S->top++;
S->elem[S->top]=x;
return 1;
}
- 出栈
算法思想:出栈时,判断当前栈是否为空
int Pop(SeqStack *S, StackElementType *x)
{
if(S->top==-1) return -1;
*x=S->elem[S->top];
S->top--;
return 1;
}
- 读栈顶元素
算法思想:将栈顶元素读出,放到x所指空间,栈顶指针保持不变
注:实现GetTop操作时,可将参数(SeqStack *S)改为(SeqStack S)也就是将传地址方式改为传值方式。传值比传地址容易理解,但传地址比传值更节省时间与空间。
int GetTop(SeqStack *S, StackElementType *x)
{
if(S->top==-1||S->top==Stack_Size) return -1;
*x=S->elem[S->top];
return 1;
}
多站共享技术
两栈共享数据结构:
#define M 100
typedef struct
{
StackElementType Stack[M]; //Stack[M]为栈区
StackElementType top[2]; //top[0]和top[1]分别为两个栈顶指示器
}DqStack;
- 双端顺序栈初始化
void InitStack(DqStack *S)
{
S->top[0]=-1;
S->top[1]=M;
}
- 双端顺序栈进栈
int Push(DqStack *S ,StackElementType x ,int i)
{
if(S->top[0]+1==S->top[1]) return -1;
switch(i):
{
case 0:
S->top[0]++;
S->Stack[S->top[0]]=x;
break;
case 1:
S->top[1]--;
S->Stack[S->top[1]]=x;
break;
default:
return -1;
}
return 1;
}
- 双端顺序栈出栈
int Pop(DqStack *S, StackElementType *x, int i)
{
if(S->top[0]==-1||S->top[1]==M) return -1;
switch(i):
{
case 0:
*x=S->Stack[S->top[0]];
S->top[0]--;
break;
case 1:
*x=S->Stack[S->top[1]];
S->top[1]++;
break;
default:
return -1;
}
return 1;
}
<2>链栈
链栈结构:
typedef struct node
{
StackElementType data;
struct node *next;
}LinkStackNode;
typedef LinkStackNode *LinkStack;
- 进栈
int Push(LinkStack top, StackElementType x)
{
LinkStackNode *p;
p=(LinkStackNode*)malloc(sizeof(LinkStackNode));
p->data=x;
p->next=top->next;
top->next=p;
return 1;
}
- 出站
int Pop(LinkStack top, StackElementType *x)
{
LinkStackNode *p;
p=top->next;
top->next=p->next;
*x=p->data;
free(p);
return 1;
}
<3>栈与递归
- 汉纳塔
算法思想:当n=1时,问题比较简单,只要将编号为1的圆盘从塔座X直接移动到塔座Z上即可;当n>1时,需利用塔座Y作辅助塔座,若能设法将压在编号为n的圆盘上的n-1个圆盘从塔座X(依照上述原则)移至塔座Y上,则可先将编号为n的圆盘从塔座X移至塔座z上,然后再将塔座Y上的n-1个圆盘(依照上述原则)移至塔座Z上。
void hanoi(int n, char x, char y, char z)
//将x上从上到下编号为1至n,且按直径由小到大叠放的n个圆盘,按规定搬到z上,y用作辅助
{
if(n==1)
move(x,1,z); //将编号为1的圆盘从x移到z
else{
hanoi(n-1,x,z,y); //将x上标号为1至n-1的圆盘移到y上,z做辅助
move(x,n,z); //将编号为n的圆盘从x移动到z
hanoi(n-1,y,x,z); //将y上编号为1至n-1的圆盘移到z上,x做辅助
}
}
汉诺塔三个盘子递归调用过程
hanoi(3,A,B,C) //
hanoi(2,A,C,B) //
hanoi(1,A,B,C) //
move(A,1,C) //1从A移动到C
move(A,2,B) //2从A移动到B
hanoi(1,C,A,B) //
move(C,1,B) //1从C移动到B
move(A,3,C) //3从A移动到C
hanoi(2,B,A,C) //
hanoi(1,B,C,A)
move(B,1,A) //1从B移动到A
move(B,2,C) //2从B移动到C
hanoi(1,A,B,C)
move(A,1,C) //1从A移动到C
- 消除递归(简单递归消除)
在简单情况下,可将递归算法转换为线性操作序列,直接用循环实现。
a.单向递归
递归函数中虽然有一处以上的递归函数调用语句,但各次递归调用语句的参数只和主调用函数有关,相互之间参数无关,并且这些递归调用语句处于算法的最后。
如:斐波那契数列
''' 递归算法 时间复杂度O(2ⁿ)'''
Fib(int n)
{
if(n==0||n==1) return n;
else return Fib(n-1)+Fib(n-2);
}
''' 非递归算法 时间复杂度O(n)'''
int Fib(int n)
{
int x,y,z;
if(n==0||n==1) return n
else{
x=0;y=1;
for(i=2;i<=n;i++)
{
z=y; //z=Fib(i-1)
y=x+y; //y=Fib(i-1)+Fib(i-2),求Fib(i)
x=z; //x=Fib(i-1)
}
return y;
}
}
b.尾递归
尾递归是指递归调用语句只有一个,而且是处于算法的最后,尾递归是单向递归的特例
求n!的递归
''' 递归算法'''
long Fact(int n)
{
if(n==1) return 1;
return n*Fact(n-1);
}
''' 非递归算法'''
long Fact(int n)
{
int fac=1;
for(int i=1;i<=n;i++) //依次计算f(1)...f(n)
fac=fac*i; //f(i)=f(i-1)*i
return fac;
}
(2)队列
<1>链队列
链队列定义:
typedef struct Node
{
QueueElementType data;
struct Node * next;
}LinkQueueNode;
typedef struct
{
LinkQueueNode * front;
LinkQueueNode * rear;
}LinkQueue;
- 链队列初始化
int InitQueue(LinkQueue *Q)
{
Q->front=(LinkQueueNode*)malloc(sizeof(LinkQueueNode));
if(Q->front!=NULL)
{
Q->rear=Q->front;
Q->front->next=NULL;
return 1;
}
else return -1;
}
- 入队操作
int EnterQueue(LinkQueue *Q, QueueElementTpye x)
{
LinkQueueNode *p;
p=(LinkQueueNode*)malloc(sizeof(LinkQueueNode));
p->data=x;
p->next=NULL;
Q->rear->next=p;
Q->rear=p;
}
- 出队
int DeleteQueue(LinkQueue *Q, QueueElementType *x)
{
LinkQueueNode *p;
if(Q->front==Q->rear) return -1;
p=Q->front->next;
Q->front->next=p->next;
if(Q->rear==p) Q->rear=Q->front;
*x=p->data;
free(p);
return 1;
}
<2>循环队列
类型定义:
#define MAXSIZE 50
typedef struct
{
QueueElementType element[MAXSIZE];
int front;
int reat;
}SeqQueue;
- 循环队列初始化
void InitQueue(SeqQueue *Q )
{
Q->front=Q->rear=0;
}
- 循环队列入队
int EnterQueue(SeqQueue *Q, QueueElementType x)
{
if((Q->rear+1)%MAXSIZE==Q->front) return -1;
Q->element[Q->rear]=x;
Q->rear=(Q->rear+1)%MAXSIZE;
return 1;
}
- 循环队列出队
int DeleteQueue(SeqQueue *Q QueueElementType *x)
{
if(Q->rear==Q->front) return -1;
*x =Q->element[Q->front];
Q->front=(Q->front+1)%MAXSIZE;
return 1;
}