0
点赞
收藏
分享

微信扫一扫

Logback日志框架

东林梁 2023-06-01 阅读 124

🎇[数据结构]栈和队列🎇


在这里插入图片描述


🌟 正式开始学习数据结构啦~此专栏作为学习过程中的记录🌟


文章目录

🍰一. 栈

🚀1.了解栈

什么是栈?


栈的图示:
在这里插入图片描述

栈的基本性质:

  1. 栈是一种逻辑结构
  2. 栈满足后进先出(LIFO)
  3. 栈的数学性质:
    n个不同的元素进栈,出栈元素不同排列的个数为: 1 n + 1 C 2 n n \frac {1} {n + 1}C_{2n}^{n} n+11C2nn

🚀2.顺序栈

🔆1.顺序栈的定义


顺序栈的结构体定义:

#define Maxsize 50

typedef struct Stack {
	Elemtype data[Maxsize]; //静态数组存放栈中元素
	int top;
}SqStack;


🔆2.初始化

初始时,结构体数组内还未存放元素,因此,不存在栈顶元素,令 top=-1

代码实现:

void InitStack(SqStack &S) {
	S.top = -1;//初始化栈顶指针
}

🔆3.判空&判满

由于顺序栈的入栈操作受数组时上界限制,所以可能发生栈上溢,此时 t o p = M a x s i z e − 1 top=Maxsize-1 top=Maxsize1;
同理,当栈空时, t o p = − 1 top=-1 top=1


在这里插入图片描述

代码实现:

//1.判空
bool Empty(SqStack& S) {
	if (S.top == -1) //栈空
		return true;
	else
		return false;
}

//2.判满
bool StackOver(SqStack& S) {
	if (S.top == Maxsize - 1)
		return true;
	else
		return false;
}

🔆4.入栈

入栈,由于只能在栈顶操作,所以当栈不满时,执行两个操作(不能反):

① 先让栈顶指针向后移一位S.top++
② 再让在当前栈顶指针的位置加入元素 xS.data[S.top]=x

最终合并为S.data[++top]=x;


代码实现:

bool Push(SqStack& S, int x) {
	if (S.top == Maxsize - 1) //栈满
		return false;
	//S.top = S.top + 1;
	//S.data[S.top] = x;
	S.data[++S.top] = x;
	return true;
}

🔆5.出栈

出栈也是只能在栈顶操作,执行两个操作(不能反):

① 先记录栈顶元素xx=S.data[top]
② 再在逻辑上删除x,让栈顶指针前移一位S.top--

最终合并为S.data[top--]=x;


代码实现:

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

🔆6.顺序栈的完整实现

完整代码实现:

#include<iostream>
#define Maxsize 50
using namespace std;

typedef struct Stack {
	int data[Maxsize]; //静态数组存放栈中元素
	int top;
}SqStack;

// 1.初始化(数组从下标0开始存放)
void InitStack(SqStack &S) {
	S.top = -1;//初始化栈顶指针
}

bool Empty(SqStack& S) {
	if (S.top == -1) //栈空
		return true;
	else
		return false;
}

bool StackOver(SqStack& S) {
	if (S.top == Maxsize - 1)
		return true;
	else
		return false;
}


// 2.新元素入栈
bool Push(SqStack& S, int x) {
	if (S.top == Maxsize - 1) //栈满
		return false;
	//S.top = S.top + 1;
	//S.data[S.top] = x;
	S.data[++S.top] = x;
	return true;
}

// 3.栈顶元素出栈
bool Pop(SqStack& S, int &x) {
	if (S.top == -1)
		return false;
	//x = S.data[S.top];
	//S.top = S.top - 1;
	x = S.data[S.top--];
	return true;
}


void Print(SqStack& S) {
	cout << "当前栈内依次出栈的元素是:" << endl;
	while (S.top != -1) {
		cout << S.data[S.top--] << " ";
	}cout << endl;
}

