一、简答题1、链表:链表就是一串存储数据的链式结构。
链式的优点在于,每个数据之间都是相关联的。
2、线性结构:线性结构是一个有序数据元素的集合。
常用的线性结构有:线性表,栈,队列,双队列,数组,串。
3、树与二叉树二叉树是每个结点最多有两个子树的有序树;树是由n(n>=1)个有限节点组成一个具有层次关系的集合。
树和二叉树的2个主要差别:1. 树中结点的最大度数没有限制,而二叉树结点的最大度数为2;2. 树的结点无左、右之分,而二叉树的结点有左、右之分。
4、堆堆通常是一个可以被看做一棵树的数组对象。
堆总是满足下列性质:堆中某个节点的值总是不大于或不小于其父节点的值;堆总是一棵完全二叉树。
5、二叉排序树二叉排序数的(递归)定义:1、若左子树非空,则左子树所有节点的值均小于它的根节点;2、若右子树非空,则右子树所有节点的值均大于于它的根节点;3、左右子树也分别为二叉排序树。
二、应用题1、树与二叉树①前中后序遍历序列一、已知前序、中序遍历,求后序遍历例:前序遍历: GDAFEMHZ中序遍历: ADEFGHMZ画树求法:第一步,根据前序遍历的特点,我们知道根结点为G第二步,观察中序遍历ADEFGHMZ。
其中root节点G左侧的ADEF必然是root的左子树,G右侧的HMZ必然是root的右子树。
第三步,观察左子树ADEF,左子树的中的根节点必然是大树的root的leftchild。
在前序遍历中,大树的root的leftchild位于root之后,所以左子树的根节点为D。
第四步,同样的道理,root的右子树节点HMZ中的根节点也可以通过前序遍历求得。
在前序遍历中,一定是先把root和root的所有左子树节点遍历完之后才会遍历右子树,并且遍历的左子树的第一个节点就是左子树的根节点。
同理,遍历的右子树的第一个节点就是右子树的根节点。
②树与二叉树的转换树转换为二叉树:二叉树转换为树:③二叉树线索化注意:图中的实线表示指针,虚线表示线索。
结点C的左线索为空,表示C是中序序列的开始结点,无前趋;结点E的右线索为空,表示E是中序序列的终端结点,无后继。
线索二叉树中,一个结点是叶结点的充要条件为:左、右标志均是1。
2、图①邻接表一、邻接表二、无向图的邻接表3、4、图7-5三、有向图的邻接表和逆邻接表(一)在有向图的邻接表中,第i个单链表链接的边都是顶点i发出的边。
(二)为了求第i个顶点的入度,需要遍历整个邻接表。
因此可以建立逆邻接表。
(三)在有向图的逆邻接表中,第i个单链表链接的边都是进入顶点i的边。
四、邻接表小结◆设图中有n个顶点,e条边,则用邻接表表示无向图时,需要n个顶点结点,2e个表结点;用邻接表表示有向图时,若不考虑逆邻接表,只需n个顶点结点,e个边结点。
◆在无向图的邻接表中,顶点v i的度恰为第i个链表中的结点数。
◆在有向图中,第i个链表中的结点个数只是顶点v i的出度。
在逆邻接表中的第i个链表中的结点个数为v i的入度。
②邻接矩阵(有向、无向)1.图的邻接矩阵表示法在图的邻接矩阵表示法中:①用邻接矩阵表示顶点间的相邻关系②用一个顺序表来存储顶点信息2.图的邻接矩阵(Adacency Matrix)设G=(V,E)是具有n个顶点的图,则G的邻接矩阵是具有如下性质的n阶方阵:【例】下图中无向图G 5 和有向图G 6 的邻接矩阵分别为A l 和A 2 。
③广度优先遍历广度优先遍历是连通图的一种遍历策略。
其基本思想如下:1、从图中某个顶点V0出发,并访问此顶点;2、从V0出发,访问V0的各个未曾访问的邻接点W1,W2,…,Wk;然后,依次从W1,W2,…,Wk出发访问各自未被访问的邻接点;3、重复步骤2,直到全部顶点都被访问为止。
例如下图中:1.从0开始,首先找到0的关联顶点3,42.由3出发,找到1,2;由4出发,找到1,但是1已经遍历过,所以忽略。
3.由1出发,没有关联顶点;由2出发,没有关联顶点。
所以最后顺序是0,3,4,1,2④深度优先遍历深度优先遍历是连通图的一种遍历策略。
其基本思想如下:设x是当前被访问顶点,在对x做过访问标记后,选择一条从x出发的未检测过的边(x,y)。
若发现顶点y已访问过,则重新选择另一条从x出发的未检测过的边,否则沿边(x,y)到达未曾访问过的y,对y访问并将其标记为已访问过;然后从y开始搜索,直到搜索完从y出发的所有路径,即访问完所有从y出发可达的顶点之后,才回溯到顶点x,并且再选择一条从x出发的未检测过的边。
上述过程直至从x出发的所有边都已检测过为止。
例如下图中:1.从0开始,首先找到0的关联顶点32.由3出发,找到1;由1出发,没有关联的顶点。
3.回到3,从3出发,找到2;由2出发,没有关联的顶点。
4.回到4,出4出发,找到1,因为1已经被访问过了,所以不访问。
所以最后顺序是0,3,1,2,4⑤Prim算法基本思想:假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。
算法从U={u0}(u0∈V)、TE={}开始。
重复执行下列操作:在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。
此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。
Prim算法的核心:始终保持TE中的边集构成一棵生成树。
注意:prim算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边得数目无关,而kruskal算法的时间复杂度为O(eloge)跟边的数目有关,适合稀疏图。
⑥Krusal算法图中先将每个顶点看作独立的子图,然后查找最小权值边,这条边是有限制条件的,边得两个顶点必须不在同一个图中,如上图,第一个图中找到最小权值边为(v1,v3),且满足限制条件,继续查找到边(v4,v6),(v2,v5),(v3,v6),当查找到最后一条边时,仅仅只有(v2,v3)满足限制条件,其他的如(v3,v4),(v1,v4)都在一个子图里面,不满足条件,至此已经找到最小生成树的所有边。
⑦拓扑排序由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
对上图进行拓扑排序的结果:2->8->0->3->7->1->5->6->9->4->11->10->125、检索①二叉排序建立typedef struct node{int data;struct node * lchild;struct node * rchild;}node;void Init(node *t){t = NULL;}void InOrder(node * t) //中序遍历输出{if(t != NULL){InOrder(t->lchild);printf("%d ", t->data);InOrder(t->rchild);}}②二叉排序删除btree * DelNode(btree *p){if (p->lchild){btree *r = p->lchild; //r指向其左子树;btree *prer = p->lchild; //prer指向其左子树;while(r->rchild != NULL)//搜索左子树的最右边的叶子结点r{prer = r;r = r->rchild;}p->data = r->data;if(prer != r)//若r不是p的左孩子,把r的左孩子作为r的父亲的右孩子prer->rchild = r->lchild;elsep->lchild = r->lchild; //否则结点p的左子树指向r的左子树free(r);return p;}else{btree *q = p->rchild; //q指向其右子树;free(p);return q;}}③Huffman树、编码与解码例. 给定有18个字符组成的文本:A A D A T A R A E F R T A A F T E R 求各字符的哈夫曼码。
(1)统计:(2)构造Huffman树:(3) 在左分枝标0,右分枝标1:(4) 确定Huffman 编码:(5)译码:例. 给定代码序列:0 0 1 0 0 0 1 1 1 0 1 0 1 0 1 0 1 1 1 10文本为:A A F A R A D E T④散列存储6、内排序①希尔排序void ShellPass(SeqList R,int d){//希尔排序中的一趟排序,d为当前增量for(i=d+1;i<=n;i++) //将R[d+1..n]分别插入各组当前的有序区if(R[i].key<R[i-d].key){R[0]=R[i];j=i-d;//R[0]只是暂存单元,不是哨兵do {//查找R[i]的插入位置R[j+d];=R[j];//后移记录j=j-d;//查找前一记录}while(j>0&&R[0].key<R[j].key);R[j+d]=R[0];//插入R[i]到正确的位置上} //endif} //ShellPassvoid ShellSort(SeqList R){int increment=n;//增量初值,不妨设n>0do {increment=increment/3+1;//求下一增量ShellPass(R,increment);//一趟增量为increment的Shell插入排序}while(increment>1)} //ShellSort②直接插入排序void lnsertSort(SeqList R){ //对顺序表R中的记录R[1..n]按递增序进行插入排序int i,j;for(i=2;i<=n;i++) //依次插入R[2],…,R[n]if(R[i].key<R[i-1].key){//若R[i].key大于等于有序区中所有的keys,则R[i]//应在原有位置上R[0]=R[i];j=i-1; //R[0]是哨兵,且是R[i]的副本do{ //从右向左在有序区R[1..i-1]中查找R[i]的插入位置R[j+1]=R[j];//将关键字大于R[i].key的记录后移j--;}while(R[0].key<R[j].key);//当R[i].key≥R[j].key时终止R[j+1]=R[0];//R[i]插入到正确的位置上}//endif}//InsertSort③选择排序void SelectSort(SeqList R){int i,j,k;for(i=1;i<n;i++){//做第i趟排序(1≤i≤n-1)k=i;for(j=i+1;j<=n;j++) //在当前无序区R[i..n]中选key最小的记录R[k]if(R[j].key<R[k].key)k=j; //k记下目前找到的最小关键字所在的位置if(k!=i){ //交换R[i]和R[k]R[0]=R[i];R[i]=R[k];R[k]=R[0];//R[0]作暂存单元} //endif} //endfor} //SeleetSort④交换排序void BubbleSort(SeqList R){ //R(l..n)是待排序的文件,采用自下向上扫描,对R做冒泡排序int i,j;Boolean exchange;//交换标志for(i=1;i<n;i++){ //最多做n-1趟排序exchange=FALSE;//本趟排序开始前,交换标志应为假for(j=n-1;j>=i;j--) //对当前无序区R[i..n]自下向上扫描if(R[j+1].key<R[j].key){//交换记录R[0]=R[j+1];//R[0]不是哨兵,仅做暂存单元R[j+1]=R[j];R[j]=R[0];exchange=TRUE;//发生了交换,故将交换标志置为真}if(!exchange) //本趟排序未发生交换,提前终止算法return;} //endfor(外循环)} //BubbleSortvoid QuickSort(SeqList R,int low,int high){ //对R[low..high]快速排序int pivotpos;//划分后的基准记录的位置if(low<high){//仅当区间长度大于1时才须排序pivotpos=Partition(R,low,high);//对R[low..high]做划分QuickSort(R,low,pivotpos-1);//对左区间递归排序QuickSort(R,pivotpos+1,high);//对右区间递归排序}} //QuickSort⑤归并排序void Merge(SeqList R,int low,int m,int high){//将两个有序的子文件R[low..m)和R[m+1..high]归并成一个有序的//子文件R[low..high]int i=low,j=m+1,p=0;//置初始值RecType *R1;//R1是局部向量,若p定义为此类型指针速度更快R1=(ReeType *)malloc((high-low+1)*sizeof(RecType));if(! R1) //申请空间失败Error("Insufficient memory available!");while(i<=m&&j<=high) //两子文件非空时取其小者输出到R1[p]上R1[p++]=(R[i].key<=R[j].key)?R[i++]:R[j++];while(i<=m) //若第1个子文件非空,则复制剩余记录到R1中R1[p++]=R[i++];while(j<=high) //若第2个子文件非空,则复制剩余记录到R1中R1[p++]=R[j++];for(p=0,i=low;i<=high;p++,i++)R[i]=R1[p];//归并完成后将结果复制回R[low..high]} //Merge三、选择题1、二叉树的第i层至多有2^(i −1)个结点;深度为k的二叉树至多有2^k −1个结点(根结点的深度为1)2、二维数组A[10...20,5....10]采用行序为主存方式存储,每个元素占4个存储单元,且A[10,5]的存储地址为1000,则A[18,9]的存储地址?a.不管按行还是按列,都是顺序存储。