数据结构-栈,队列

| 这个作业属于哪个班级 | 数据结构--网络2011/2012 |
| ---- | ---- | ---- |
| 这个作业的地址 | DS博客作业02--栈和队列 |
| 这个作业的目标 | 学习栈和队列的结构设计及运算操作 |
| 姓名 | 付峻霖 |

0.PTA得分截图

1.本周学习总结

1.1 栈

栈结构的定义和特点

  • 栈的顺序存储是由数组来实现的
  • 只允许在栈顶进行插入删除操作,另一端为栈底
  • 栈是后进先出的线性表
  • 线性表的表尾是栈顶,而不是栈底
  • 无论是进栈还是出栈,均在栈顶操作,栈底是固定的

栈的抽象数据类型

ADT 栈(stack)
Data
	同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
	InitStack(*S):初始化操作,建立一个空栈S。
	DestroyStack(*S):若栈存在,则销毁它。
	ClearStack(*S):将栈清空。
	StackEmpty(S):若栈为空,返回true,否则返回false。
	GetTop(S,*e):若栈存在且非空,用e返回S的栈顶元素。
	Push(*S,e):若栈S存在,插入新元素e到栈S中并成为栈顶元素
	Pop(*S,*e):删除栈S中栈顶元素,并用e返回其值。
	StackLength(S):返回栈S的元素个数
endADT

Q:什么时候函数操作要用‘*’号?
A:调用函数过后,内容有被修改就要用‘*’

1.1.1 顺序栈

顺序栈的图形

顺序栈的结构定义

typedef int SElemType;

/*顺序栈结构*/
typedef struct
{
	SElemType data[MAXSIZE];
	int top;        /*用于栈顶指针*/
}SqStack;

顺序栈的基本操作

  • 初始化栈
void InitStack(SqStack* S)
{
   S=new SqStack;      //分配一个顺序栈空间,首地址放在S中
   S->top=-1;          //栈顶指针置为-1
}
  • 进栈操作
/*插入元素e为新的栈顶元素*/
bool Push(SqStack* S, SElemType e)
{
	if (S->top == MAXSIZE - 1)/*栈满*/
		return ERROR;
	S->top++;                 /*栈顶指针增加一*/
	S->data[S->top] = e;      /*将新插入元素赋值给栈顶空间*/
	return OK;
}
  • 出栈操作
/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK,否则返回ERROR*/
bool Pop(SqStack* S, SElemType* e)
{
	if (S->top == -1)      /*栈空*/
		return ERROR;
	*e = S->data[S->top];  /*将要删除的栈顶元素赋值给e*/
	S->top--;              /*栈顶指针减一*/
	return OK;
}
  • 销毁栈
void DestroyStack(SqStack* S)
{
	free(S);
}

1.1.2 顺序栈--两栈共享空间

栈的顺序存储还是很方便的,在插入删除时不需要移动元素,不过它有一个缺陷,必须事先确定数组大小,这很可能造成资源浪费

两栈共享空间的图形

两栈共享空间的结构定义

/*两栈共享空间结构*/
typedef struct
{
	SElemType data[MAXSIZE];
	int top1;  /*栈1的栈顶指针*/
	int top2;  /*栈2的栈顶指针*/
}SqDoubleStack;

两栈共享空间的基本操作

  • 插入操作
/*插入元素e为新的栈顶元素*/
bool Push(SqDoubleStack* S, SElemType e, int stackNumber)
{
	if (S->top1 + 1 == S->top2)  /*栈已满,不能再push新元素了*/
		return ERROR;
	if (stackNumber == 1)        /*栈1有元素进栈*/
		S->data[++S->top1] = e;  /*若是栈1则先top1+1后给数组元素赋值*/
	else if (stackNumber == 2)   /*栈2有元素进栈*/
		S->data[--S->top2] = e;  /*若是栈2则先top2-1后给数组元素赋值*/
	return OK;
}
  • 删除操作
/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
bool Pop(SqDoubleStack* S, SElemType *e, int stackNumber)
{
	if (stackNumber == 1)
	{
		if (S->top1 == -1)      
			return ERROR;        /*说明栈1已经是空栈,溢出*/
		*e = S->data[S->top1--]; /*将栈1的栈顶元素出栈*/
	}
	if (stackNumber == 2)
	{
		if (S->top2 == -1)
			return ERROR;        /*说明栈2已经是空栈,溢出*/
		*e = S->data[S->top2++]; /*将栈2的栈顶元素出栈*/
	}
	return OK;
}

