数据结构复习笔记 第三章 栈和队列

第一部分 栈

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

栈的定义

**栈(Stack)**是只允许在一端进行插入或删除操作的线性表
特点:后进先出

栈的基本操作

**InitStack(&S):初始化栈。**构造一个空栈 S,分配内存空间。
**DestroyStack(&S):销毁栈。**销毁并释放栈 S 所占用的内存空间。
Push(&S,x):进栈,若栈S未满,则将x加入使之成为新栈顶。
Pop(&S,&x):出栈,若栈S非空,则弹出栈顶元素,并用x返回。
**GetTop(S, &x):读栈顶元素。**若栈 S 非空,则用 x 返回栈顶元素
其他常用操作:
StackEmpty(S):判断一个栈 S 是否为空。若S为空,则返回true,否则返回false。

栈的实现

顺序栈

#define MaxSize 10//定义栈中元素的最大个数
typedef struct{
	ELemType data[MaxSize];//静态数组存放栈中元素
	int top;//栈顶指针
} SqStack;
1.栈的初始化
//初始化栈
void InitStack(SqStack &S){
	S.top=-1;//初始化栈顶指针
}
2.判栈空
//判断栈空
bool StackEmpty(SqStack S){
	if(S.top==-1)	//栈空
		return true; 
	else			//不空
		return false;
}
3.进栈操作
//新元素入栈
bool Push(SqStack &S,ElemType x){
	if(S.top==MaxSize-1) //栈满,报错
		return false;
	S.top = S.top + 1;//指针先加1
	S.data[S.top]=x;//新元素入栈
//以上两步等价于S.data[++S.top]=x;
	return true;
}

S.top = S.top + 1;//指针先加1 S.data[S.top]=x;//新元素入栈
等价于S.data[++S.top]=x;

4.出栈操作
//出栈操作
bool Pop(SqStack &S,ElemType &x){
	if(S.top==-1)//栈空,报错
		return false;
	x=S.data[S.top];//栈顶元素先出栈
	S.top= S.top-1;//指针再减1.
//以上两步等价于x=S.data[S.top--];
	return true;
}

x=S.data[S.top];//栈顶元素先出栈 S.top= S.top-1;//指针再减1.
等价于x=S.data[S.top--];

5.获取栈顶元素
//读栈顶元素
bool GetTop(SqStack s,ELemType &x){
	if(s.top==-1) //栈空,报错
		return false;
	x=S.data[S.top];//x记录栈顶元素
	return true;
}

栈满的条件:top == MaxSize

6.共享栈

利用栈底位置相对不变的特性,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分
别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如图3.3所示。
在这里插入图片描述
两个栈的栈顶指针都指向栈顶元素,top0=-1 时0号栈为空,top1=MaxSize时1号栈为空;仅当两个栈项指针相邻(top1-top0=1)时,判断为栈满。当0号栈进栈时top0先加1再赋值,1 号栈进栈时top1先减1再赋值;出栈时则刚好相反。
共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。其存取数据的时间复杂度均为O(1),所以对存取效率没有什么影响。

#define MaxSize 10//定义栈中元素的最大个数
typedef struct{
	ELemType data[MaxSize];//静态数组存放栈中元囊
	int top0;//0号栈找顶指针
	int top1;//1号枝线顶指针
} ShStack;

//初始化栈
void InitStack(ShStack &S){
	S.top0=-1;//初始化栈顶指针
	S.top1=MaxSize;
}

栈满条件:top0 + 1 == top1

链栈(单链表)

链栈结点的数据结构:

typedef struct Linknode{
	ElemType data;			//数据域
	struct Linknode *next;	//指针域
}LNode,*LiStack;//栈类型定义

单链表结点的数据结构:

typedef struct LNode{	//定义单链表结点类型
	ElemType data;		//每个节点存放一个数据元素
	struct LNode *next;	//指针指向下一个节点
}LNode,*LinkList;

差不多。

1.链栈的创建与进栈(单链表的头插法)
带头结点
//自改,不知对错
//带头结点,栈的创建与初始化
LiStack LStackBuild(LiStack &L) {//创建
	LNode *s;//声明新结点,此时s没有分配空间
	L=(LiStack)malloc(sizeof(LNode));//创建头结点
	L->next=NULL;//初始为空链
	Push(&L);
	return L;
}

