数据结构学习--第3章(1)

一、栈

定义:
只允许在一端进行插入或删除的操作的线性表
操作受限的线性表

栈的操作特性:后进先出(LIFO)
栈的数学性质:
n个元素进栈,出栈元素不同排列组合的个数: 1/(n+1) * C2nn

栈的顺序存储结构

采用顺序存储的栈称为顺序栈
利用一组地址连续的存储单元存放栈底到栈顶的数据元素,同时附设一个 指针(top) 指示栈顶元素位置。

栈的存储类型描述

与顺序表类似

#define MaxSize 50
typedef struct{
	int data[MaxSise];//静态数组
	int top;
}SqStack;
  1. 栈顶指针: S.top
  2. 进栈操作:
    栈不满时,栈顶指针先+1,再送值到栈顶元素。
  3. 出栈操作:
    栈不空时,先栈顶元素,再将栈指针 -1
  4. (若top初始值赋值为-1)
    栈空条件:S.top==-1;
    栈满条件:S.top==MaxSize-1;
    栈 长 : S.top+1

顺序栈的基本运算

== 创、增、删、查都是O(1)复杂度==

初始化
void InitStack(SqStack &S){//注意&符号
	S.top = -1;
}
判栈空
bool StackEmpty(SqStack S){
	if(top == -1){
		return true;
	}else{
		return false;
	}
}
进栈
bool Push(SqStack &S,int x){//注意&符号
	if(S.top==S..MaxSize-1) return false;
	
	S.data[++S.top] = x;
	return true;
}
出栈

数据还残留再内存,逻辑上被删除。

bool Pop(SqStack &S,int &x){
	if(S.top == -1) return false;
	
	x = S.data[S.top--];
	return true;
}

若top初值为1:

  1. 入栈:先入栈再top+1
  2. 出栈:先top-1再出栈
读栈顶元素
bool GetTop(SqStack &S,int &x){
	if(top == -1) return false;
	
	x = S.data[S.top];
	return true;
}

共享栈

利用栈底位置相对不变性质,可让两个顺序栈共享一个一维数组空间。(两个栈从两边往中间增长)
共享栈可以节省存储空间,降低发生上溢的可能(栈顶指针超出最大范围)

#define MaxSize 10
typedef struct{
	int data[MaxSize];
	int top0;//0号栈栈顶指针
	int top1;//1号栈栈顶指针
}ShStack;
//初始化栈
void InitStack(ShStack &S){
	S.top0 = -1;
	S.top1 = MaxSize;
}

栈满条件:top1=top0+1

栈的链式存储结构

采用链式存储的栈成为链栈
优点:

  1. 便于多个栈共享存储空间和提高效率
  2. 不存在栈满上溢的情况
  3. 通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。(推荐不带头节点的)

存储类型描述

typedef struct Linknode{
	int data;
	struct Linknode *next;
}*LiStack;

操作

进栈对应单链表的头插法
在这里插入图片描述

出栈对应单链表的后删操作
在这里插入图片描述

初始化
void InitStack(LinkStack &S){
	S = (LinkStack)malloc(sizeof(Linknode));
	S->next = NULL;
}
判空
bool isEmpty(LinkStack S){

	if(S->next==NULL) return true;
	
	return false;
}
入栈
bool Push(LinkStack &S,int e){
	LinkStack p; 
	p=(LinkStack)malloc(sizeof(Linknode));
	
	if(p==NULL) return false;
	
	p->data = e;
	p->next = S->next
	S->next = p;
	
	return true;
}
出栈
bool Pop(LinkStack &S,int &x){
	if(S->next = NULL) return false;
	
	LinkStack p = S->next;
	x = p->data;
	S->next = p->next;
	free(p);
	return true;
}
取栈顶元素
boll GetTop(LinkStack S,int &x){
	if(S->next = NULL) return false;
	
	x = S->next->data;
	
	return false;
}

二、队列

定义:

也是一种操作受限的线性表;
只允许在表的一端进行插入(入队),在表的另一端进行删除(出队)。
操作特性:先进先出(FIFO)

在这里插入图片描述

队列的顺序实现

分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针front队尾指针

存储类型描述