1.1.3 链式栈

链式栈的图形

链式栈的结构定义

/*链栈结构*/
typedef struct StackNode
{
	SElemType data;        //数据域
	struct StackNode* next;//指针域
}StackNode,*LinkStackPtr;      //链栈结点类型

typedef struct
{
	LinkStackPtr top;
	int count;
}LinkStack;

链式栈的基本操作

  • 初始化栈
void InitStack(StackNode* S)
{
	S = new StackNode;
	S->next = NULL;
}
  • 进栈操作
/*插入元素e为新的栈顶元素*/
bool Push(LinkStack* S, SElemType e)
{
	LinkStackPtr s = new StackNode;
	s->data = e;
	s->next = S->top;/*把当前的栈顶元素赋值给新结点的直接后继*/
	S->top = s;      /*将新的结点s赋值给栈顶指针*/
	S->count++;
	return OK;
}
  • 出栈操作
bool 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;
}

1.2 栈的应用

1.2.1 斐波那契数列的实现--兔子繁衍后代

题目

兔子在出生两个月后,就右繁衍能力,一对兔子每个月能生出一对小兔子来。假设所有兔子都不死,那么一年后可以繁衍多少兔子呢?

题目分析
第一个月小兔子没有繁殖能力,所以还是一对,第二个月生下一对小兔子,总共两对,第三个月,老兔子又生一对,小兔子没有繁衍能力,总共三对······

  • 数学定义
    F(n)=F(n-1)+F(n-2)

代码实现

/*斐波那契的递归函数*/
int Fbi(int i)
{
	if (i < 2) /*0月零对兔子,1月一对兔子*/
		return i == 0 ? 0 : 1; //特殊处理
	return Fbi(i - 1) + Fbi(i - 2);
}
int main()
{
	int i;
	printf("递归显示斐波那契数列:\n");
	for (i = 0; i <= 12; i++)  //打印每个月 繁殖的小兔子对数
		printf("%d", Fbi(i));
	return 0;
}

1.2.2 十进制数转八进制数

十进制数转八进制数码云
输入样例与输出样例

1.2.3 逆波兰计算结果

后缀表达式计算结果码云
输入样例与输出样例

1.3 队列

队列结构的定义和特点

  • 队列是一种先进先出的线性表
  • 允许插入的一端称为队尾,允许删除的一端称为队头
  • 插入数据只能再队尾进行,删除数据只能在队头进行

队列的抽象数据类型

ADT 队列(Queue)
Data
	同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
	InitQueue(*Q):初始化操作,建立一个空队列Q。
	DestroyQueue(*Q):若队列Q存在,则销毁它。
	ClearQueue(*Q):将队列Q清空。
	QueueEmpty(Q):若队列Q为空,返回true,否则返回false。
	GetHead(Q,*e):若队列Q存在且非空,用e返回队列Q的队头元素。
	EnQueue(*Q,e):若队列Q存在,插入新元素e到队列Q中并成为队尾元素。
	DeQueue(*Q,*e):删除队列Q中队头元素,并用e返回其值
	QueueLenghth(Q):返回队列Q的元素个数。
endADT

Q:什么时候函数操作要用‘*’号?
A:调用函数过后,内容有被修改就要用‘*’

1.3.1 顺序队列

顺序队列的图形

顺序队列的结构定义

typedef int QElemType;
/*顺序队列的存储结构*/
typedef struct
{
	QElemType data[MAXSIZE];
	int front;         /*头指针*/
	int rear;          /*尾指针,若队列不空,指向队列尾元素的下一个位置*/
}SqQueue;

顺序队列的基本操作

  • 初始化队列
/*初始化一个空队列Q*/
void InitQueue(Queue& Q)
{
    Q = new Queue;//动态申请内存
    Q->front = 0;
    Q->rear = 0;
}
  • 入队操作
/*若队列未满,则插入元素e为Q新的队尾元素*/
bool EnQueue(SqQueue* Q, QElemType e)
{
    if (Q->rear + 1 == MAXSIZE)/*队列满的判断*/
        return ERROR;
    Q->data[Q->rear] = e;      /*将元素e赋值给队尾*/
    Q->rear = Q->rear + 1;     /*rear指针向后移一位置*/
    return OK;
}
  • 出队操作
/*若队列不空,则删除Q中队头元素,用e返回其值*/
bool DeQueue(SqQueue* q, Elemtype* e)
{
    if (Q->front == Q->rear)       /*队列空的判断*/
        return ERROR;
    *e = Q->data[Q->front];        /*将队头元素赋值给e*/
    Q->front = Q->front + 1;       /*front指针向后移一位置*/
    return OK;
}

