背景
现有如下问题:
现在给出一串字符串,里面的括号要不成双出现,要不就不出现,也就是括号匹配。括号的种类包括’{’、’}’、’(’、’)‘、’[’、’]’。请大家判断字符串是否括号匹配,是则输出"Yes",反之输出"No"。
问题分析
几种可能的方法:
1.设立计数器,如果遇到 ( 或 { 或 [ 对应的计数器就 ++ ,如果遇到 ) 或 } 或 ] 对应的计数器就 --。
可行性分析:如果遇到的是 ({[]}) 型的字符串可以通过,并且空间复杂度O(1),时间复杂度O(N)。但是如果遇到 }])([{ 的情况,明显括号没有匹配,但是该方法显示通过。
#include<stdio.h>
#include<string.h>
int main()
{
char a[100];
scanf("%s",a);
int x=0,y=0,z=0;
int n=strlen(a);
int count,count1,count2;
int ret;
for(count=0;count<n;count++)
{
if(a[0]==')'||a[0]=='}'||a[0]==']')
{
x=1;
break;
}
if(a[count]=='(')
x++;
if(a[count]=='{')
y++;
if(a[count]=='[')
z++;
if(a[count]==')')
x--;
if(a[count]=='}')
y--;
if(a[count]==']')
z--;
}
if(x==0&&y==0&&z==0)
printf("Yes");
else
printf("No");
return 0;
}
2.在方法1的基础上建立标记,初始标记为flag=0,如果遇到 ( 或 { 或 [ ,对应的flag标记为1,如果遇到 ) 或 } 或 ] 并且flag=1时, 进入判断并且对应的flag标记为0。
可行性分析:这样避免了}{ 、)( 、][ 被识别为匹配的情况,但是这也有一个极大的隐患:如果为(( ))的情况时,flag的变化情况为:(初始)0-1-1-0-0,导致最后一个 )甚至无法进入判断。
#include<stdio.h>
#include<string.h>
int main()
{
char a[100];
scanf("%s",a);
int x=0,y=0,z=0;
int n=strlen(a);
int count,count1,count2;
int ret;
int flag1=0,flag2=0,flag3=0;
for(count=0;count<n;count++)
{
if(a[count]=='(')
{
flag1=1;
x++;
}
if(a[count]=='{')
{
flag2=1;
y++;
}
if(a[count]=='[')
{
flag3=1;
z++;
}
if(a[count]==')'&&flag1==1)
{
x--;
flag1=0;
}
if(a[count]=='}'&&flag2==1)
{
y--;
flag2=0;
}
if(a[count]==']'&&flag3==1)
{
z--;
flag3=0;
}
}
if(x==0&&y==0&&z==0)
printf("Yes");
else
printf("No");
return 0;
}
3.(忍无可忍)(逼上梁山) 怎么办?有没有什么万全之策?我们可以用栈来分析。
栈
如果想进一步了解栈,可以参考一下我的另一篇博文: https://blog.csdn.net/zjnyly/article/details/104435808
这篇文章是在模拟的层面(函数调用)来模拟程序在计算机内存中类似栈的结构的运行。
栈的运行方式
所谓数据结构,就是数据存储的方式。
最常见的数据结构有数组、链表以及栈等。本文我们就围绕这几个数据结构展开讨论。
其中,栈的运行模式很适合作为我们这道题的解决方案:
栈是如何运行的?记住一个原则:先入后出。比如有一串数据——1 2 3 4 5 6;我们来模拟一下数据在栈中的存储方式:
1 //入栈
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6 //出栈
1 2 3 4 5
1 2 3 4
1 2 3
1 2
1
至此,这一串数就完成了入栈和出栈的过程。有没有发现,栈的顶部一直都是上一个输入的数据?
这和我们的这道题目有什么关系呢?假设这道题目给出的字符串是
abc(abc){haha}{([])}
首先要明确什么情况入栈,什么情况出栈。这道题我们可以明确为:遇到左括号就入栈,遇到右括号,先将其入栈,如果栈中的前两个数据匹配为() 、{} 、[] 那么我们就让他们出栈。(为什么要让右括号先入栈呢?为什么不能直接判断栈顶元素是否与右括号匹配?说起来都是泪,经过一番摸索,我才明白了栈的真实含义以及如何真正地使用栈这个数据结构,这也是我写这篇文章的原因,后续我可能会讲到)
我们来模拟一下这个字符串入栈和出栈的过程
(
()
空
{
{}
空
{
{(
{( [
{( [ ]
{(
{( )
{
{ }
空
遍历结束。我们发现如果字符串匹配的话,那么栈结构最终是空的。
如果不匹配,你可以自己模拟一下,肯定至少存在一个元素在栈中。
我们再把讨论方案一和二的干扰样例再栈这个方法中模拟一下,发现都可以完美排除,至此,我们明确了这道题的最佳解法——栈。
建栈
栈这么好用我们当然要学会怎么建栈啦,以后遇到类似情形,我们都可以用栈来解决。
建栈的方法:
数组 || 链表
为什么数组和链表可以建栈呢?
我们发现栈是一个顺序的数据结构,他和数组的存储方式十分相似,数组一个数接着一个数往后存,栈一个数一个数往里进。
链表为什么可以呢?链表也是顺序的线性存储结构,我们甚至可以认为链表这个数据结构的实现就是为了模拟数组,继而推之,模拟栈。
数组空间的分配一定是一段连续的内存空间,如果我们要很大的空间,而内存整体很混乱,只有大量的碎片空间时,我们就可以用链表,用链子将这些碎片空间有序地串在一起,达到高效利用空间并且实现数组功能的目的,链表中的每一个环节都可以想象成一个数组,例如我们这样定义链表结构
struct node
{
int value[10];
struct node* next;
};
这就是将一系列数组串起来了
struct node
{
int value;
struct node* next;
};
或者像本文一样,就建立每个节点仅存储一个值的单向链表。
栈的调用
我们可以对栈进行什么样的操作呢?
//对栈进行初始化
void InitiateStack(Stack* stack);
//将数据压入栈
void push(Stack* stack, const int val);
//将数据弹出栈
void pop(Stack* stack);
//判断栈是否为空
int empty(const Stack* stack);
//获取栈顶的数据
int top(const Stack* stack);
//获取栈的大小
int size(const Stack* stack);
//按顺序遍历并且打印栈中数据
void print(const Stack* stack);
栈的调用之数组实现
int stack[256];
int size=0;
void InitiateStack(int stack[])
{
//这一个函数在数组实现中并不需要
}
//将数据压入栈
void push(int stack[], const int val)
{
stack[size]=val;
size++;
}
//将数据弹出栈
void pop(int stack[])
{
if(size>0)
{
stack[size]=0;
size--;
}
}
//判断栈是否为空
int empty(const int stack[])
{
return size==0;
}
//获取栈顶的数据
int top(const int stack[])
{
return stack[size];
}
//获取栈的大小
int size(const int stack[])
{
return size+1;
}
//按顺序遍历并且打印栈中数据
void print(const Stack* stack)
{
int cnt=0;
while(stack[cnt++]!=0)
{
printf("%d",stack[cnt++]);
}
}
数组的实现方法很简单,应该不用赘述,不过以上代码未经过编译,也没有进行实际测验,如有错误多多包涵:)
栈的调用之链表实现
我们先来看下一整套函数,然后再细讲:
typedef struct Node
{
int val;
struct Node* next;
} node;
typedef struct
{
int size;
node* top;
} Stack;
void InitiateStack(Stack* stack)
{
stack->top=NULL;
stack->size=0;
}
void push(Stack* stack, const int val)
{
node* newNode=(node*)malloc(sizeof(node));
newNode->next=stack->top;
newNode->val=val;
stack->top=newNode;
stack->size=stack->size+1;
}
void pop(Stack* stack)
{
node* temp;
temp=stack->top;
stack->top=temp->next;
stack->size=stack->size-1;
free(temp);
}
int empty(const Stack* stack)
{
if(stack->size==0)
return 1;
else
return 0;
}
int top(const Stack* stack)
{
if(stack->size == 0)
{
return 0;
}
return stack->top->val;
}
int size(const Stack* stack)
{
return stack->size;
}
void print(const Stack* stack)
{
node* temp=stack->top;
while(temp->next!=NULL)
{
printf("%d->",temp->val);
temp=temp->next;
}
printf("null\n");
}
比数组复杂很多对吗?但是为什么本文主要选择讲述链表建栈呢?原因是当处理更多的数据时,链表显然能够高效地利用一切内存空间,作为程序员,这应该是对于自己写出的代码的一大追求:可靠,高效。
高效地利用内存空间体现在两点:1.当分配内存空间时,我们是链接碎片空间,而非像数组一样的连续空间。 2.当我们将栈顶地数据弹出时,由于我们使用了malloc函数,动态分配的堆中内存可以随时被free函数清理掉,而数组仅仅是将下标回退一位。(附:内存中也有栈、堆等结构,但是还是和本文中地栈以及数据结构堆有所区别的,更类似与我给出的链接中讲述地栈)
好了我们来具体分析一下代码
typedef struct Node
{
int val;
struct Node* next;
} node;
typedef struct
{
int size;
node* top;
} Stack;
typedef的作用是将struct Node宏替换为更简洁的node。
这里选择定义了两个结构体,第一个结构体描述的是链表中的节点,其中node . val表示节点存储的值,node . next 代表一个指针,这个指针指向的数据类型是我们定义的node数据结构,用途是实现向下一个节点的连接。
第二个结构体是一个头指针,具体原因是因为我们利用的单链表更新中的头插法,稍后我们再详细讲述该方法。这个结构体包括两个成员,一个是Stack . size,用于表示链表中存储的数据的总数,另一个是Stack . top,这也是一个指针,仔细观察,是一个指向上一个结构体定义的node节点的指针,我们将这个指针指向链表的第一个node节点。
头插法
这个我不会讲太多,因为目前我们设计的链表更多是为了实现栈的功能,因此其实是个功能不齐全的链表。
什么是头插法?为什么要头插?试想一下,我们如何在内存的茫茫大海中找到我们的这一串链表?我们需要知道链表的头节点在哪,知道了头节点的内存地址,我们就可以随时想调用这个链表就调用。
如果头节点要删除呢?如果头节点前面想插入一个新节点呢?毕竟链表的一大好处就是可以随心所欲地在每一个环节增删数据。这就导致一个问题——我们现在不知道原有的头节点的内存地址了,新的头节点是一个新的地址。链表不像数组,你将原有地址+/-1就可以得到新地址。我们的线索断掉了!要想实时定位这个链表,我们就必须一直返回新的头节点的地址给我们的定位器。
有没有什么好方法?我们可以把第一个节点放在第二个节点,这时我们对该链表定义头节点就是第二个节点,而第一个节点就是一个固定的地址确定器。要插入新的头节点,就是在实际的第一和二节点之间插入。要查询新的头节点,我们只需要用实际的第一节点指向它就好了,而实际第一节点我们是始终知道的。
(注意一点,为了讲述方便,我将“实际的第一节点”定义为节点,而实际不是这样的。这个节点,它实际是个指针,这个指针是一个指向节点的指针,由于它太像节点了,他可以通过自己的pointer->next找到头节点,就跟头节点能够通过自己的next找到下一个节点一样。
但是第一个节点实际上就只有指针成分,而真正的节点既有指针,又有存储的值。怎么区别呢?看有没有malloc一个节点,如果有的话,我们就把他视为真的节点,如果只是声明一个指针,那么他就是一个伪节点。
具体分析:
更具体地链表实现、内存管理我会在下一个篇章描述,这里就简单展示一下
对栈进行初始化
void InitiateStack(Stack* stack)
{
stack->top=NULL;
stack->size=0;
}
stack指针就是我们所说的伪节点,他一开始不指向链表元素,所以置为NULL;
将数据压入栈
void push(Stack* stack, const int val)
{
node* newNode=(node*)malloc(sizeof(node));
newNode->next=stack->top;
newNode->val=val;
stack->top=newNode;
stack->size=stack->size+1;
}
由于我们模拟的是栈,真头节点就是栈中压入的数据,所以请看以下示意图
首先通过原有的指针,获取真第一节点的内存位置
新真第一节点指向旧真第一节点
原有指针改变指向,指向新真第一节点
结构体stack中的成分size++
这就完成了元素的插入
将数据弹出栈
void pop(Stack* stack)
{
node* temp;
temp=stack->top;
stack->top=temp->next;
stack->size=stack->size-1;
free(temp);
}
首先通过指针指向第一真节点,通过第一真节点指向第二真节点
然后删除第一真节点
结构体stack中的成分size–
判断栈是否为空
int empty(const Stack* stack)
{
if(stack->size==0)
return 1;
else
return 0;
}
如果结构体stack中的成分size为0,说明指针没有指向任何一个链表节点。
获取栈顶元素
int top(const Stack* stack)
{
if(stack->size == 0)
{
return 0;
}
return stack->top->val;
}
如果栈元素为零,说明栈中没有元素
否则,通过指针找到第一真节点,获取值
获取栈的大小
int size(const Stack* stack)
{
return stack->size;
}
返回结构体stack中的成分size的值
这道题用链表实现栈用来解决问题的具体实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Node
{
int val;
struct Node* next;
} node;
typedef struct
{
int size;
node* top;
} Stack;
void InitiateStack(Stack* stack)
{
stack->top=NULL;
stack->size=0;
}
void push(Stack* stack, const int val)
{
node* newNode=(node*)malloc(sizeof(node));
newNode->next=stack->top;
newNode->val=val;
stack->top=newNode;
stack->size=stack->size+1;
}
void pop(Stack* stack)
{
node* temp;
temp=stack->top;
stack->top=temp->next;
stack->size=stack->size-1;
free(temp);
}
int empty(const Stack* stack)
{
if(stack->size==0)
return 1;
else
return 0;
}
int top(const Stack* stack)
{
if(stack->size == 0)
{
return 0;
}
return stack->top->val;
}
int size(const Stack* stack)
{
return stack->size;
}
void print(const Stack* stack)
{
node* temp=stack->top;
while(temp->next!=NULL)
{
printf("%d->",temp->val);
temp=temp->next;
}
printf("null\n");
}
int main() {
Stack mystack;
InitiateStack(&mystack);
char a[100];
scanf("%s",a);
for(int i=0;i<strlen(a);i++)
{
if(a[i]=='{'||a[i]=='['||a[i]=='(')
{
push(&mystack,a[i]);
}
if(a[i]=='}'||a[i]==']'||a[i]==')')
{
int temp1=top(&mystack);
push(&mystack,a[i]);
int temp2=top(&mystack);
if(!empty(&mystack))
{
if(temp1=='('&&temp2==')')
{
pop(&mystack);
pop(&mystack);
}
if(temp1=='['&&temp2==']')
{
pop(&mystack);
pop(&mystack);
}
if(temp1=='{'&&temp2=='}')
{
pop(&mystack);
pop(&mystack);
}
}
else
break;
}
}
if(!empty(&mystack))
printf("No");
else
printf("Yes");
return 0;
}