栈队列相关算法题|整理字符串|字符串去重|循环队列入队出队|队列通过栈逆置|栈模拟队列|循环链队入队和出队|循环队列队尾删除和队头插入(C)

整理字符串

一个由大小写英文字母组成的字符串s,一个整理好的字符串中,两个相邻字符s[i]s[i+1],其中0<=i<=s.length-2,要满足以下条件,
s[i]是小写字符,则s[i+1]不可以是相同的大写字符
s[i]是大写字符,则s[i+1]不可以是相同的小写字符
将字符串整理好,每次可以从字符串中选出满足条件的两个相邻字符并删除,直到字符串整理好为止

算法思想

当待入栈的元素跟此时栈顶的元素不互为大小写字符时,入栈,否则弹出此时的栈顶元素
函数传入字符串s
通过i指针从头开始遍历字符串,直到遇到字符串结束符号\0
top初始化为0,入栈的时候,先把数据赋给top位置,再++top,top指向的是栈顶元素的下一个位置
直到top指向\0,表示字符串的数据被全部遍历完毕,返回整理好的字符串
![[Pasted image 20241116144720.png]]

char* makeGood(char* s)
{
	int top = 0;
	for (int i = 0; s[i] != '\0', i++)
	{
		// 栈为空或者栈顶元素跟待入栈的元素不互为大小写字符时,入栈
		if (top == 0 || (abs(s[top-1] - s[i]) != 'a'-'A'))
		{
			s[top++] = s[i];
		}
		else
		{
			top--;
		}
	}
	s[top] = '\0';
	return s;
}

字符串去重

给出由小写字母组成的字符串S,重复项删除操作会选择两个相邻且相同的字母,并删除它们
在S上反复执行重复项删除操作,直到无法继续删除,
在完成所有重复项删除操作后返回最终的字符串

算法思想

当字符串中同时有多种相邻重复项时,无论先删除那一个都不会影响最终的结果
因此可以从左向右顺次处理字符串,而消除一对相邻重复项可能会导致新的相邻重复项出现
因此需要保存当前还未被删除的字符

用strlen算出传入的字符串s的长度n
新开一个n+1大小的空间,stk,作为栈使用,用于存储去重后的字符
retSize表示栈中元素的个数,也是下一个可写入位置的指针
通过for循环遍历字符串逐个检查s中的字符
如果当前stk栈非空,并且栈顶字符与当前字符是否相同
如果相邻字符重复retSize–,将栈顶元素移除
如果不重复,将当前元素入栈,并增加retSize
最后将stk的栈顶输入\0,使得stk成为一个字符串
最后返回stk

char* removeDuplicates(char* s)
{
	int n = strlen(s);
	char* stk = (char*)malloc(sizeof(char) * (n + 1));
	int retSize = 0;
	for (int i = 0; i < n; i++)
	{
		if (retSize > 0 && stk[retSize - 1] == s[i])
		{
			retSize--;
		}
		else
		{
			stk[retSize++] = s[i];
		}
	}
	stk[retSize] = '\0';
	return stk;
}

循环队列入队出队

若希望循环队列中的元素都能得到利用,需设置一个标志域tag,并以tag值为0或1来区分队头指针front和队尾指针rear相同时的队列状态是空还是满
编写入队和出队算法

算法思想

入队时设置tag为1,出队时设置tag为0,因为只有入队操作可能导致队满,出队操作才能导致队空
队列初始时,只tag=0,front=rear=0,
队空:front = rear,且tag=0
队满:front = rear,且tag=1
进队:data.rear=x,rear=(rear+1)%MaxSize,且tag=1
出队:x=data.front,front=(front+1)%MaxSize,且tag=0
![[Pasted image 20241116214934.png]]

要入栈时,如果front=rear且tag=1,代表队满,直接返回
否则在rear的位置赋值x
![[Pasted image 20241116215057.png]]

然后rear+1,往后移一位,因为要在数组空间内实现循环,所以要%一个最大空间大小
![[Pasted image 20241116215546.png]]

假如MaxSize是5,整个数组是0-4,rear是4,+1是5,%一个5,结果是0,返回到第一个位置
如果rear+1不足5,%一个5,数值不变
相反出栈的时候,如果front=rear,且tag=0,表示队列为空,直接返回
否则将front位置的数据赋给x,再front+1
![[Pasted image 20241116215612.png]]

int EnQueue(SqQueue &Q, ElemType 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;
}

int DeQueue(SqQueue &Q, ElemType x)
{
	if (Q.front == Q.rear && Q.tag == 0)
		return 0;
	x = Q.data[Q.front];
	Q.front = (Q.front + 1)%MaxSize;
	Q.tag = 0;
	return 1;
}

队列通过栈逆置

Q是一个队列,S是一个空栈,实现将队列里的元素逆置的算法

算法思想

通过栈将队列里的元素逆置,让队列中的元素逐个出队,然后入栈,再逐个出栈,入队,完成逆置

void Inverse(ST &S, Que &Q)
{
	// 如果队列不为空,持续出队
	while (!QueEmpty(Q))
	{
		// 将出队元素赋给x
		int x = DeQueue(Q);
		// 将x入栈
		STPush(S, x);
	}
	// 如果栈不为空
	while (!STEmpty(S))
	{
		// 持续出栈,入队
		STPop(S, x);
		EnQueue(Q, x);
	}
}

栈模拟队列

利用栈S1和S2模拟队列
已知

STPush(s,x)      //元素x入栈
STPop(s,x)       //S出栈,并把值赋给x
STEmpty(s)       //判断栈是否为空
STOverflow(s)    //判断栈是否满

实现

Enqueue(q,x)    //将元素x入队
Dequeue(q,x)    //出队,并将出队元素赋给x
QueEmpty(q)     //判断队列是否为空
算法思想