//新元素入栈
bool Push(LiStack &L){ //链栈的进栈操作
	if(L==NULL)
		return false;
	int x;//设ElemType为整型
	scanf("%d",&x);//输入结点的值
	while(x!=9999){//输入9999表示结束建表
		s=(LiStack)malloc(sizeof(LNode)); //创建新结点
		s->data=x;
		s->next=L->next;
		L->next=s;//将新结点插入表中,L为头指针
		scanf("%d",&x);
	}
	return true;
}

大差不差,用单链表的写法也行。。

单链表(带头结点)的头插法

//带头结点
LinkList List_HeadInsert(LinkList &L) { //逆向建立单链表
	LNode *s;//声明新结点,此时s没有分配空间
	int x;//设ElemType为整型
	L=(LinkList)malloc(sizeof(LNode)); //创建头结点
	L->next=NULL;//初始为空链表
	scanf("%d",&x);//输入结点的值
	while(x!=9999) {//输入9999表示结束建表
		s=(LNode*)malloc(sizeof(LNode)); //创建新结点
		s->data=x;
		s->next=L->next;
		L->next=s;//将新结点插入表中,L为头指针
		scanf("%d", &x);
	}
	return L;
}
不带头结点
//自改,不知对错
//不带头结点时,创建与进栈相同
LiStack LStackBuild(LiStack &L) { 
	LNode *s;//声明新结点,此时s没有分配空间
	int x;//设ElemType为整型
		scanf("%d",&x);
		while(x!=9999) {//输入9999表示结束建表
			s=(LNode*)malloc(sizeof(LNode)); //创建新结点
			s->data=x;//数据域赋值
			s->next=L;//新结点的next指向NULL
			L=s;//将新结点插入表中,L为头指针
			scanf("%d",&x);
		}
	return L;
}

单链表(不带头结点)的头插法

//不带头结点(自己改的,可能有错)
LinkList List_HeadInsert(LinkList &L) { //逆向建立单链表
	LNode *s;//声明新结点,此时s没有分配空间
	int x;//设ElemType为整型
		scanf("%d",&x);
		while(x!=9999) {//输入9999表示结束建表
			s=(LNode*)malloc(sizeof(LNode)); //创建新结点
			s->data=x;//数据域赋值
			s->next=L;//新结点的next指向NULL
			L=s;//将新结点插入表中,L为头指针
			scanf("%d",&x);
		}
	return L;
}

不能说一摸一样,只能说完全一致。。

2.出栈(单链表的删除)
带头结点
typedef struct Linknode{
	ElemType data;			//数据域
	struct Linknode *next;	//指针域
}LNode,*LiStack;//栈类型定义

//带头结点
bool Pop(LiStack &L,ElemType &x){
	if(L=NULL||L->next=NULL)//栈未创建或为空栈
		return false;
	LNode *q=L->next;//令q指向被删除结点
	x = q->data;//用x返回元素的值
	L->next=q->next;//将*q结点从链中“断开”
	free(q);//释放结点的存储空间
	return true;//删除成功
}
不带头结点
typedef struct Linknode{
	ElemType data;			//数据域
	struct Linknode *next;	//指针域
}LNode,*LiStack;//栈类型定义

//不带头结点
bool Pop(LiStack &L,ElemType &x){
	if(L=NULL)//栈未创建或为空栈
		return false;
	LNode *q=L;//令q指向被删除结点
	x = q->data;//用x返回元素的值
	L=q->next;//将*q结点从链中“断开”
	free(q);//释放结点的存储空间
	return true;//删除成功
}
3.获取栈顶元素
//带头结点
bool GetTop(LiStack &L,ElemType &x){
	if(L=NULL||L->next=NULL)//栈未创建或为空栈
		return false;
	x = L->next->data;//用x返回元素的值
	return true;//删除成功
}
//不带头结点
bool GetTop(LiStack &L,ElemType &x){
	if(L=NULL)//栈未创建或为空栈
		return false;
	x = L->data;//用x返回元素的值
	return true;//删除成功
}
4.求链栈长
//带头结点
int Length(LinkList L){
	int len = 0; //统计表长
	LNode *p = L;
	while (p->next != NULL){
		p = p->next;
		len++;
	}
	return len;
}
//不带头结点
int Length(LinkList L){
	int len = 0; //统计表长
	LNode *p = L;
	while (p!= NULL){
		p = p->next;
		len++;
	}
	return len;
}

第二部分 队列

在这里插入图片描述
在这里插入图片描述
链式存储:
在这里插入图片描述

队列的定义

队列是只允许在一端进行插入,在另一端删除的线性表。
在这里插入图片描述
先进先出。

队列的基本操作