int main() {
	SqStack S;
	InitStack(S);
	int x, i = 0;
	cout << "请依次输入栈内元素" << endl;
	while (cin >> x) {
		S.data[i++] = x;
		if (cin.get() == '\n')
			break;
	}
	S.top = --i; //还原i

	//出栈
        x=0;
	Pop(S, x); //x用于记录栈顶元素
	cout << "栈顶元素是:" << x << endl;

	//入栈
	Push(S, 9);
	Print(S);

	system("pause");
	return 0;
}

输出结果:

在这里插入图片描述



🚀3.共享栈

🔆1.共享栈的定义

由于顺序栈需要在定义时开辟一整片连续的内存空间,对空间的利用率低,因此,我们考虑对其进行优化成为共享栈


共享栈图示:

在这里插入图片描述


由于在一片内存空间中有两个栈,即两个栈顶指针 t o p 1 , t o p 2 top1,top2 top1,top2,所以,共享栈的结构体定义如下:


#define Maxsize 50

typedef struct Stack {
	int data[Maxsize];
	int top1;
	int top2;
}Stack;

🔆2.初始化

初始化需要对两个指针进行操作, t o p 1 , t o p 2 top1,top2 top1,top2分别指向一维数组空间的 两端

代码实现:

void InitStack(Stack& S) {
	S.top1 = -1;
	S.top2 = Maxsize;
}

🔆3.判空&判满

①对于判空:

S 1 S1 S1的判空为:top1=-1 S 2 S2 S2的判空为:top2=Maxsize

①对于判满:

当两个栈顶指针相邻时,栈满: top2-top1=1
在这里插入图片描述

代码实现:

//1.栈空
bool Empty(Stack& S,int i) { //i表示访问的是栈Si
	if (i == 1 && S.top1 == -1)
		return true;
	if (i == 2 && S.top2 == Maxsize)
		return true;
	return false;
}

//2.栈满
bool StackOver(Stack& S, int i) {
	if (S.top2 - S.top1 == 1)
		return true;
	return false;
}

🔆4.入栈

入栈时,S1为栈顶指针向后移动一位,S2为栈顶指针向前移动一位,但要先判断合法性


代码实现:

bool Push(Stack& S, int i,int x) {
	if (i < 1 || i>2) {
		cout << "访问的栈不存在" << endl;
		return false;
	}
	if (StackOver(S)) {
		cout << "栈满" << endl;
		return false;
	}

	switch (i) { //对i进行条件分类
	case 1:
		S.data[++S.top1] = x;
		return true;
		break;
	case 2:
		S.data[--S.top2] = x;
		return true;
	}
}

🔆5.出栈

同理,出栈时,S1的栈顶指针向前移动一位,S2的栈顶指针向后移动一位,也要先判断合法性


代码实现:

//出栈
bool Pop(Stack& S, int i,int x) {
	if (i < 1 || i>2) {
		cout << "访问的栈不存在" << endl;
		return false;
	}

	switch (i) {
	case 1:
		if (Empty(S, 1)) {
			cout << "S1栈空" << endl;
			return false;
		}
		else {
			x = S.data[S.top1--];
			return true;
		}
		break;

	case 2:
		if (Empty(S, 2)) {
			cout << "S2栈空" << endl;
			return false;
		}
		else {
			x = S.data[S.top2++];
			return true;
		}
	}
}

🔆6.共享栈的完整实现

完整代码实现:

#include<iostream>
#define Maxsize 50
using namespace std;

typedef struct Stack {
	int data[Maxsize];
	int top1;
	int top2;
}Stack;

//1.初始化
void InitStack(Stack& S) {
	S.top1 = -1;
	S.top2 = Maxsize;
}

void GetStack(Stack& S) {
	cout << "请依次输入加入的元素以及对应的栈号:" << endl;
	int x, i;
	while (cin >> x >> i) {
		if (i == 1)
			S.data[++S.top1] = x;
		if (i == 2)
			S.data[--S.top2] = x;
		if (cin.get() == '\n')
			break;
	}
}

bool Empty(Stack& S,int i) { //i表示访问的是栈Si
	if (i == 1 && S.top1 == -1)
		return true;
	if (i == 2 && S.top2 == Maxsize)
		return true;
	return false;
}