#define MaxSize 50
typedef struct{
	int data[MaxSize];
	int front,rear;
}SqQueue;
  1. 判空:Q.front == Q.rear
  2. 进栈操作:队不满时,先送值到队尾元素,再将队尾指针 -1
  3. 出队操作:队不空时,先取队头元素值,再将队头指针 +1.

操作

初始化

<rear和front初始化为0,表示front指向最后一个元素所在位置,rear指向下一个元素放置位置>

void InitQueue(SqQueue &Q){
	Q.rear = Q.front = 0;
}
判空
bool QueueEmpty(SqQueue Q){
	if(Q.rear == Q.front) return true;
	else return fasle;
}
入队
bool EnQueue(SqQueue &Q,int x){
	if(栈满) return false;
	Q.data[Q.rear++] = x;//先添后+1
	return true;
}

在这里插入图片描述
注意
不能用Q.rear == MaxSize作为队满条件。
上图最后一种状态,满足Q.rear == MaxSize,出现上溢出,是一种假溢出

循环队列(解决假溢出)

把存储队列的元素的表从逻辑上视为一个环,称循环队列

  1. 初始时:Q.front = Q.rear = 0;
  2. 队头元素进1:Q.front = (Q.front+1)%MaxSize;
  3. 队尾元素进1:Q.rear = (Q.rear+1)%MaxSize;
  4. 队列长度:(Q.rear-Q.front+MaxSize)%MaxSize
判空条件的讨论

若入队元素快于出队元素速度,rear很快就会赶上front,则对空和堆满条件都是Q.front ==Q.rear;

  1. 牺牲一个存储单元来区分对空和堆满(常用)

队满条件(Q.rear+1)%MaxSize==Q.front
队空条件Q.front==Q.rear
队中元素个数(Q.rear-Q.front+Q.MaxSize)%Q.MaxSize

  1. 类型中添加表示元素个数的数据成员

入队成功:size++;出队成功:size–

  1. 队空条件:Q.size == 0
  2. 队满条件Q.size == MaxSize

队满和队空都会出现Q.front==Q.rear

  1. 类型中添加tag数据成员

最近进行的是删除:tag=0;最近进行的是插入:tag=1;
只有删除才会导致队空;只有插入才会导致队满。
队满条件(Q.rear==Q.front) && (tag==1)
队空条件(Q.rear==Q.front) && (tag==0)

循环队列操作

1.初始化

void InitQueue(SqQueue &Q){
	Q.rear = Q.front=0;
}
  1. 判空
bool isEmpty(SqQueue Q){
	if(Q.rear = Q.front) return true;
	return false;
}
  1. 入队
bool EnQueue(SqQueue &Q,int e){
	if((Q.rear+1)%MaxSize==Q.front) return false;//是否队满
	Q.data[Q.rear]=e;
	Q.rear = (Q.rear+1)%MaxSize;
	return true;
}
  1. 出队
bool DeQueue(SqQueue &Q,int &x){
	if(Q.rear = Q.front) return false;//判队空
	
	Q.front = (Q.front+1)%MaxSize;
	return true;
}

队列的链式存储

队列的链式表示称为链队列,实际上是一个同时带有队头指针和队尾指针的单链表。
(注意区分带头结点和不带头结点)

存储类型描述

typedef struct{//链式队列结点
	int data;
	struct LinkNode *next;
}LinkNode;

typedef struct{//链式队列
	LinkNode *front,*rear//链式队列的头尾结点
}LinkQueue;
基本操作
  1. 初始化
void InitQueue(LinkQueue &Q){
	Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));
	Q.front->next = NULL;
}
  1. 判空
bool IsEmpty(LinkQueue Q){
	if(Q.front==Q.rear) return true;
	return false;
}
  1. 入队
void EnQueue(LinkQueue &Q,int e){
	LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
	
	s->data = e;s->next = NULL;
	
	Q.rear->next = s;
	Q.rear = s;
}
  1. 出队
bool DeQueue(LinkQueue &Q,int &x){
	if(Q.front== Q.rear) return false;
	
	LinkNode *p = Q.front->next;
	x = p->data;
	Q.front = p->next;
	//若只剩最后一节点,rear也得变
	if(Q.rear==p) Q.rear =  Q.front;
	free(p);
	
	return true;
}