InitQueue(&Q):初始化队列,构造一个空队列Q。
DestroyQueue(&Q):销毁队列。销毁并释放队列Q所占用的内存空间。
EnQueue(&Q,x):入队,若队列Q未满,将x加入,使之成为新的队尾。
DeQueue(&Q,&x):出队,若队列Q非空,删除队头元素,并用x返回。
GetHead(Q,&x):读队头元素,若队列Q非空,则将队头元素赋值给x。
其他常用操作:
QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false。

队列的顺序实现

#define MaxSize 10//定义队列中元素的最大个数
typedef struct{
	ElemType data [MaxSize];//用静态数组存放队列元素
	int front,rear;//队头指和队尾指针
} SqQueue;

队尾指针指向的是队尾元素的后一个位置,做题的时候可能会出队尾指针指向队尾元素的题。
在这里插入图片描述

1.初始化

//初始化队列
void InitQueue(SqQueue &Q){
	Q.rear=Q.front=0;//初始时队头、队尾指针指向0
}

2.判空

//判断队列是否为空
bool QueueEmpty(SqQueue Q){
	if(Q.rear==Q.front) //队空条件
		return true;
	else
		return false;
}

3.判满

//判断队列是否已满
bool QueueEmpty(SqQueue Q){
	if((Q.rear+1)%MaxSize==Q.front) //队满条件
		return true;
	else
		return false;
}

4.入队操作

//入队
bool EnQueue(SqQueue &Q, ElemType x){
	if((Q.rear+1)%MaxSize==Q.front)
		return false;//队满则报错
	Q.data[Q.rear]=x;//将x插入队尾
	Q.rear=(Q.rear+1)% MaxSize; //队尾指针+1取模
	return true;
}

模运算(取余):两个整数 a,b,a%b 表示 a除以b所得的余数。(也是a MOD b)
用模运算将存储空间在逻辑上变成了==“环状”==;
在这里插入图片描述
(Q.rear+1)%MaxSize==Q.front判断队满

5.出队操作

//出队(删除一个队头元素,并用x返回)
bool DeQueue (SqQueue &Q, ElemType &x){
	if(Q.rear==Q.front)//判断队空
		return false; //队空则报错
	x=Q.data[Q.front];
	Q.front=(Q.front+1)%MaxSize;//队头指针后移
		return true;
}

队列元素个数:( rear + MaxSize - front )%MaxSize

不牺牲存储单元的方案一

#define MaxSize 10//定义队列中元素的最大个数
typedef struct{
	ElemType data [MaxSize];//用静态数组存放队列元素
	int front,rear;//队头指和队尾指针
	int size;//队列当前长度
} SqQueue;
//插入成功,size++;删除成功,size--;

//初始化
void InitQueue(SqQueue &Q){
	Q.rear=Q.front=0;//初始时队头、队尾指针指向0
	Q.size=0;
}

//判断队列是否为空
bool QueueEmpty(SqQueue Q){
	if(Q.size==0) //队空条件
		return true;
	else
		return false;
}

//判断队列是否已满
bool QueueEmpty(SqQueue Q){
	if(Q.size==MaxSize) //队满条件
		return true;
	else
		return false;
}

//入队
bool EnQueue(SqQueue &Q, ElemType x){
	if(Q.size==MaxSize)
		return false;//队满则报错
	Q.data[Q.rear]=x;//将x插入队尾
	Q.rear=(Q.rear+1)% MaxSize; //队尾指针+1取模
	Q.size++;
	return true;

//出队(删除一个队头元素,并用x返回)
bool DeQueue (SqQueue &Q, ElemType &x){
	if(Q.size==0)//判断队空
		return false; //队空则报错
	x=Q.data[Q.front];
	Q.front=(Q.front+1)%MaxSize;//队头指针后移
	Q.size--;
	return true;
}

不牺牲存储单元的方案二

#define MaxSize 10//定义队列中元素的最大个数
typedef struct{
	ElemType data [MaxSize];//用静态数组存放队列元素
	int front,rear;//队头指和队尾指针
	int tag;//最近进行的是删除/插入
} SqQueue;
//每次删除操作成功时,都令tag=0;
//每次插入操作成功时,都令tag=1;

//初始化
void InitQueue(SqQueue &Q){
	Q.rear=Q.front=0;//初始时队头、队尾指针指向0
	Q.tag=0;
}

//判断队列是否为空
bool QueueEmpty(SqQueue Q){
	if(Q.front==Q.rear&&Q.tag==0) //队空条件
		return true;
	else
		return false;
}

//判断队列是否已满
bool QueueEmpty(SqQueue Q){
	if(Q.front==Q.rear&&Q.tag==1) //队满条件
		return true;
	else
		return false;
}

//入队
bool EnQueue(SqQueue &Q, ElemType x){
	if(Q.front==Q.rear&&Q.tag==1)
		return false;//队满则报错
	Q.data[Q.rear]=x;//将x插入队尾
	Q.rear=(Q.rear+1)% MaxSize; //队尾指针+1取模
	Q.tag=1;
	return true;

//出队(删除一个队头元素,并用x返回)
bool DeQueue (SqQueue &Q, ElemType &x){
	if(Q.size==0)//判断队空
		return false; //队空则报错
	x=Q.data[Q.front];
	Q.front=(Q.front+1)%MaxSize;//队头指针后移
	Q.tag=0;
	return true;
}

队列的链式实现

typedef struct LinkNode{ // 链式队列结点
	ElemType data;
	struct L inkNode *next;
}LinkNode;
typedef struct{//链式队列
	LinkNode *front,*rear; //队列的队头和队尾指针
}LinkQueue;

在这里插入图片描述

初始化和判空(带头结点)

//初始化队列(带头结点)
void InitQueue(LinkQueue &Q){
	//初始时front. rear都指向头结点
	Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));
	Q.front->next=NULL;
}
//判断队列是否为空
bool IsEmpty(LinkQueue Q){
	if(Q.front= =Q.rear)
		return true;
	else
		return false;
}

