简单的数据结构

哇,最近终于交上了作业,下一份作业……暂时还不及,所以就勉勉强强终于晚上不用熬夜思考作业了,开始思考我的选修课了……,啊,哭唧唧,选修课马上要结束了 ,就要去每周三周五的小比赛了,我到底有多菜就要暴漏在公众的目光之中了,不过应该没有那么多人注意到我,安心安心……

嗯,接下来好好写一下最后学的东西,这个再拖就忘没影子了。 

一.栈

栈是一种只支持栈顶插入(进栈)或者删除(出栈)的一种特殊线性表 。

此栈非彼栈,在学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的晚上十一点半,还是先去睡觉,狗命要紧,以后再看看那个遍历,以及这个例题还没看明白呢!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值