哇,最近终于交上了作业,下一份作业……暂时还不及,所以就勉勉强强终于晚上不用熬夜思考作业了,开始思考我的选修课了……,啊,哭唧唧,选修课马上要结束了 ,就要去每周三周五的小比赛了,我到底有多菜就要暴漏在公众的目光之中了,不过应该没有那么多人注意到我,安心安心……
嗯,接下来好好写一下最后学的东西,这个再拖就忘没影子了。
一.栈
栈是一种只支持栈顶插入(进栈)或者删除(出栈)的一种特殊线性表 。
此栈非彼栈,在学stl时,我们也学过一个栈名叫stack,可以直接进行进栈和出栈的操作,但是这里是用数组来模拟一个栈,进行各种操作(omg,实在不知道为什么要这样找麻烦,莫非就是为了深入了解这个数据结构??)
一个栈可以用定长为N的数组S来表示,用一个栈指针TOP指向栈顶。若TOP=0,表示栈空,TOP=N时栈满。进栈时TOP加1。退栈时TOP减1。当TOP<0时为下溢。栈指针在运算中永远指向栈顶。
进栈push,退栈pop算法函数呈现:(实际并不需要指针,用数字也可以代替这个指针)
#define n 100
void push(int s[],int top,int x) //入栈
{
if (top==n) printf("overflow");
else { (top)++; s[top]=x; }
}
void pop(int s[],int y,int top) //出栈
{
if (*top==0) printf("underflow");
else { y=s[top]; (top)--; }
}
简单例题:
(1)括号匹配
题目概述:
假设一个表达式有英文字母(小写)、运算符(+,—,*,/)和左右小(圆)括号构成,以“@”作为表达式的结束符。请编写一个程序检查表达式中的左右圆括号是否匹配,若匹配,则返回“YES”;否则返回“NO”。假设表达式长度小于255,左圆括号少于20个。
不由得想到动态规划……可惜我学业不精,不知道是否确实是用动态规划可行,即使知道是用动态规划也不是很会。啊!
可以定义一个栈:char s[maxn+1]; int top;用它来存放表达式中从左往右的左圆括号(maxn=20)。
顺序(从左往右)扫描表达式的每个字符c[i],若是“(”,则让它进栈;若遇到的是“)”,则让栈顶元素出栈;当栈发生下溢或当表达式处理完毕而栈非空时,都表示不匹配,返回“NO”;否则表示匹配,返回“YES”;
#include<cstdio>
#include<cstdlib>
using namespace std;
#define maxn 20
char c[256];
bool judge(char c[256]);
int main()
{
scanf("%s",c);
if (judge(c))printf("YES");
else printf("NO");
return 0;
}
bool judge(char c[256])
{
int top=0,i=0;
while (c[i]!='@') //没有到最后一个字符
{
if (c[i]=='(') top++;//相当于将左括号放进栈里面,实际并没有,可能这就是不用stack的原因,模拟!
if (c[i]==')')
{
if (top>0) top--;
else return 0;//这样子就是没有左括号情况下先有了右括号,自然是不行的,直接输出no
}
i++;
}
if (top!=0) return 0; //检测栈是否为空。不空则说明有未匹配的括号
else return 1;
}
(2)高级版括号匹配
从键盘上输入算术表达式串(只含+、-、×、÷运算符,允许含括号),输出算术表达式的值。设输入的表达式串是合法的。
建立两个栈,一个是操作数栈(number),一个是运算符栈(symbol),根据运算符的优先级对两个栈进行相应的操作。
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
using namespace std;
int number[101],i=0, p=1;
char symbol[101],s[256], t[256];
//操作
void push() //运算符入栈运算
{
symbol[++p]=s[i];
}
void pop() //运算符栈顶元素出栈,并取出操作数栈元素完成相应的运算
{
switch (symbol[p--])//p先操作,在-1
{
case '+':number[p]+=number[p + 1];break;
case '-':number[p]-=number[p + 1];break;
case '*':number[p]*=number[p + 1];break;
case '/':number[p]/=number[p + 1];break;
}
}
bool can() //判断运算符的优先级别,建立标志函数
{
if ((s[i]=='+'||s[i]=='-')&&symbol[p]!='(') return 1;
if ((s[i]=='*'||s[i]=='/')&&(symbol[p]=='*'||symbol[p]=='/'))return 1;
return 0;
}
main()
{
gets(s);
s[strlen(s)]=')';//将待运算的字符串最后加上一个右括号
symbol[p]='(';//操作符开始加上左括号,与待运算字符串加的右括号相匹配
while (i<strlen(s))
{
while (s[i]=='(') //遇见左括号先放入运算符栈
{
push();
i++;
}
int x=0;
while (s[i]>='0'&&s[i]<='9') //将操作数分成一个个数字而不再是字符串,并放入数字栈
x=x*10+s[i++]-'0';
number[p]=x;
do
{
if (s[i]==')') //右括号处理
{
while (symbol[p]!='(') pop();//如果不是左括号,证明还没运算完括号里面的
number[--p]=number[p + 1];//此时p已经减去1,要换成p+1时的数
}
else//没有括号的时候,先乘除,后加减
{
while (can()) pop();
push();
}
i++;
}while (i<strlen(s)&&s[i-1]==')');
}
printf("Result=%d", number[0]);
return 0;
}
二.队列
队列是限定在一端进行插入,另一端进行删除特殊线性表。跟栈一样,stl中也有队列,同样的是这里也是注重模拟,不注重栈的存储之类的使用。
队列的删除和插入分别称为出队和入队。允许出队的一端称为队头,允许入队的一端称为队尾。
队列可以用数组Q[m+1]来存储,数组的上界m即是队列所容许的最大容量。在队列的运算中需设两个指针:
head:队头指针,指向实际队头元素的前一个位置
tail:队尾指针,指向实际队尾元素所在的位置
队列有一个防止溢出的方法:变成环。如此即使存储多了元素,也只是占了开头,并不会造成溢出。
循环队的入队算法如下:
1、tail=tail+1;
2、若tail=n+1,则tail=1;
3、若head=tail尾指针与头指针重合了,表示元素已装满队列, 则作上溢出错处理;
4、否则,Q[tail]=x,结束(x为新入出元素)。
例题:
很好的一个题,没怎么用队列,用的是深搜。但是很喜欢他,老师也用在这了,就只整理这一个好了。
题目:
一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。如:
阵列 4 10
0234500067
1034560500
2045600671
0000000089
有4个细胞。
分析:搜索过去,遇见不是0的就深搜,然后将所有连着的数字都变成0。n++即可。
#include<cstdio>
using namespace std;
char a[51][51];
bool b[51][51];
int n,m,i,j,s=0,c[5]={1,0,-1,0,1};
void f(int,int);
main()
{
scanf("%d%d\n",&n,&m);
for (i=0;i<n;i++)
gets(a[i]);
for (i=0;i<n;i++)
for (j=0;j<m;j++)
b[i][j]=a[i][j]-'0';
for (i=0;i<n;i++) //在矩阵中寻找细胞
for (j=0;j<m;j++)
if (b[i][j])
{
b[i][j]=0;
f(i,j);//深搜
s++;
}
printf("%d",s);
return 0;
}
void f(int x,int y)
{
for (int i=0;i<=4;i++)
if (x+c[i]>-1&&x+c[i]<n&&y+c[i+1]>-1&&y+c[i+1]<m&&b[x+c[i]][y+c[i+1]])
{ //沿细胞的上下左右四个方向搜索细胞
b[x+c[i]][y+c[i+1]]=0;
f(x+c[i],y+c[i+1]);
}
}
三.树及二叉树
一棵树是由n(n>0)个元素组成的有限集合,其中:
(1)每个元素称为结点(node);
(2)有一个特定的结点,称为根结点或树根(root);
(3)除根结点外,其余结点能分成m(m>=0)个互不相交的有限集合T0,T1,T2,……Tm-1。其中的每个子集又都是一棵树,这些集合称为这棵树的子树。
树的基本概念:
一个结点的子树个数,称为这个结点的度;度为0的结点称为叶结点;度不为0的结点称为分支结点;根以外的分支结点又称为内部结点;树中各结点的度的最大值称为这棵树的度。
在用图形表示的树型结构中,对两个用线段(称为树枝)连接的相关联的结点,称上端结点为下端结点的父结点,称下端结点为上端结点的子结点。称同一个父结点的多个子结点为兄弟结点。称从根结点到某个子结点所经过的所有结点为这个子结点的祖先。称以某个结点为根的子树中的任一结点都是该结点的子孙。
定义一棵树的根结点的层次(level)为1,其它结点的层次等于它的父结点层次加1。一棵树中所有的结点的层次的最大值称为树的深度(depth)。
对于树中任意两个不同的结点,如果从一个结点出发,自上而下沿着树中连着结点的线段能到达另一结点,称它们之间存在着一条路径。可用路径所经过的结点序列表示路径,路径的长度等于路径上的结点个数减1。
森林(forest)是m(m>=0)棵互不相交的树的集合。
树的遍历:
先序(根)遍历:先访问根结点,再从左到右按照先序思想遍历各棵子树。
后序(根)遍历:先从左到右遍历各棵子树,再访问根结点。
层次遍历:按层次从小到大逐个访问,同一层次按照从左到右的次序。
二叉树基本概念:
二叉树(binary tree,简写成BT)是一种特殊的树型结构,它的度数为2的树。即二叉树的每个结点最多有两个子结点。每个结点的子结点分别称为左孩子、右孩子,它的两棵子树分别称为左子树、右子树。
二叉树的性质:
1.在二叉树的第i层上最多有2^(i-1)个结点(i>=1)。
2.深度为k的二叉树至多有2^k –1个结点(k>=1)。
3.一棵深度为k且有2k–1个结点的二叉树称为满二叉树。
4.对任意一棵二叉树,如果其叶结点数为n0,度为2的结点数为n2,则一定满足:n0=n2+1。
5.¢具有n个结点的完全二叉树的深度为floor(log2n)+1
6.对于一棵n个结点的完全二叉树,对任一个结点(编号为i),有:
①如果i=1,则结点i为根,无父结点;如果i>1,则其父结点编号为i/2。
如果2*i>n,则结点i无左孩子(当然也无右孩子,为什么?即结点i为叶结点);否则左孩子编号为2*i
②如果2*i+1>n,则结点i无右孩子;否则右孩子编号为2*i+1。
二叉树的遍历:
(1)先序遍历:
若二叉树为空,则空操作,否则
①访问根结点
②先序遍历左子树
③先序遍历右子树
void preorder(tree bt)
{
if(bt)
{
cout << bt->data;
preorder(bt->lchild);
preorder(bt->rchild);
}
}
(2)中序遍历
若二叉树为空,则空操作,否则
①中序遍历左子树
②访问根结点
③中序遍历右子树
void inorder(tree bt) //中序遍历根结点为bt的二叉树的递归算法
{
if(bt)
{
inorder(bt->lchild);
cout << bt->data;
inorder(bt->rchild);
}
}
(3)后序遍历
若二叉树为空,则空操作,否则
①后序遍历左子树
②后序遍历右子树
③访问根结点
void postorder(tree bt) //后序遍历根结点为bt的二叉树的递归算法
{
if(bt)
{
postorder(bt->lchild);
postorder(bt->rchild);
cout << bt->data;
}
}
二叉树的其他操作:
有点深搜的感觉。真希望我能掌握深搜呀
(1)建立一棵二叉树
void pre_crt(tree &bt) //按先序次序输入二叉树中结点的值,生成
{
char ch;
ch = getchar(); //二叉树的单链表存储结构,bt为指向根结点的指针,'$'表示空树
if(ch != '$')
{
bt = new node; //建根结点
bt->data = ch;
pre_crt(bt->lchild); //建左子树
pre_crt(bt->rchild); //建右子树
}
else bt = NULL;
}
(2)删除二叉树
void dis(tree &bt) //删除二叉树
{
if(bt)
{
dis(bt->lchild); //删左子树
dis(bt->rchild); //删右子树
delete bt; //释放父结点
}
}
(3)插入一个节点到排序二叉树中
void insert(tree &bt, int n) //插入一个结点到排序二叉树中
{
if(bt)
{
if(n < bt->data) insert(bt->lchild, n);
else if(n > bt->data) insert(bt->rchild, n);
}
else
{
bt = new node; //新开一个空间
bt->data = n;
bt->lchild = bt->rchild = NULL;
}
}
(4)在排序二叉树中查找节点
tree findn(tree bt, int n) //在二叉树中查找一个数,找到返回该结点,否则返回NULL。
{
if(bt)
{
if(n < bt->data) findn(bt->lchild, n);
else if(n > bt->data) findn(bt->rchild, n);
else return bt;
}
else return NULL;
}
(5)用嵌套括号的方法输出二叉树:我怎么感觉只输出了一层,好奇怪
void print(tree bt) //用嵌套括号表示法输出二叉树
{
if(bt)
{
cout << bt->data;
if(bt->lchild || bt->rchild)
{
cout << '(';
print(bt->lchild);
if(bt->rchild) cout << ',';
print(bt->rchild);
cout << ')';
}
}
}
一个结论:
已知先序序列和中序序列可以确定出二叉树;
已知中序序列和后序序列也可以确定出二叉树;
但,已知先序序列和后序序列却不可以确定出二叉树;
例题:
求后序遍历:
输入二叉树的先序以及中序遍历,输出其后序遍历:
#include<iostream>
#include<fstream>
using namespace std;
string s1, s2;
void calc(int l1, int r1, int l2, int r2)
{
int m = s2.find(s1[l1]);
if(m > l2) calc(l1 + 1, l1 + m - l2, l2, m - 1);
if(m < r2) calc(l1 + m - l2 + 1, r1, m + 1, r2);
cout << s1[l1];
}
int main()
{
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
cin >> s1 >> s2;
calc(0, s1.length() - 1, 0, s2.length() - 1);//先序第一最后的数,中序第一,最后的数
cout << endl;
return 0;
}
今天更新先到这,这里是5.18的晚上十一点半,还是先去睡觉,狗命要紧,以后再看看那个遍历,以及这个例题还没看明白呢!