向队列插入元素的时候,用S1栈来存储元素,当出队时,对S2栈进行操作
因为存入S1栈后,再取出是逆序,所以需要先将S1栈中的数据逐个取出,入S2栈,再在S2执行出栈操作,实现出队,出队时需要判断S2栈是否为空
S1和S2都为空时,队列为空
如果入队,S1满后,可以将S1的元素移到S2中
![[Pasted image 20241116221622.png]]

int EnQueue(ST &S1, ST &S2, ElemType x)
{
	// 如果S1栈没有满,直接入栈S1
	if (!STOverflow(S1))
	{
		STPush(S1, x);
		return 1;
	}
	// 如果栈S1满,并且S2栈非空
	if (STOverflow(S1) && !STEmpty(S2))
	{
		printf("队列满");
		return 0;
	}
	// 如果栈S1满,并且S2栈空
	if (STOverflow(S1) && STEmpty(S2))
	{
		while (!STEmpty(S1))
		{
			STPop(S1, x);
			STPush(S2, x);
		}
	}
	STPush(S1, x);
	return 1;
}

void DeQueue(ST &S1, ST &S2, ElemType x)
{
	// 如果栈S2非空,直接出栈
	if (!STEmpty(S2))
	{
		STPop(S2, x);
	}
	// 如果栈2为空,且栈S1也为空,返回队列为空
	else if (STEmpty(S1));
	{
		printf("队列为空");
	}
	// 如果栈S1不为空
	else
	{
		// 将S1的依次出栈,入到S2去
		while (!STEmpty(S1))
		{
			STPop(S1, x);
			STPush(S2, x);
		}
	}
	// 在从S2出栈
	Pop(S2, x);
}

int QueEmpty(ST &S1, ST &S2)
{
	return STEmpty(S1) && STEmpty(S2);
}

循环链队入队和出队

在一个循环链队中,只有尾指针rear,给出出队和入队的实现过程

算法思想

当队空时,
![[Pasted image 20241116225250.png]]

rear指向一个哨兵节点,rear的next指向rear,队列为空
rear是带头节点的循环链队列的尾指针

  1. 入队时,需要在rear后插入节点
    ![[Pasted image 20241116225317.png]]

新malloc一个s节点,将s的data置为x
![[Pasted image 20241116225326.png]]

s的next指向rear的next
![[Pasted image 20241116225352.png]]

rear的next指向s
![[Pasted image 20241116225413.png]]

rear指向s
![[Pasted image 20241116225425.png]]

完成入队,现在队列里有一个元素
2. 出队时,需要判断队是否为空,如果只剩一个rear节点,rear的next指向rear自己,则队列为空,直接返回程序
![[Pasted image 20241116225626.png]]

如果队不为空,rear的next指向哨兵节点,rear的next的next指向第一个元素节点
创建指针s指向第一个节点
![[Pasted image 20241116225648.png]]

rear的next也就是h节点的next指向s的next
![[Pasted image 20241116225733.png]]

free掉s
如果s指向rear
![[Pasted image 20241116230027.png]]

头节点的next应该指向自己
![[Pasted image 20241116230054.png]]

rear指向rear的next

void EnQueue(LinkList rear, ElemType x)
{
	// 申请节点空间
	LNode* s = (LinkList)malloc(sizeof(LNode));
	s->data = x;
	s->next = rear->next;
	rear->next = s;
	rear = s;
}

void DeQueue(LinkList rear)
{
	if (rear->next == rear)
	{
		pritnf("队空");
		exit(0);
	}
	LNode* s = rear->next->next;
	rear->next->next = s->next;
	printf("出队元素", s->data);
	if (s == rear)
		rear = rear->next;
	free(s);
}

循环队列队尾删除和队头插入

允许循环队列在队头和队尾都可以进行插入和删除操作
写出循环队列的类型定义
写出从队尾删除和从队头插入的算法

算法思想

用一维数组0-M-1实现循环队列,M是队列长度,队头指针front和队尾指针rear
front指向队头元素的前一个位置,rear指向队尾元素
定义front=rear时为对队空,
(rear+1)%M=front为队满
队头入队下标减小,队尾入队下标增大
![[Pasted image 20241116231424.png]]

假如M是5,rear是4,(4+1)%5=0,rear返回0,也就是第一个位置

从队尾删除元素,需要判断队是否为空,如果front等于rear,表示队空
否则需要将rear指针左移一个位置,删除最后一个元素,
rear需要-1
因为左移的话需要考虑rear为0的情况,左移需要移动到M-1下标位置
rear-1如果大于等于0,比如2,(2+5)%5下标不变
如果rear-1为-1,(-1+5)%5 = 4,也就是M-1,最后一个位置
最后返回出队元素,因为rear已经减过1了,所以返回rear+1的位置的元素,需要+M并取模M

从队头入队
![[Pasted image 20241117103216.png]]

需要判断队是否为满,当rear循环过来,与front相邻的时候,队满
front-1等于rear,如果front-1等于-1,需要循环,所以要+M,并对M取模
如果队不满,将x赋值给front位置,然后front–
同样front-1需要+M并对M取模

#define M 100
typedef struct
{
	ElemType a[M];
	int front, rear;
}Que;

ElemType DeQueue(Que Q)
{
	if (Q.front == Q.rear)
	{
		printf("队空");
		exit(0);
	}
	Q.rear = (Q.rear - 1 + M) % M;
	return (Q.data[(Q.rear + 1 + M) % M]);
}

void EnQueue(Que Q, ElemType x)
{
	if (Q.rear == (Q.front - 1 + M) % M)
	{
		pritnf("队满");
		exit(0);
	}
	Q.data[Q.front] = x;
	Q.front = (Q.front - 1 + M) % M;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值