1.3.2 循环队列

循环队列的图形

循环队列的结构定义

typedef int QElemType;
/*循环队列的顺序存储结构*/
typedef struct
{
	QElemType data[MAXSIZE];
	int front;         /*头指针*/
	int rear;          /*尾指针,若队列不空,指向队列尾元素的下一个位置*/
}SqQueue;

循环队列的基本操作

  • 初始化队列
/*初始化一个空队列Q*/
bool InitQueue(Queue& Q)
{
    Q->front = 0;
    Q->rear = 0;
    return OK;
}
  • 入队操作
/*若队列未满,则插入元素e为Q新的队尾元素*/
bool EnQueue(SqQueue* Q, QElemType e)
{
    if ((Q->rear + 1)%MAXSIZE == Q->front)/*队列满的判断*/
        return ERROR;
    Q->data[Q->rear] = e;      /*将元素e赋值给队尾*/
    Q->rear = (Q->rear + 1) % MAXSIZE;     /*rear指针向后移一位置*/
                                           /*若到最后则转到数组头部*/
    return OK;
}
  • 出队操作
/*若队列不空,则删除Q中队头元素,用e返回其值*/
bool DeQueue(SqQueue* q, Elemtype* e)
{
    if (Q->front == Q->rear)                   /*队列空的判断*/
        return ERROR;
    *e = Q->data[Q->front];                    /*将队头元素赋值给e*/
    Q->front = (Q->front + 1) % MAXSIZE;       /*front指针向后移一位置*/
                                               /*若到最后则转到数组头部*/
    return OK;
}
  • 求队列长度