在这里插入图片描述

初始化和判空(不带头结点)

//初始化队列(不带头结点)
void InitQueue(LinkQueue &Q) {
	//初始时front、rear都指向NULL
	Q.front=NULL;
	Q.rear=NULL; 
}
//判断队列是否为空(不带头结点)
bool IsEmpty(LinkQueue Q){
	if(Q.front==NULL)
		return true;
	else
		return false;
}

在这里插入图片描述

入队(带头结点)

//新元素入队(带头结点)
void EnQueue(LinkQueue &Q, ElemType x){
	LinkNode *s=(LinkNode * )malloc(sizeof(LinkNode));
	s->data=x;
	s->next=NULL;
	Q.rear->next=s;//新结点插入到rear之后
	Q.rear=s;//修改表尾指针
}

在这里插入图片描述

入队(不带头结点)

//新元素入队(不带头结点)
void EnQueue(L inkQueue &Q,ElemType x){
	LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));
	s->data=x;
	s->next=NULL;
	if(Q.front == NULL){ //在空队列中插入第一个元素
		Q.front =s;//修改队头队尾指针
		Q.rear=s;
	//不带头结点的队列,第一个元素入队时需要特别处理
	} else {
		Q.rear->next=s;//新结点插入到rear结点之后
		Q.rear=s;//修改rear指针
	}
}

在这里插入图片描述

出队(带头结点)

//队头元素出队(带头结点)
bool DeQueue(LinkQueue &Q, ElemType &x){
	if(Q.front==Q.rear)
		return false;//空队
	LinkNode *p=Q.front->next;
	x=p->data;//用变量x返回队头元素
	Q.front->next=p->next;//修改头结点next指针
	if(Q.rear==p)//此次是最后一个结点出队
		Q.rear=Q.front;//修改rear指针
	free(p);//释放结点空间.
	return true;
}

在这里插入图片描述

出队(不带头结点)

