数据结构与算法(四):栈和队列

栈的定义

栈是一种重要的线性结构,栈是前面讲过的线性表的一种具体形式。
栈是一种后进先出的结构,例如子弹的压入和发出,例如c语言中的函数。
栈是一种后进先出(Last in first out,LIFO)的线性表,它要求只在表尾进行删除和插入操作。

  • 栈的元素必须“后进先出”
  • 栈的操作只能在这个线性表的表尾进行。
  • 对于栈来说,这个表尾称为栈的栈顶(top),相应的表头称为栈底(bottom)

栈的插入和删除操作

  • 栈的插入操作(Push),叫做进栈,也成为压栈、 入栈。类似于子弹压入弹夹的动作。
  • 栈的删除操作(Pop),叫做出栈,也称为弹栈。如同弹夹中的子弹出夹。

栈的顺序存储结构

  • 因为栈的本质是一个线性表,线性表有两种存储形式,那么栈也分为栈的顺序存储结构和栈的链式存储结构
  • 最开始的栈不含有任何数据,叫做空栈,此时栈顶就是栈底。然后数据从栈顶进入,栈底分离,整个栈的当前容量变大。数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。
  • 栈的抽象数据类型定义
ADT Stack{
	数据对象:
	D={ai|ai∈ElemSet,i=1,2....n,n≥0}
	数据关系:
	R1={<ai-1,ai>|ai-1,ai∈D,i=2,..n}
	约定an端为栈顶,a1端为栈底。
	基本操作:初始化、进栈、出栈、取栈顶元素等
}ADT Stack
typedef struct{
	ElemType *base; //栈底指针
	ElemType *top;  //栈顶指针
	int stackSize;  //栈可用最大容量  
}sqStack;
  • 这里定义了一个顺序存储的栈,它包含了三个元素:base,top,stacksize,其中base是指向栈底的指针,top是指向栈顶的指针,stacksize是指示当前栈当前可使用的最大容量。

  • 创建一个栈

status initStack(sqStack &s){
	s.base=(ElemType *)malloc(MAX_SIZE * sizeof(ElemType));
	if(!s.base) return ERROR;
	s.top=s.base;// 栈顶=栈底
	s.stackSize=MAX_SIZE;
}
  • 判断栈是否为空
Status StackEmpty(sqStack S){
	if(S.top == S.base)
	{
		return TRUE;
	}
	else return FALSE;
}
  • 清空顺序栈
Status ClearStack(sqStack S){
	if(S.base)S.top=S.base;
	return OK;
}
  • 销毁顺序栈
Status DestroyStack(sqStack &S){
	if(S.base){
		free(S.base);
		S.stacksize=0;
		S.base=S.top=NULL;
	}
	return OK;
}
  • 入栈操作又叫压栈操作,就是向栈中存放数据。
Status Push(sqStack &s,ElemType e)
{
	//如果栈满,返回错误
	if(s.top-s.base>=s.stackSize)
	{
		return ERROR;
	}
	*s.top = e;
	s.top++;
	return OK;
}
  • 顺序栈的出栈
Status Pop(sqStack &s,ElemType &e){
	if(s.top==s.base) return ERROR;
	--s.top;
	e=*s.top;
	return OK;
}

栈的链式存储结构

  • 链栈是运算受限的单链表,只能在链表头部进行操作。
typedef struc StackNode{
	ELemType data;
	struct StackNode *next;
}StackNode,*LinkStack;
  • 链栈的特点:

    • 链表的头指针就是栈顶
    • 不需要头结点
    • 基本不存在栈满的情况
    • 空栈相当于头指针指向空
    • 插入和删除仅在栈顶处执行
  • 链栈的初始化

Status InitStack(LinkStack &S){
	//构造一个空栈,栈顶指针置为空
	S=NULL;
	return OK;
}
  • 链栈的入栈
Status Push(LinkStack &S,ElemType e){
	p = (StackNode*)malloc(sizeof(StackNode));//生成新结点P
	p->data = e;//将新结点数据域置为e
	p->next = S;//将新结点插入栈顶
	S=p;//修改栈顶指针
	return OK;
}
  • 链栈的出栈
Status Pop(LinkStack &S,ElemType &e){
	if(S==NULL)return ERROR;
	e = S->data;
	P=S;
	S=S->next;
	free(P);
	return OK;
}