bool StackOver(Stack& S) {
	if (S.top2 - S.top1 == 1)
		return true;
	return false;
}

//入栈
bool Push(Stack& S, int i,int x) {
	if (i < 1 || i>2) {
		cout << "访问的栈不存在" << endl;
		return false;
	}
	if (StackOver(S)) {
		cout << "栈满" << endl;
		return false;
	}

	switch (i) { //对i进行条件分类
	case 1:
		S.data[++S.top1] = x;
		return true;
		break;
	case 2:
		S.data[--S.top2] = x;
		return true;
	}
}

//出栈
bool Pop(Stack& S, int i,int &x) {
	if (i < 1 || i>2) {
		cout << "访问的栈不存在" << endl;
		return false;
	}

	switch (i) {
	case 1:
		if (Empty(S, 1)) {
			cout << "S1栈空" << endl;
			return false;
		}
		else {
			x = S.data[S.top1--];
			return true;
		}
		break;

	case 2:
		if (Empty(S, 2)) {
			cout << "S2栈空" << endl;
			return false;
		}
		else {
			x = S.data[S.top2++];
			return true;
		}
	}
}

void Print(Stack& S, int i) {
	cout << "从栈底向栈顶为:" << endl;
	if (i == 1) {
		int k = 0;
		while (k <= S.top1) {
			cout << S.data[k++] << " ";
		}cout << endl;
	}
	if (i == 2) {
		int k = Maxsize-1;
		while (k >= S.top2) {
			cout << S.data[k--] << " ";
		}cout << endl;
	}
}

int main() {
	Stack S;
	InitStack(S);
	GetStack(S);

	//1.入栈
	int x=0;
	Pop(S, 1,x);
	cout << "S1出栈的元素为:" << x << endl;
	Pop(S, 2, x);
	cout << "S2出栈的元素为:" << x << endl;

	//2.入栈
	Push(S, 1, 9);
	Push(S, 2, -9);
	cout << "加入9后,对S1:" << endl;
	Print(S, 1);
	cout << "加入-9后,对S2为:" << endl;
	Print(S, 2);

	system("pause");
	return 0;
}

输出结果:

在这里插入图片描述



🚀4.链栈

🔆1.链栈的定义

链栈的优点:

  1. 便于多个栈共享存储空间和提高效率
  2. 不存在栈满上溢的情况

栈的链式存储结构:

在这里插入图片描述

进栈顺序: a 1 − > a 2 − > . . . − > a n a_1->a_2->...->a_n a1>a2>...>an


定义链栈的结点(不带头结点):

typedef struct LinkNode {
	int data;
	struct LinkNode* next;
}LinkNode,*LiStack;

🔆2.初始化

这里默认是不带头结点的链栈

void InitStack(LiStack& S) {
	S = NULL;
}

🔆3.判空

bool Empty(LiStack& S) {
	if (S == NULL)
		return true;
	return false;
}

🔆4.入栈

即单链表的 前插操作:

①开辟一片内存空间存放新结点 p p pp->data=x;

②让 t o p top top 指针指向 p p pS=p;


代码实现:

bool Push(LiStack& S,int x) {
	LinkNode* p = (LinkNode*)malloc(sizeof(LinkNode));
	if (p == NULL) //内存分配不足
		return false;
	p->data = x;
	p->next = S;
	S = p;
	return true;
}

🔆5.出栈

即删除单链表的首结点:

①用 x x x记录首结点的数据:x=S->data;

②让 t o p top top 指针指向下一个结点,逻辑上删除首结点:S=S->next;


代码实现:

#include<iostream>
using namespace std;

typedef struct LinkNode {
	int data;
	struct LinkNode* next;
}LinkNode,*LiStack;

void InitStack(LiStack& S) {
	S = NULL;
}

bool Empty(LiStack& S) {
	if (S == NULL)
		return true;
	return false;
}