//队头元素出队(不带头结点)
bool DeQueue(LinkQueue &Q, ElemType &x){
	if(Q.front==NULL)
		return false;//空队
	LinkNode *p=Q.front;//p指向此次出队的结点
	x=p->data;//用变量x返回队头元素
	Q.front=p->next;//修改front指针
	if(Q.rear==p){//此次是最一-个结点出队
		Q.front=NULL;//front 指向NULL 
		Q.rear=NULL;//rear 指向NULL 
	free(p);//释放结点空间.
	return true;
}

在这里插入图片描述

队列满的条件

在这里插入图片描述

双端队列

在这里插入图片描述
在这里插入图片描述
考点:判断输出序列合法性。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第三部分 应用

栈的应用——括号匹配

#define MaxSize 10//定义栈中元素的最大个数
typedef struct{
	char data[MaxSize];//静态数组存放栈中元素
	int top;//栈顶指针
} SqStack;
//担心存满可用链栈

//初始化栈
void InitStack(SqStack &S)
//判断栈是否为空
bool StackEmpty(SqStack S)
//新元素入栈
bool Push(SqStack &S, char x)
//栈顶元素出栈,用x返回
bool Pop(SqStack &S, char &x)
//考试中可直接使用基本操作,建议简要说明接口

//算法如下:
bool bracketCheck(char str[], int length) {
	SqStack S;
	InitStack(S); //初始化一个栈
	for (int i=0;i<length;i++){
		if (str[i]=='('|| str[i]=='[' || str[i]=='{'){
			Push(S,str[i]); //扫描到左括号,入栈
		} else {
			if (StackEmpty(S)) //扫描到右括号,且当前栈空
				return false; //匹配失败
			char topElem;
			Pop(S,topElem);//栈顶元素出栈
			if(str[i]==')' && topElem!='(')
				return false;
			if(str[i]==']' && topElem!='[')
				return false;
			if(str[i]=='}' && topElem!='{')
				return false;
		}
	}
	return StackEmpty(S); //检索完全部括号后,栈空说明匹配成功
}

在这里插入图片描述
用栈实现括号匹配:
依次扫描所有字符,遇到左括号入栈,遇到右括号则弹出栈顶元素检查是否匹配。
在这里插入图片描述
遇到左括号就入栈;遇到右括号,就 “消耗”一个左括号
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
匹配失败情况:
①左括号单身②右括号单身③左右括号不匹配

流程图如下:
在这里插入图片描述

栈的应用——表达式求值

在这里插入图片描述

中缀表达式转后缀表达式

中缀转后缀的手算方法:
① 确定中缀表达式中各个运算符的运算顺序
② 选择下一个运算符,按照==「左操作数 右操作数 运算符」==的方式组合成一个新的操作数
③ 如果还有运算符没被处理,就继续 ②
在这里插入图片描述

运算顺序不唯一,因此对应的后缀表达式也不唯一:
在这里插入图片描述
注:客观来看两种都正确,只是“机算”的结果是前者
“左优先”原则:只要左边的运算符能先计算,就优先算左边的。
在这里插入图片描述
后缀表达式的手算方法:
从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数。(注意:两个操作数的左右顺序)

在这里插入图片描述

后缀表达式的计算(机算)

用栈实现后缀表达式的计算:
①从左往右扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
在这里插入图片描述

中缀表达式转前缀表达式(手算)

中缀转前缀的手算方法:
① 确定中缀表达式中各个运算符的运算顺序
② 选择下一个运算符,按照==「运算符 左操作数 右操作数」==的方式组合成一个新的操作数
③ 如果还有运算符没被处理,就继续 ②
“右优先”原则:只要右边的运算符能先计算,就优先算右边的
在这里插入图片描述

前缀表达式的计算

用栈实现前缀表达式的计算:
①从右往左扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①

中缀表达式转后缀表达式(手算)

中缀转后缀的手算方法:
① 确定中缀表达式中各个运算符的运算顺序
② 选择下一个运算符,按照「左操作数 右操作数 运算符」的方式组合成一个新的操作数
③ 如果还有运算符没被处理,就继续 ②
“左优先”原则:只要左边的运算符能先计算,就优先算左边的

中缀表达式转后缀表达式(机算)

初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。
从左到右处理各个元素,直到末尾。可能遇到三种情况:
① 遇到操作数。直接加入后缀表达式。
② 遇到界限符。遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。注意:“(”不加入后缀表达式。
③ 遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符(* / 优先级高于 + -),并加入后缀表达式,若碰到“(” 或栈空则停止。之后再把当前运算符入栈。
在这里插入图片描述
在这里插入图片描述

中缀表达式的计算(用栈实现)

中缀转后缀+后缀表达式求值 两个算法的结合
用栈实现中缀表达式的计算:
(1)初始化两个栈,操作数栈运算符栈
(2)若扫描到操作数,压入操作数栈
(3)若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
在这里插入图片描述

栈的应用——递归

递归的精髓在于能否将原始问题转换为属性相同但规模较小的问题。
在递归调用的过程中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储,递归次数过多容易造成栈溢出等。而其效率不高的原因是递归调用过程中包含很多重复的计算。
递归的效率低下,但优点是代码简单,容易理解。
将递归算法转换为非递归算法,通常需要借助栈来实现这种转换。
在这里插入图片描述
递归调用时,函数调用栈可称为“递归工作栈”
每进入一层递归,就将递归调用所需信息压入栈顶
每退出一层递归,就从栈顶弹出相应信息

队列的应用

队列应用——树的层次遍历

队列应用——图的广度优先遍历

队列在操作系统中的应用

多个进程争抢着使用有限的系统资源时,FCFS(First Come First Service)是一种常用策略。

特殊矩阵的压缩存储

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值