双端队列

  1. 双端队列是指允许两端都可以进行入队和出队操作的队列。
  2. 其元素的逻辑结构仍然是线性结构
  3. 将队列的两端分别称为前端后端

输出受限的双端队列
允许在一端进行插入和删除,但在另一端只允许插入的双端队列。

输入受限的双端队列
允许在一端进行插入和删除,但在另一端只允许删除的双端队列。

三、栈和队列的应用

1.栈→括号匹配

最后出现的左括号最先被匹配(LIFO)

算法思想:

  1. 初始设置一个空栈,顺序读入括号。
  2. 若是右括号,则或者使置于栈顶的左括号消解(左括号出栈),或者是不合法的情况(左右括号不批配)程序结束。
  3. 若只左括号,压入栈中

算法结束时,栈为空,否则括号序列不匹配

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);
}

2.栈→表达式求值

在这里插入图片描述

中缀转后缀(手算)

在这里插入图片描述

  1. 运算顺序不唯一,因此后缀表达式不唯一。
  2. 保证手算和计算结果相同,采用左优先原则:只要左边的原算法符能算就先算左边的。
  3. 采用左优先可以保证顺算顺序唯一。
    在这里插入图片描述

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

初始化一个栈,用于保存暂时还不能确定运算顺序的运算符
从左到右处理各个元素,直到末尾。可能出现三种情况:
、遇到操作数。直接加入后缀表达式
、遇到界限符"(“直接入栈:遇到”)“则依次弹出站内运算符并加入后缀表达式,直到弹出“(”。“(”不加入后缀表达式
、遇到运算符。依次弹出栈中优先级高高于或等于当前运算符的所有运算符(*/优先级高于±),并加入后缀表达式,若碰到”("或栈空则停止。之后再把运算符进栈。
按上述方法处理完后,将栈中剩余运算符依次弹出,并加入后缀表达式。

后缀中字母顺序和中缀中的一样

  1. 操作符加入后缀表达式
    这里是引用
  2. 运算符入栈,栈中无高于等于+的运算符
    在这里插入图片描述
  3. 操作数加入后缀表达式
    在这里插入图片描述
  4. 运算符入栈,无比*优先级高或者相等的
    在这里插入图片描述
  5. 界限符"("直接入栈
  6. 操作数直接加入后缀表达式
    在这里插入图片描述
  7. 运算符-,将栈中优先级高于或等于-的运算符输出,碰到"("停止。-入栈。
    在这里插入图片描述
  8. 操作数
    在这里插入图片描述
  9. 界限符")",依次弹出栈中运算符并加入后缀表达式,直到碰到")"为止在这里插入图片描述
  10. 运算符-,弹出栈中优先级比-高或等于的运算符
    在这里插入图片描述
  11. 操作数
    在这里插入图片描述
  12. 运算符/
    在这里插入图片描述
  13. 操作数
    在这里插入图片描述
  14. 弹出栈中剩余操作符
    在这里插入图片描述

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

在这里插入图片描述

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

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

注意:

  1. 先出栈的时右操作数
  2. 若表达合法,则栈中只会留下最后一个元素(最终结果)。

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

在这里插入图片描述
采用**右优先原则**:只要右边的运算符能先计算,就优先算右边的。
在这里插入图片描述

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

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

注意:
先出栈的是左操作数

3. 栈→递归

函数调用特点:
最后被调用的函数最先被执行结束。

求阶乘

int factorial(int n){
	if(n==0||n==1){
		return 1;
	}
	return n*factorial(n-1);
}

递归缺点:
效率低,太多层递归可能会导致栈溢出;可能包含很多重复计算

4. 队列

  1. 图的广度优先遍历
  2. 操纵系统进程调度算法:FCFS
  3. 页面替换算法的:FIFO

补充:

  1. 栈:迷宫求解;进栈转换;
  2. 队列:缓冲区;
  3. 递归一般效率都比较低,因为包含许多重复计算。
  4. 执行函数时,局部变量一般采用栈来存储。
  5. 消除递归通常需要栈来存储,也可以不用。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值