void GetStack(LiStack& S) {
	cout << "请输入进链栈的元素:" << endl;
	int x;
	while (cin >> x) {
		LinkNode* p = (LinkNode*)malloc(sizeof(LinkNode));
		p->data = x;
		p->next = S;
		S = p;
		if (cin.get() == '\n')
			break;
	}
}

//入栈
bool Push(LiStack& S,int x) {
	LinkNode* p = (LinkNode*)malloc(sizeof(LinkNode));
	if (p == NULL) //内存分配不足
		return false;
	p->data = x;
	p->next = S;
	S = p;
	return true;
}

//出栈
bool Pop(LiStack& S,int &x) {
	if (Empty(S))
		return false;
	x = S->data;
	S=S->next;
	return true;
}

void Print(LiStack &S) {
	cout << "当前的链栈元素为(栈顶到栈底):" << endl;
	LinkNode* p = S;
	while (p != NULL) {
		cout << p->data << " ";
		p = p->next;
	}cout << endl;
}


int main() {
	LiStack S;
	InitStack(S);
	GetStack(S);

	int x = 0;
	Pop(S, x);
	cout << "出栈的元素是:" << x << endl;

	cout << "将9压入链栈中:" << endl;
	Push(S, 9);
	Print(S);

	system("pause");
	return 0;
}

输出结果:

在这里插入图片描述




🍰二. 队列

🚀1.了解队列

什么是队列?


队列的图示:

在这里插入图片描述



🚀2.队列的顺序存储结构

🔆1.顺序队列的定义


删除元素在队头 f r o n t front front,插入元素在队尾 r e a r rear rear

也就是说,rear指向的是入队时元素将要插入的位置:
在这里插入图片描述

顺序队列的结构体定义:

#define Maxsize 50

typedef struct SqQueue {
	int data[Maxsize];
	int front, rear;
}SqQueue;

基本操作:


🔆2.判空&判满 ?

顺序存储下,队列的判空和判满就很有意思了

我们对上述初始化的队列中增加一个元素(入队)

在这里插入图片描述


那按照之前顺序存储所说的,队满时的条件就是: Q . r e a r = = M a x s i z e Q.rear==Maxsize Q.rear==Maxsize 吗?


举一个例子:
在这里插入图片描述

此时,我们并没有在队列中插入元素,所以,队尾指针 r e a r rear rear不会改变,而删除元素导致 f r o n t front front不断后移, Q . r e a r = M a x s i z e Q.rear=Maxsize Q.rear=Maxsize而删除的元素空间已经空出来了,可以存放新元素,这是称为"假上溢",所以,无法单凭 r e a r rear rear的值就判定队满

所以,我们采取 取余 的操作,实现队满的判断

当我们插入一个元素时,我们先令 a [ Q . r e a r ] = x a[Q.rear]=x a[Q.rear]=x,此时,进行判断: ( Q . r e a r + 1 ) % M a x s i z e = f r o n t (Q.rear+1)\% Maxsize=front (Q.rear+1)%Maxsize=front,那么我们则可以判断——队满!


图解:

若此时 f r o n t front front指向 3 3 3 r e a r rear rear指向 2 2 2,需要插入一个新元素x:
在这里插入图片描述

在移动 r e a r rear rear之前,我们先判断 r e a r rear rear f r o n t front front的关系: ( 2 + 1 ) % 5 = 3 (2+1)\%5=3 2+1%5=3,所以,队满

在这里插入图片描述

🚀3.循环队列

🔆1.了解循环队列

在了解了顺序存储的缺陷之后,我们用循环队列进行优化


这里只是逻辑上视为一个环,所以定义方式不变,只是初始化会改变:

void InitQueue(SqQueue& Q) {
	Q.front = Q.rear = 0; //队头队尾指针指向0
}

基本操作:


在这里插入图片描述

那么,队满和队空的条件又是什么呢?显然,队空的条件是 Q . f r o n t = = Q . r e a r Q.front==Q.rear Q.front==Q.rear,若入队元素的速度快于出队元素的速度,则尾指针很快会追上首指针,如图,当循环队列队满的时候,同样也有 Q . f r o n t = = Q . r e a r Q.front==Q.rear Q.front==Q.rear