/*返回Q的元素个数,也就是队列的当前长度*/
int QueueLength(SqQueue Q)
{
    return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

1.3.3 链式队列

链式队列的图形

链式队列的结构定义

typedef int QElemType;

typedef struct QNode      /*结点结构*/
{
	QElemType data;
	struct QNode* next;
}QNode, * QueuePtr;

typedef struct            /*队列的链表结构*/
{
	QueuePtr front, rear; /*队头、队尾指针*/
}LinkQueue;

链式队列的基本操作

  • 入队操作
/*插入元素e为Q的新的队尾元素*/
bool EnQueue(LinkQueue* Q, QElemType e)
{
	QueuePtr s = new QNode;
	if (!s)            /*存储分配失败*/
		exit(OVERFLOW);
	s->data = e;
	s->next = NULL;
	/*尾插法*/
	Q->rear->next = s;/*把拥有元素e的新结点s赋值给原队尾结点的后继*/
	Q->rear = s;      /*把当前的s设置为队尾结点,rear指向s*/
	return OK;
}
  • 出队操作
/*若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR*/
bool DeQueue(LinkQueue* Q, QElemType* e)
{
	QueuePtr p;
	if (Q->front == Q->rear)
		return ERROR;
	p = Q->front->next;        /*将要删除的队头结点暂存给p*/
	*e = p->data;              /*将要删除的队头结点的值赋值给e*/
	Q->front->next = p->next;  /*将原队头结点的后继p->next赋值给头结点后继*/
	if (Q->rear == p)             /*若队头就是队尾,则删除后将rear指向头结点*/
		Q->rear = Q->front;
	free(p);
	return OK;
}

队列应用

1.3.1 6-3 jmu-ds-舞伴问题 (20 分)

题目

假设在周末舞会上,男士和女士们分别进入舞厅,各自排成一队。跳舞开始,依次从男队和女队队头各出一人配成舞伴,若两队初始人数不同,则较长那一队未配对者等待下一轮舞曲。现要求写一算法模拟上述舞伴配对问题。

思路

  1. 创建俩队列分别放男士和女士
  2. 俩队列同时弹出一个人,组成舞伴

核心代码

/*将Person里的人 分别放到Mdancers,Fdancers两个队列中*/
void DancePartner(Person dancer[], int num)//舞蹈合作伙伴
{
	for (int i = 0; i < num; i++)
	{
		if (dancer[i].sex == 'M')
		{  //男生在男栈
			EnQueue(Mdancers, dancer[i]);
		}
		else
		{  //女生在女栈
			EnQueue(Fdancers, dancer[i]);
		}
	}
	while (QueueEmpty(Mdancers) != 1 && QueueEmpty(Fdancers) != 1)
	{   //当  两个栈都不为空
		Person x, y;
		DeQueue(Mdancers, x);//弹出一个男生
		DeQueue(Fdancers, y);//弹出一个女生
		cout << y.name << " " << x.name << endl;
	}
}

2.PTA实验作业(4分)

符号配对码云地址
银行业务码云地址

2.1 符号配对

2.1.1 解题思路及伪代码

解题思路

  1. 遍历字符串,把括号之外的东西全部忽略
  2. 只要是左符号就入栈。
  3. 只要当前符号与栈顶符号配对成功,就出栈
  4. 最后进行条件判断,flag的状态栈是否为空

伪代码

for (遍历字符串)
{
	if (是左括号)
		入栈
	else if (栈空且当前位置为右符号)//配对失败
		flag = 1;
		break; 结束
	else if (栈顶元素与当前位置符号刚好配对)//配对成功
		栈顶元素出栈
}
if (flag == 1)
	右符号剩余
else if (flag == 0 且 栈空)
	无剩余,完全配对成功
else 栈不为空
	左符号剩余
return 0;

2.1.2 总结解题所用的知识点

  1. stack模板各类函数pop,push,empty的应用
  2. 分类讨论思想,当它是左符号时入栈,为右符号时出栈,还要考虑特殊情况
  3. 巧用flag与栈的状态表示配对结果

2.2 银行业务队列简单模拟

2.2.1 解题思路及伪代码

解题思路

  1. 首先把这些数字分配到A或B队列中
  2. 如果A是奇数,那就直接删除A队头
  3. 如果A是偶数,那就A先走,B再走

伪代码

/*让所有顾客进入队列*/
for (顾客总数)
	输入每个数字
	if (数字为奇数)
		入A队列
	else 数字为偶数
		入B队列

/*头部空格单独处理*/
if (!A.empty())
	打印A队头
	删除A队头
	i = 1;//处理完的顾客人数
else
	打印B队头
	删除B队头

/*正常处理*/
while (A不为空 或 B不为空)
	i++;    //处理完的顾客人数+1
	if (奇数)
		if (A队列不为空)
			打印A队头
			删除A队头
	else 偶数
		/*A先删除,B再删除*/
		if (A队列不为空)
			打印A队头
			删除A队头
		if (B队列不为空)
			打印B队头
			删除B队头

2.2.2 总结解题所用的知识点

  1. queue模板各类函数pop,push,empty的应用
  2. 关于奇数偶数的处理,用i来计数奇数出A,偶数出A再出B

3.用两个栈实现队列

3.1 题目及解题代码

  • 题目

  • 代码

class CQueue {
    Stack<Integer> in;
    Stack<Integer> out;
    public CQueue() {
        in = new Stack<Integer>();
        out = new Stack<Integer>();
    }
    public void appendTail(int value) {
        in.push(value);
    }
    public int deleteHead() {
        if(in.isEmpty()&&out.isEmpty()) return -1;
        if(out.isEmpty())
            while(!in.isEmpty())
                out.push(in.pop());
        return out.pop();
    }
}
作者:Jokar兄
链接:https://www.bilibili.com/video/BV1E54y127LP?t=56
来源:哔哩哔哩(bilibili)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3.2 该题的设计思路及伪代码

题意解读
通过两个先进后出的栈,实现一个先进先出的队列
设计思路

  1. 创建两个栈,栈in用于存储存入的元素,栈out用于存储用于弹出的元素
  2. 比如123,栈in存放完元素后栈顶为3,将栈in中的元素弹入栈out中,栈out的栈顶元素为1,再弹出栈out的123,实现了输入为123,输出也为123

伪代码

class CQueue {
    /*创建两个栈*/
    创建in栈 //栈in用于存储存入的元素
    创建out栈//栈out用于存储用于弹出的元素
    public CQueue() {
        为栈in开辟空间
        为栈out开辟空间
    }
    public int deleteHead() {
        if(两栈都为空) return -1;
        if(若栈out中的元素为空)
            则将栈in中的元素弹出并存入栈out中
        return 弹出元素;
    }
}

时间复杂度为O(n)
空间复杂度为O(n)

3.3 分析该题目解题优势及难点。

  • 需要深刻的理解栈结构先入后出,以及队列结构先入先出的原理
  • 巧妙的运用两个栈结构,将in栈元素弹出到out栈中,实现元素的逆序
posted @ 2021-04-11 21:41  强扭的甜瓜  阅读(154)  评论(0编辑  收藏  举报