任何一个算法的设计取决于选定的数据结构,而算法的实现依赖于采用的存储结构。
之前线性表的数据元素都是非结构的原子类型,元素的值是不可再分的。下面学习的这两个线性表是很特殊的,其中数据元素本身也可能是一种数据结构。
认识数组和广义表
数组可以看成是一种特殊的线性表,也就是线性表中的数据元素本身也是一个线性表,数组中的个元素具有统一的类型。其实说白了就是在脑海中想数组中的数据如何在内存中以什么形式的线性表来存储。在C语言中,一个二维数组可以定义为其分量类型为一维数组类型的一维数组类型。
数组一旦被建立,数组中的维度和维界就不再改变,即数组中的数据元素数目固定,并且数组中每个数据元素都和唯一的一组下标值对应。也就是数组的元素个数和数据元素之间的关系就不能发生变化,所以不会有元素的插入和删除等操作,其基本操作主要是数据元素的读取和更新。
由于内存空间是一维的结构,而数组元素之间的位置是有规律的,因此用一维连续存储单元存放数组的数据元素就有个次序约定问题。
广义表简称表,和之前数组是一类的,但是还有点区别,广义表中的不同元素可以有不同的结构,它是一种递归的数据结构。
广义表有三个重要的结论:
- 层次性:里面的元素可以是子表,子表中的元素也可以是子表,是一个多层次结构
- 共享性:可以其它表所共享。
- 递归性:可以是其自身的子表。
这里面就是说在广义表里面的一个空间中在有一个结构体,里面保存数据的值。之前的内容主要是指针。总感觉广义表这些东西是内存中需要保存的,我们做程序的大体知道就OK,不需要说把里面的细节全部的会。
树和二叉树
之前了解的栈,队列,数组,广义表等都是线性结构,而树是一种非线性结构,但是一种分层结构,树和二叉树是处理层次模型的典型结构。
- 树的定义:是n个结点的有限集,在任意一颗非空树中有且只有一个称为根(root)的结点,其余的结点被分为m个互不相交的有限集,其中每个集合本身又是一颗树,称为跟结点的子树。
图示法表示二叉树的一个结论:
- 边的数目恰好比结点数目少一个,即e=n-1;
- 节点分为根节点,分支结点,叶子结点。
- 度分为结点的度和树的度,结点的度是指该结点相连的孩子结点的数目,树的度是指树中所有结点的度的最大值。
- 树是一种分层结构,根结点作为第一层,结点的层次(树深度)是指从根结点开始到该结点的层次数,树的深度是指该树中所有结点的层次的最大值。
- 森林是m颗互不相交的树的集合。对于树中的每个结点而言,其子树的集合及时森林。
- 二叉树是一种特殊的有向树,也叫二元位置树。特点是每个结点至多有两棵子树,即二叉树中的每个结点至多有两个孩子结点,且每个孩子结点都有各自的位置关系。
二叉树定义:二叉树或者可以为空,或者是由一个根结点加上两棵分别称为左子树和右子树的,互不相交的二叉树组成。
- 满二叉树:就是除叶子结点外的任何结点均有两个孩子结点,且所有的叶子结点都在同一层上的二叉树。特点是每一层上的结点树是最大的。
- 完全二叉树:除去最底层结点后的二叉树是一颗满二叉树,且最底层结点均靠左对其的二叉树。
算法中处理的事件有两类,一类是客户到达事件,另一类是客户离开事件,前一类事件发生的时刻随客户到来自然形成;后一类事件发生的时刻按先后顺序进行,则由客户事务所需时间和等待所耗的时间而定。
递归定义的基本项描述了一个或几个递归过程的终结状态,虽然一个有限的递归(无迭代)可以描述一个无限的计算过程,但任何实际应用的递归过程除错误情况外,必定能经过有限层次的递归而终止。所谓终结状态指的是不需要继续递归而可直接求解的状态。
递归定义的归纳项描述了如何实现从当前状态到终结状态的转换。
递归设计的实质:当一个复杂的问题可以分解成若干个子问题来处理时,其中某些子问题与原问题有相同的特征属性,则可利用和原问题相同的分析处理方法,反之这些子问题解决了原问题也解决了。
/* 二叉树的存储 */ #define VirNode '0'; #define MAX_TREE_SIZE 100; typedef char ElemType; typedef ElemType SqBitTree[MAXZ_TREE_SIZE]; //SqBitTree[0]单元存放结点的总数,通常存放构成满二叉树时的结点总数; //二叉树的层次遍历算法 void leveltree(SqBitTree bt){ int i,j; i=1; while(i<=bt[0]){ for(j=i;j<2*i;j++){ if(bt[j]==VirNode) printf("*"); else printf("%c",bt[j]); } printf(" "); i=2*i; } } //二叉树的按层次建立算法 void crebitree(){ int i,j,m; i=1;m=0; while(m<n){ for(j=i;j<2*i;j++){ scanf("%c",bt+j); if(bt[j]!=VirNode) m++; } i=2*i; } bt[0]=i-1; } //交换二叉树中所有结点的左右子树算法 void exchangetree(SqBitTree bt){ int k=2,i,j;ElemType t; while(k<=bt[0]){ for(i=k,j=2*k-1;i<j;i++,j--){ t=bt[i]; bt[i]=bt[j]; bt[j]=t; } k=2*k; } } //统计叶子结点的个数 int countleaf(SqBitTree bt){ int i,j,n; i=1;n=0; while(i<=bt[0]/2){ for(j=i;j<2*i;j++) if(bt[j]!=VirNode&&bt[2*j]==VirNode&&bt[2*j+1]==VirNode) n++; i=2*i; } for(j=i;j<2*i;j++) if(bt[j]!=VirNode) n++; return n; } //求二叉树的高度 int height(SqBitTree bt){ int i,j,h; i=1;h=0; while(i<=bt[0]){ h++; i=2*i; } return h; }
二叉树的遍历就是依次访问二叉树中的各个结点,而且每个结点仅被访问一次。遍历的3中方式有,先序遍历,中序遍历,后序遍历。这个主要是看根结点在什么地方,比如第一个,先序,那么就是根左右,中序就是左根右,后序就是左右根;
认识图
线性表是一种一对一的相邻关系,树是一种一对多的层次关系,图是一种多对多的网状关系,