因此,对于队空还是队满的判断,有三种方法:


🔆2. 牺牲单元法

牺牲一个单元来区分队空和队满,入队时少用一个队列单元

以队头指针在队尾指针的下一位置作为队满的标志


图解:

在这里插入图片描述


1. 队空:

bool Empty(SqQueue& Q) {
	if (Q.rear == Q.front)
		return true;
	return false;
}

2. 队满:

bool Over(SqQueue& Q) {
	if ((Q.rear + 1) % Maxsize == Q.front)
		return true;
	return false;
}

3. 出队:

bool DeQueue(SqQueue& Q, int& x) {
	if (Empty(Q))
		return false;
	x = Q.data[Q.front];
	Q.front = (Q.front + 1) % Maxsize; // 头移
	return true;
}

4. 入队:

bool EnQueue(SqQueue& Q, int x) {
	if (Over(Q))
		return false;
	Q.data[Q.rear] = x;
	Q.rear = (Q.rear + 1) % Maxsize; // 尾移
	return true;
}

🔆3. tag法

在结构体中新设一个 t a g tag tag数据成员,以区分是队满还是队空

判断依据:

进队时置 tag为1,出队时置tag为0,因为只有入队才会导致队满,出队才会导致队空



1. 入队:

int EnQueue(SqQueue &Q,int x){
        if(Q.front==Q.rear && Q.tag=1) //队满条件
                return 0;
        Q.data[Q.rear]=x;
        Q.rear=(Q.rear+1)%Maxsize;
        Q.tag=1;  // 标记
        return 1;
}

2. 出队:

int DeQueue(SqQueue &Q,int &x){
    if(Q.rear==Q.front && Q.tag==0) //队空条件
            return 0;
    x=Q.data[Q.front];
    Q.front=(Q.front+1)%Maxsize;
    Q.tag=0; // 标记
    return 1;
}

🔆4. size法

类型中增设表示元素个数的size成员

入队则 Q . s i z e + 1 Q.size+1 Q.size+1 ;出队则 Q . s i z e − 1 Q.size-1 Q.size1

这样,队空的条件为 Q . s i z e = = 0 Q.size==0 Q.size==0 ;队满的条件为 Q . s i z e = = M a x s i z e Q.size==Maxsize Q.size==Maxsize 这两种情况都有 Q . f r o n t = = Q . r e a r Q.front==Q.rear Q.front==Q.rear



🚀4.队列的链式存储结构

🔆1.链队列的定义

对于顺序存储下的队列,在实现一些基本操作时很不方便,因此,我们用链式存储进行优化


队列的链式存储结构:

不带头结点
在这里插入图片描述

带头结点

在这里插入图片描述


链队列的优点

  1. 链队列适合数据变动较大时
  2. 不存在队列满且产生溢出的情况

链队列的结构体定义:

typedef struct LinkNode {
	Elemtype data;
	struct LinkNode* next;
}LinkNode;

typedef struct LinkQueue{
	struct LinkNode* front, * rear;
}LinkQueue;

这里需要定义两个结构体:一个是单链表结点的结构体 L i n k N o d e LinkNode LinkNode,包含 d a t a data data数据域和 n e x t next next指针域,另一个是代表链队列整体的结构体 L i n k Q u e u e LinkQueue LinkQueue,包含 头指针 f r o n t front front和尾指针 r e a r rear rear


🔆2.初始化

对于链队列的基本操作,我们分成带头结点和不带头结点进行讨论

1.不带头结点

不带头结点时,头指针直接指向链表的第一个结点,初始化头指针和尾指针均指向 N U L L NULL NULL

代码实现:

void InitQueue(LinkQueue& Q) {
	Q.front = NULL;
	Q.rear = NULL;
}

2.带头结点

而带头结点时,头指针将始终指向头结点,不会因为删除操作移动,同样也要让头指针和尾指针指向同一位置

在这里插入图片描述

代码实现:

void InitQueue(LinkQueue& Q) {
	Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
	Q.front->next = NULL;
}

🔆3.判空

1.不带头结点

