栈和队列是限定插入和删除只能在表的 “端点” 进行的线性表
栈和队列的特点
栈
定义: 栈(stack) 是一个特殊的线性表,是限定仅在一端(通常是表尾)进行插入和删除操作的线性表。
又称为 后进先出 的线性表。
逻辑结构: 与线性表相同,认为一对一的关系。
存储结构: 用顺序栈或者链栈存储均可,但以顺序栈更常见。
运算规则: 只能在栈顶运算,且访问节点时依照后进先出的原则。
实现方式: 关键是编写入栈和出栈函数,具体实现依顺序栈或链栈的不同而不同。
入栈:
出栈:
队列
定义: 只能在表的一段进行插入运算,在表的另一端进行删除运算的线性表。头删尾插
逻辑结构: 与线性表相同,仍为一对一关系。
存储结构: 顺序队或链队,以循环队列更常见。
运算规则: 只能在队首和队尾运算,且访问结点时依照先进先出的原则。
实现方式: 关键是掌握入队和出队操作,具体实现依顺序队或链队的不同而不同。
栈的表示和实现
栈的案例
上面这个例子,利用了栈的 后进先出 的特性,每一次除以 8 后将余数存储在栈中,最后依次输出栈里面的内容,就将十进制转换为了八进制。
上面这个案例,也是这个样子,当我们遇到 ( 或者 [ 的时候,我们就把他们存储在栈中,但是当我们遇到了 ) 或者 ] 就查询栈中栈顶的元素是否匹配,匹配就继续下去,不匹配我们就输出 FALSE。
顺序栈的操作的实现
注意事项
为了操作方便,我们通常将 top 指针指向真正栈顶元素之上的下标地址。
- 空栈:base == top 是栈空的标志
- 栈满:top-base == stackSize
- 上溢:栈已经满,又要压入元素
- 下溢:栈已经空,还要弹出元素
数据结构的准备
/* 顺序栈结构 */
typedef struct
{
SElemType *base;
SElemType *top;
int stackSize; //当前分配的储存空间
}SqStack;
顺序表的入栈
- 判断是否栈满,若满则出错(上溢)
- 元素 e 压入栈顶
- 栈指针加 1
Status Push(SqStack &s,SElemType e){
// 栈满
if(s.top - s.base == s.stackSize) return ERROR;
*s.top++ = e;
return OK;
}
顺序表的出栈
- 判断是否栈空,若空则出错(下溢)
- 获取栈顶元素e
- 栈顶指针-1
Status Pop(SqStack &s,SElemType &e){
//若栈不空,则删除s的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
if(s.top == s.base) return ERROR;
e = *--s.top;
}
完整实现
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */
typedef int Status;
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */
/* 顺序栈结构 */
typedef struct
{
SElemType *base;
SElemType *top;
int stackSize; //当前分配的储存空间
}SqStack;
/* 初始化顺序表 */
Status InitStack(SqStack &s){
s.base = (SElemType *)malloc(MAXSIZE * sizeof(SElemType));
if(!s.base) exit(-1);
s.top = s.base;
s.stackSize = MAXSIZE;
return OK;
}
/* 判断顺序栈是否为空 */
/* 如果栈为空,返回TURE;否则返回FALSE */
Status StackEmpty(SqStack &s){
if(s.top == s.base) return TRUE;
else return FALSE;
}
/* 求栈的长度 */
Status StackLength(SqStack s){
return s.top - s.base;
}
/* 清空顺序栈 */
/* 并不需要将所有指针指向的数据全部清空,我们就当他们是空的就行了 */
Status ClearStack(SqStack s){
if(s.base) s.top = s.base;
return OK;
}
/* 销毁顺序栈 */
Status DestoryStack(SqStack &s){
if(s.base){
free(s.base);
s.stackSize = 0;
s.base = NULL;
s.top = NULL;
}
return OK;
}
/* 顺序栈的入栈 */
Status Push(SqStack &s,SElemType e){
// 栈满
if(s.top - s.base == s.stackSize) return ERROR;
*s.top++ = e;
return OK;
}
/* 顺序栈的出栈 */
Status Pop(SqStack &s,SElemType &e){
//若栈不空,则删除s的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
if(s.top == s.base) return ERROR;
e = *--s.top;
return OK;
}
/* 从栈底到栈顶,依次输出元素 */
void StackTraverse(SqStack s){
SElemType *cur = s.base;
printf("当前栈的元素为:");
while(cur < s.top){
printf(" %d",*cur);
cur++;
}
printf("\n");
}
int main(){
int j;
SqStack s;
int e;
// 初始化顺序表
j = InitStack(s);
printf("初始化顺序表:%d (1表示成功,0表示失败)\n",j);
// 插入元素
for(j=1;j<=10;j++){
Push(s,j);
}
StackTraverse(s);
// 删除元素
Pop(s,e);
printf("删除的元素为: %d\n",e);
StackTraverse(s);
// 判断栈是否为空
j = StackEmpty(s);
printf("数组是否为空:%d(1表示空,0表示非空)\n",j);
// 判断栈中的元素个数
j = StackLength(s);
printf("数组的程度为 %d \n",j);
// 清空栈
ClearStack(s);
j = StackEmpty(s);
printf("数组是否为空:%d(1表示空,0表示非空)\n",j);
// 销毁栈
DestoryStack(s);
return 0;
}
链栈的操作和实现
注意事项
链栈是运算受限的单链表,只能在链表头部进行操作。
- 链表的头指针就是栈顶
- 不需要头结点
- 基本不存在栈满的情况
- 空栈相当于头结点指向空
- 插入和删除仅在栈顶处执行
链栈的实现
主要关注初始化,插入和删除的操作
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */
typedef int Status;
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */
/* 链栈结构 */
typedef struct StackNode
{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStackPtr;
typedef struct
{
LinkStackPtr top;
int count;
}LinkStack;
/* 构造一个空栈 */
Status InitStack(LinkStack &s){
s.top = (LinkStackPtr)malloc(sizeof(StackNode));
if(!s.top) return ERROR;
s.top = NULL;
s.count = 0;
return OK;
}
/* 将链栈置为空栈 */
Status ClearStack(LinkStack &s){
LinkStackPtr p,q;
p = s.top;
while(p){
q=p;
p=p->next;
free(q);
}
s.count = 0;
s.top = NULL;
return OK;
}
/* 判断s是否为空栈 */
Status StackEmpty(LinkStack s){
if(s.count == 0) return TRUE;
else FALSE;
}
/* 返回元素长度 */
int StackLength(LinkStack s)
{
return s.count;
}
/* 获取元素 */
Status GetTop(LinkStack s,SElemType e){
if(s.top == NULL) return ERROR;
else e = s.top->data;
return OK;
}
/* 插入元素 */
Status Push(LinkStack &s,SElemType e){
LinkStackPtr p=(LinkStackPtr)malloc(sizeof(StackNode));
p->data=e;
p->next=s.top; /* 把当前的栈顶元素赋值给新结点的直接后继,见图中① */
s.top=p; /* 将新的结点s赋值给栈顶指针,见图中② */
s.count++;
return OK;
}
/* 删除元素 */
Status Pop(LinkStack &s,SElemType e)
{
LinkStackPtr p;
if(StackEmpty(s)) return ERROR;
e=s.top->data;
p=s.top; /* 将栈顶结点赋值给p */
s.top=s.top->next; /* 使得栈顶指针下移一位,指向后一结点,见图中④ */
free(p); /* 释放结点p */
s.count--;
return OK;
}
Status StackTraverse(LinkStack s)
{
LinkStackPtr p;
p=s.top;
while(p)
{
printf(" %d",p->data);
p=p->next;
}
printf("\n");
return OK;
}
int main(){
int j;
LinkStack s;
int e;
if(InitStack(s)==OK)
for(j=1;j<=10;j++)
Push(s,j);
printf("栈中元素依次为:");
StackTraverse(s);
Pop(s,e);
printf("弹出的栈顶元素 e=%d\n",e);
printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));
GetTop(s,e);
printf("栈顶元素 e=%d 栈的长度为%d\n",e,StackLength(s));
ClearStack(s);
printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));
return 0;
}
栈和递归
递归的定义
若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的。
如果一个过程直接或者间接地调用自己,则称这个过程是递归的过程。
eg:递归求n的阶乘
long Fact(long n){
if(n==0) return 1; // 基本项
else return n*Fact(n-1); // 归纳项
}
分治法求递归
分治法:对于一个较为复杂的问题,能够分解成几个相对简单且解法相同或类似的字问题来求解。
必备的三个条件:
- 能将一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些处理对象的变化时是有规律的。
- 可通过上述变化而使问题简化
- 必须有一个明确的递归出口,或称递归的边界。
分治法求递归的基本形式:
void p(参数表){
if(递归结束条件) 可直接求解步骤; ---基本项
else p(较小的参数); ---归纳项
}
递归函数调用过程
调用前:
- 将实参,返回地址等等传递给被调用函数
- 为被调用函数的局部变量分配存储区
- 将控制转移到被调用函数入口
调用后: 1.保存被调用函数的计算结果 2.释放被调用函数的数据区 3.依照被调用函数保存的返回地址将控制转移到调用函数
解释一下这个函数的调用吧:
- 首先进入
main函数,执行fact(3)之前的代码- 然后进入
fact函数,执行mypow(3.5,2)之前的函数- 执行
mypow函数里面的代码,并返回到zfact执行完毕后,返回到y执行剩下的代码
递归的优缺点
优点: 结构清晰,程序易读
缺点: 每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,回复状态信息。时间开销大。
顺序队列的操作和实现
注意事项
上图为入队,出队和空队的情况
真溢出和假溢出的情况
这种情况,front=0, rear=MAXSIZE,当再有元素进入队列的时候,这自然是 真溢出。
但是当这种情况,很显然, front != 0 , reae == MAXSIZE 下面还有空间未使用,这就是假溢出。
解决假上溢的方法:
-
将对中元素依次向对头方向移动。
缺点:浪费时间。每移动一次,队中元素都要移动。
-
将对空间设想成为一个循环的表,即分配给队列的 m 个储存单位可以循环使用,当 rear 为 MAXSIZE 时,又可以从头使用空着的空间。当
front为MAXSIZE时,也是一样的。
此时,我们会发现,对空和队满的情况都是
front == rear。
解决方法:
1. 另外设置一个标志以区分对空和对满
2. 另设一个变量,记录元素个数
3. 少用一个元素空间
顺序队的实现
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20
typedef int Status;
typedef int QElemType;
/* 循环队列的顺序存储结构 */
typedef struct
{
QElemType *base;
int front; /* 头指针 */
int rear; /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SqQueue;
/* 初始化列表 */
Status InitQueue(SqQueue &Q)
{
Q.base = (QElemType*)malloc(MAXSIZE*sizeof(QElemType));
if(!Q.base) exit(-1);
Q.front=0;
Q.rear=0;
return OK;
}
/* 求队列的长度 */
int QueueLength(SqQueue Q){
return ((Q.rear-Q.front+MAXSIZE)%MAXSIZE);
}
/* 判断队列是否为空 */
Status QueueEmpty(SqQueue Q)
{
if(Q.front==Q.rear) /* 队列空的标志 */
return TRUE;
else
return FALSE;
}
/* 循环队列入队 */
Status EnQueue(SqQueue &Q,QElemType e){
if((Q.rear+1)%MAXSIZE == Q.front) return ERROR; // 队满
Q.base[Q.rear] = e;
Q.rear = (Q.rear+1)%MAXSIZE;
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)%MAXSIZE;
return OK;
}
/* 返回头元素 */
QElemType GetHead(SqQueue Q){
// 队列不为空,返回头指针元素的值
if(Q.front != Q.rear) return Q.base[Q.front];
}
/* 从队头到队尾依次对队列Q中每个元素输出 */
Status QueueTraverse(SqQueue Q)
{
int i;
i=Q.front;
printf("当前队列元素为:");
while((i+Q.front)!=Q.rear)
{
printf(" %d",Q.base[i]);
i=(i+1)%MAXSIZE;
}
printf("\n");
return OK;
}
/* 将Q清为空队列 */
Status ClearQueue(SqQueue Q)
{
Q.front=Q.rear=0;
return OK;
}
int main()
{
Status j;
int i=0,l;
QElemType d;
SqQueue Q;
InitQueue(Q);
printf("初始化队列后,队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
printf("请输入整型队列元素(不超过%d个),-1为提前结束符\n",MAXSIZE-1);
do
{
d=i+100;
if(d==-1)
break;
i++;
EnQueue(Q,d);
}while(i<MAXSIZE-1);
printf("队列长度为: %d\n",QueueLength(Q));
printf("现在队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
printf("连续%d次由队头删除元素,队尾插入元素:\n",MAXSIZE);
for(l=1;l<=MAXSIZE;l++)
{
DeQueue(Q,d);
printf("删除的元素是%d,插入的元素:%d \n",d,l+1000);
d=l+1000;
EnQueue(Q,d);
}
l=QueueLength(Q);
QueueTraverse(Q);
j=GetHead(Q);
if(j)
printf("现在队头元素为: %d\n",d);
ClearQueue(Q);
printf("清空队列后, 队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
return 0;
}
链栈的操作和实现
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */
typedef struct QNode /* 结点结构 */
{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct /* 队列的链表结构 */
{
QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;
/* 初始化链队 */
Status InitQueue(LinkQueue &Q)
{
Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q.front)
exit(OVERFLOW);
Q.front->next=NULL;
return OK;
}
/* 清空队列 */
Status ClearQueue(LinkQueue Q)
{
QueuePtr p,q;
Q.rear=Q.front;
p=Q.front->next;
Q.front->next=NULL;
while(p)
{
q=p;
p=p->next;
free(q);
}
return OK;
}
/* 销毁链队列 */
Status DestroyQueue(LinkQueue &Q){
while(Q.front){
Q.rear = Q.front->next;
free(Q.front);
Q.front = Q.rear;
}
return OK;
}
/* 判断是否为空队列 */
Status QueueEmpty(LinkQueue Q)
{
if(Q.front==Q.rear)
return TRUE;
else
return FALSE;
}
/* 求队列的长度 */
int QueueLength(LinkQueue Q)
{
int i=0;
QueuePtr p;
p=Q.front;
while(Q.rear!=p)
{
i++;
p=p->next;
}
return i;
}
/* 链队列的入队 */
Status EnQueue(LinkQueue &Q,QElemType e){
QueuePtr p=(QueuePtr)malloc(sizeof(QNode));
if(!p) exit(-1);
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}
/* 链队列的出队 */
Status DeQueue(LinkQueue &Q,QElemType e){
if(Q.front == Q.rear) return ERROR;
QueuePtr p = Q.front->next;
e=p->data;
Q.front->next = p->next;
// 链队列中只有一个元素
if(Q.rear == p) Q.rear = Q.front;
free(p);
return OK;
}
/* 获取头结点 */
Status GetHead(LinkQueue Q,QElemType e)
{
QueuePtr p;
if(Q.front==Q.rear)
return ERROR;
p=Q.front->next;
e=p->data;
return OK;
}
/* 输出队列 */
Status QueueTraverse(LinkQueue Q)
{
QueuePtr p;
p=Q.front->next;
while(p)
{
printf(" %d",p->data);
p=p->next;
}
printf("\n");
return OK;
}
int main()
{
int i;
QElemType d;
LinkQueue q;
i=InitQueue(q);
if(i)
printf("成功地构造了一个空队列!\n");
printf("是否空队列?%d(1:空 0:否) \n",QueueEmpty(q));
printf("队列的长度为%d\n",QueueLength(q));
EnQueue(q,-5);
EnQueue(q,5);
EnQueue(q,10);
printf("插入3个元素(-5,5,10)后,队列的长度为%d\n",QueueLength(q));
printf("是否空队列?%d(1:空 0:否) \n",QueueEmpty(q));
printf("队列的元素依次为:");
QueueTraverse(q);
i=GetHead(q,d);
if(i==OK)
printf("队头元素是:%d\n",d);
DeQueue(q,d);
printf("删除了队头元素%d\n",d);
i=GetHead(q,d);
if(i==OK)
printf("新的队头元素是:%d\n",d);
ClearQueue(q);
printf("清空队列后,q.front=%u q.rear=%u q.front->next=%u\n",q.front,q.rear,q.front->next);
DestroyQueue(q);
printf("销毁队列后,q.front=%u q.rear=%u\n",q.front, q.rear);
return 0;
}
结语
文章如果有不正确的地方,欢迎指正,共同学习,共同进步。
若有侵权,请联系作者删除。