栈与递归

  • 递归的定义:

    • 若一个对象部分地包括它自己,或用它自己给他自己定义,则称这个对象是递归的;
    • 若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
      • 例如:递归求n的阶乘
         long Fact(long n){
      	if(n==0) return 1;
      	else return n*Fact(n-1);
      }
      
    • 以下三种情况常常用到递归方法
      • 递归定义的数学函数(斐波那契数列)
      • 具有递归特性的数据结构(树、广义表等)
      • 可递归求解的问题(汉诺塔)
    • 递归问题——用分治法求解:
      • 对于一个复杂的问题,能够分解成几个相对简单的方法且解法相同或类似的子问题来求解。
    • 必备的三个条件
      • 能够将一个问题变成一个新问题,而新问题与原问题的解法相同或类同。

      • 可以通过上述转化使问题简化

      • 必须有一个明确的递归出口,或称递归的边界

        void p(参数表){
        	if(递归结束条件) 可直接求解步骤; //基本项
        	else p(较小的参数);  //归纳项
        }
        
  • 函数调用过程:

    • 调用前,系统完成:
      1. 将实参,返回地址等参数传递给被调用函数
      2. 为被调用函数的局部变量分配存储区
      3. 将控制转移到被调用函数的入口
    • 调用后,系统完成:
      1. 保存被调用函数的计算结果
      2. 释放被调用函数的数据区
      3. 依照被调用函数保存的返回地址将控制转移到调用函数

队列

  • 队列是一种先进先出的线性表,在表一端插入(尾部),在另一端(表头)删除,类似我们的排队。
  • 队列的存储结构为链队或顺序队(常用顺序循环队)

队列的顺序存储结构

ADT Queue{
	数据对象:D={ai|ai∈ElemSet,i=1,2....n(n≥0}
	数据关系:R={<ai-1,ai>|ai-1,ai∈D,i=2...n}
	基本操作:
	InitQueue(&Q)  操作结果:构造空队列Q
	DestroyQueue(&Q) 条件:队列Q已存在;操作结果:队列Q被销毁;
	ClearQueue(&Q)   条件:队列Q已存在;操作结果:将Q清空;
	QueueLength(&Q)  条件:队列Q已存在;操作结果:返回Q的元素个数,即队长
	GetHead(Q,&e)   条件:Q为非空状态;操作结果:用e返回队列Q点队头元素;
	EnQueue(&Q,e)   条件:队列Q已存在;操作结果:插入元素e为队列Q的队尾元素;
	DeQueue(&Q,&e)  条件:Q为非空队列;操作结果:删除Q的队头元素,用e返回。
}ADT Queue
#define MAXQSIZE 100 //最大度列长度
typedef struct{
	QElemType *base; //初始化的动态分配存储空间
	int front;  	 //头指针
	int rear;		 //尾指针
}SqQueue;
  • 队列的初始化
Status InitQueue(SqQueue &Q){
	Q.base = (QElemType*)malloc(sizeof(QElemType)*MAXQSIZE);
	if(!Q.base) exit(OVERFLOW);   //存储分配失败
	Q.front = Q.rear = 0;      //头尾指针置为0 , 队列为空
	return OK;
}
  • 求队列的长度
int QueueLength(SqQueue Q){
	return ((Q.rear-Q.front+MAXQSIZE)%MAXQSIZE);
}
  • 循环队列的入队
Status EnQueue(SqQueue &Q,QElemType e){
	if((Q.rear+1)%MAXQSIZE ==Q.front) return ERROR; //队满
	Q.base[Q.rear]=e;
	Q.rear=(Q.rear+1)%MAXQSIZE;
	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;		//将队头指针+1
	return OK; 	
}
  • 取队头元素
QElemType GetHead(SqQueue Q){
	if(Q.front!=Q.rear)		//队列不为空
	return Q.base[Q.front];		//返回队头指针元素的值,队头指针不变
}

队列的链式存储结构

  • 若用户无法估计所用队列长度,宜采用链队列

    • 链队列的类型定义:
    #define MAXQSIZE 100  //最大队列长度
    typedef struct Qnode{
    	QElemType data;
    	struct Qnode *next;
    }QNode,*QuenePtr;
    
    typedef struct{
    	QuenePtr front;  //队头指针
    	QuenePtr 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 DestroyQueue(LinkQueue &Q)
{
	while(Q.front)
	{
		p=Q.front->next;
		free(Q.front);
		Q.front=p;
	}
	return OK;
}
  • 将元素e入队
Status EnQueue(LinkQueue &Q,QElemType e){
	p=(QueuePtr)malloc(sizeof(QNode));
	if(!p) exit(OVERFLOW);   //分配失败
	p->data = e;
	p->next=NULL;
	Q.rear->next=p;
	Q.rear = p;
	return OK;
}
  • 链队列出队
Status QUeue(LinkQueue &Q,QElemtype &e)
{
	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;
}
  • 求队头元素
Status GetHead(LinkQueue Q,QElemType &e){
	if(Q.front==Q.rear)return ERROR;
	e=Q.front->next->data;
	return OK;
}
  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值