对于不带头结点的链队列,头指针始终指向首结点,而当队空时, f r o n t front front只能指向 N U L L NULL NULL

代码实现:

//判队空
bool EmptyQueue(LinkQueue Q) {
	if (Q.front == NULL) //因为只要有结点存在,front一定指向第一个结点
		return true;
	return false;
}

2.带头结点

带头结点时, f r o n t front front始终指向头结点而不会指向 N U L L NULL NULL,所以我们根据它的初始化条件判断是否为空:

即当 Q . f r o n t = = Q . r e a r Q.front == Q.rear Q.front==Q.rear (也就是头指向头结点时) 链队列为空

代码实现:

//判队空
bool EmptyQueue(LinkQueue Q) {
	if (Q.front == Q.rear) //因为只要有结点存在,rear一定会移动
		return true;
	return true;
}

🔆4.入队

1.不带头结点

当加入一个新结点 x x x 时,由于是在队尾进行操作,因此,让指向新结点的指针 s s s 的下一个指向 r e a r rear rear 的下一个位置:s->next=Q.rear->next ,连接尾结点和新结点:Q.rear->next=s,再移动尾指针到现在的 s s s 位置: Q.rear=s

在这里插入图片描述

而不带头结点的入队要比带头结的繁琐,原因是对于链表为空时要进行特判

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

当链队列为空时,由于 f r o n t front front r e a r rear rear 均指向 N U L L NULL NULL,因此:

不能直接让:s->next=Q.rear->next ❌;而是 s->next=NULL

在加入新结点后,不仅要让: Q.rear=s,还要令头指针指向第一个结点: Q.front=s


代码实现:

//入队
void EnQueue(LinkQueue& Q, Elemtype x) {
	LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));
	s->data = x;
	s->next = NULL; //这时候不能用尾指针的下一个指针了,因为尾指针有可能指向NULL
	if (Q.front == NULL) { //Q.rear==NULL
		Q.front = s; //头结点也要跟着变
		Q.rear = s;
	}
	else {
		Q.rear->next = s; //Q.rear!=NULL
		Q.rear = s;
	}
}

2.带头结点

而对于带头结点的入队操作就要方便很多,因为头结点的存在,所以即使链队列为空, f r o n t front front r e a r rear rear 是指向头结点而不是 N U L L NULL NULL,这实现了操作上的统一
在这里插入图片描述

代码实现:

//入队
void EnQueue(LinkQueue& Q, Elemtype x) {
	LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));
	s->data = x;
	s->next = Q.rear->next;
	Q.rear->next = s;
	Q.rear = s;
}

🔆5.出队

1.不带头结点

出队操作在队头进行,因此,需要不断移动队头指针 f r o n t front front 进行逻辑上的删除操作:p=Q.front ,Q.front=p->next,最后释放结点 free§

在这里插入图片描述

但是,还存在特殊情况

①链队列为空:

也就是当 Q.front=NULL 时,则出队失败 return false

②链队列中只有一个结点(即要删除的结点为尾结点):

这时候,特殊的是,不仅要移动头指针: Q.front=NULL;还需要移动尾指针: Q.rear=NULL

在这里插入图片描述

代码实现:

//出队
bool DeQueue(LinkQueue& Q, Elemtype& x) {
	if (Q.front == NULL)
		return false;
	LinkNode* p = (LinkNode*)malloc(sizeof(LinkNode));
	p = Q.front; //即为第一个结点
	x = p->data;
	Q.front = p->next;
	if (Q.rear == p){ //如果要删除的p就是尾结点
		Q.rear = NULL;
		Q.front = NULL; //没有头结点,都指向NULL
	}
	free(p);
	return true;
}

2.带头结点

带头结点时出队的操作与不带头结点时相似,也必须进行:①判空;②判断出队结点是否为尾结点,只是此时的操作对象变为: p=Q.front->next

在这里插入图片描述

代码实现:

//出队
bool DeQueue(LinkQueue& Q, Elemtype& x) {
	if (Q.rear == Q.front)
		return false;
	LinkNode* p = (LinkNode*)malloc(sizeof(LinkNode));
	p = Q.front->next;
	x = p->data;
	Q.front->next = p->next;
	if (Q.rear == p) //如果要删除的p就是尾结点
		Q.rear = Q.front;
	free(p);
	return true;
}


🚀5.双端队列

🔆1.了解双端队列

双端队列的形式变换多样,操作灵活,需要熟练掌握


双端队列的结构:

在这里插入图片描述


🔆2.受限的双端队列

在这里插入图片描述

在这里插入图片描述

若限定一般的双端队列从某个端点插入的元素只能从该端点删除,则这个双端队列可以视为两个栈底相邻接的栈
在这里插入图片描述


🔆3.双端队列的操作

e . g e.g e.g 设有一个双端队列,输入序列为 1 , 2 , 3 , 4 1,2,3,4 1234,要求得到如下的输出序列:

  1. 能由输入受限的双端队列得到,而不能由输出受限的双端队列得到的输出序列
  2. 能由输出受限的双端队列得到,而不能由输入受限的双端队列得到的输出序列
  3. 既不能由输入受限的双端队列得到,又不能由输出受限的双端队列得到的输出序列

在这里插入图片描述


首先,由四种结构的关系可以得出:

①栈能实现的输出序列,其他三种结构一定能实现
②出限的双端队列和入限的双端队列能实现的输出序列,双端队列一定能实现

而双端队列能实现的输出序列为: A 4 4 = 24 A_4^4=24 A44=24 种,栈能实现的输出序列为(卡特兰数): 1 n + 1 C 8 4 = 14 \frac {1} {n + 1}C_{8}^{4}=14 n+11C84=14


这里直接给出结果:

1. 出限不能实现的输出序列为: 4 , 2 , 3 , 1 4,2,3,1 4,2,3,1 4 , 1 , 3 , 2 4,1,3,2 4,1,3,2

判断出限输出序列的方法:


按照此方法,我们来验证上述两种输出序列:

抓"大头",此时最先出队的数为 4 4 4,比 4 4 4小的有 1 , 2 , 3 1,2,3 1,2,3,由于栈的出栈顺序就是这些数在栈中的排列顺序,所以可以得到如下排列的栈,其中 1 , 2 , 3 1,2,3 1,2,3 的排列为: 1 , 3 , 2 1,3,2 1,3,2

在这里插入图片描述

我们尝试通过两端交替输入得到排列 1 , 3 , 2 1,3,2 1,3,2
在这里插入图片描述

可以看到,此时 3 3 3 夹在 1 1 1 2 2 2 中间,不论两端怎么输入,都不可能将 3 3 3 放入,因此,这种序列无法由出限队列得到

同理,可以判断,对于 4 , 2 , 1 , 3 4,2,1,3 4,2,1,3 也无法由出限队列得到:

在这里插入图片描述



2. 入限不能实现的输出序列为: 4 , 2 , 3 , 1 4,2,3,1 4,2,3,1 4 , 2 , 1 , 3 4,2,1,3 4,2,1,3

判断入限输出序列的方法:


按照此方法,我们也来验证上述两种输出序列:

首先抓"大头",最先输出的数为 4 4 4,所以在 4 4 4 之前的数 1 , 2 , 3 1,2,3 1,2,3 此时一定已经入队,而根据输入序列为 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4,则可以确定,队列中的排列为: 4 , 3 , 2 , 1 4,3,2,1 4,3,2,1

在这里插入图片描述

我们尝试通过两端的交替输出得到上述两种输出序列:

在这里插入图片描述

可以看到,无论如何都无法得到 2 2 2 1 1 1 o r or or 3 3 3 先出队的输出序列

所以可以得出,既不能由输入受限的双端队列得到,又不能由输出受限的双端队列得到的输出序列为: 4 , 2 , 3 , 1 4,2,3,1 4,2,3,1



🎇本节详细讲解了新的数据结构栈和队列,之后还会更新具体的应用场景~🎇

如有错误,欢迎指正~!


在这里插入图片描述

举报

相关推荐

0 条评论