数据结构复习资料亲自整理.docx
《数据结构复习资料亲自整理.docx》由会员分享,可在线阅读,更多相关《数据结构复习资料亲自整理.docx(35页珍藏版)》请在冰豆网上搜索。
数据结构复习资料亲自整理
一、简答题
1、链表:
链表就是一串存储数据的链式结构。
链式的优点在于,每个数据之间都是相关联的。
2、线性结构:
线性结构是一个有序数据元素的集合。
常用的线性结构有:
线性表,栈,队列,双队列,数组,串。
3、树与二叉树
二叉树是每个结点最多有两个子树的有序树;
树是由n(n>=1)个有限节点组成一个具有层次关系的集合。
树和二叉树的2个主要差别:
1.树中结点的最大度数没有限制,而二叉树结点的最大度数为2;
2.树的结点无左、右之分,而二叉树的结点有左、右之分。
4、堆
堆通常是一个可以被看做一棵树的数组对象。
堆总是满足下列性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
5、二叉排序树
二叉排序数的(递归)定义:
1、若左子树非空,则左子树所有节点的值均小于它的根节点;2、若右子树非空,则右子树所有节点的值均大于于它的根节点;3、左右子树也分别为二叉排序树。
二、应用题
1、树与二叉树
1前中后序遍历序列
一、已知前序、中序遍历,求后序遍历
例:
前序遍历:
GDAFEMHZ中序遍历:
ADEFGHMZ
画树求法:
第一步,根据前序遍历的特点,我们知道根结点为G
第二步,观察中序遍历ADEFGHMZ。
其中root节点G左侧的ADEF必然是root的左子树,G右侧的HMZ必然是root的右子树。
第三步,观察左子树ADEF,左子树的中的根节点必然是大树的root的leftchild。
在前序遍历中,大树的root的leftchild位于root之后,所以左子树的根节点为D。
第四步,同样的道理,root的右子树节点HMZ中的根节点也可以通过前序遍历求得。
在前序遍历中,一定是先把root和root的所有左子树节点遍历完之后才会遍历右子树,并且遍历的左子树的第一个节点就是左子树的根节点。
同理,遍历的右子树的第一个节点就是右子树的根节点。
2树与二叉树的转换
树转换为二叉树:
二叉树转换为树:
3二叉树线索化
注意:
图中的实线表示指针,虚线表示线索。
结点C的左线索为空,表示C是中序序列的开始结点,无前趋;
结点E的右线索为空,表示E是中序序列的终端结点,无后继。
线索二叉树中,一个结点是叶结点的充要条件为:
左、右标志均是1。
2、图
1邻接表
一、邻接表
邻接表是图的一种链式存储结构。
邻接表中,对图中每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点Vi的边(对有向图是以顶点Vi为尾的弧)。
邻接表中的表结点和头结点结构:
表结点
adjvex
nextarc
info
头结点
data
firstarc
二、无向图的邻接表
3、
4、图7-5
三、有向图的邻接表和逆邻接表
(一)在有向图的邻接表中,第i个单链表链接的边都是顶点i发出的边。
(二)为了求第i个顶点的入度,需要遍历整个邻接表。
因此可以建立逆邻接表。
(三)在有向图的逆邻接表中,第i个单链表链接的边都是进入顶点i的边。
四、邻接表小结
◆ 设图中有n个顶点,e条边,则用邻接表表示无向图时,需要n个顶点结点,2e个表结点;用邻接表表示有向图时,若不考虑逆邻接表,只需n个顶点结点,e个边结点。
◆ 在无向图的邻接表中,顶点vi的度恰为第i个链表中的结点数。
◆ 在有向图中,第i个链表中的结点个数只是顶点vi的出度。
在逆邻接表中的第i个链表中的结点个数为vi的入度。
2邻接矩阵(有向、无向)
1.图的邻接矩阵表示法
在图的邻接矩阵表示法中:
①用邻接矩阵表示顶点间的相邻关系
②用一个顺序表来存储顶点信息
2.图的邻接矩阵(AdacencyMatrix)
设G=(V,E)是具有n个顶点的图,则G的邻接矩阵是具有如下性质的n阶方阵:
【例】下图中无向图G5和有向图G6的邻接矩阵分别为Al和A2。
3广度优先遍历
广度优先遍历是连通图的一种遍历策略。
其基本思想如下:
1、从图中某个顶点V0出发,并访问此顶点;
2、从V0出发,访问V0的各个未曾访问的邻接点W1,W2,…,Wk;然后,依次从W1,W2,…,Wk出发访问各自未被访问的邻接点;
3、重复步骤2,直到全部顶点都被访问为止。
例如下图中:
1.从0开始,首先找到0的关联顶点3,4
2.由3出发,找到1,2;由4出发,找到1,但是1已经遍历过,所以忽略。
3.由1出发,没有关联顶点;由2出发,没有关联顶点。
所以最后顺序是0,3,4,1,2
4深度优先遍历
深度优先遍历是连通图的一种遍历策略。
其基本思想如下:
设x是当前被访问顶点,在对x做过访问标记后,选择一条从x出发的未检测过的边(x,y)。
若发现顶点y已访问过,则重新选择另一条从x出发的未检测过的边,否则沿边(x,y)到达未曾访问过的y,对y访问并将其标记为已访问过;然后从y开始搜索,直到搜索完从y出发的所有路径,即访问完所有从y出发可达的顶点之后,才回溯到顶点x,并且再选择一条从x出发的未检测过的边。
上述过程直至从x出发的所有边都已检测过为止。
例如下图中:
1.从0开始,首先找到0的关联顶点3
2.由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->12
5、检索
1二叉排序建立
typedefstructnode
{
intdata;
structnode*lchild;
structnode*rchild;
}node;
voidInit(node*t)
{
t=NULL;
}
voidInOrder(node*t)//中序遍历输出
{
if(t!
=NULL)
{
InOrder(t->lchild);
printf("%d",t->data);
InOrder(t->rchild);
}
}
2二叉排序删除
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;
else
p->lchild=r->lchild;//否则结点p的左子树指向r的左子树
free(r);
returnp;
}
else
{
btree*q=p->rchild;//q指向其右子树;
free(p);
returnq;
}
}
3Huffman树、编码与解码
例.给定有18个字符组成的文本:
AADATARAEFRTAAFTER求各字符的哈夫曼码。
(1)统计:
(2)构造Huffman树:
(3)在左分枝标0,右分枝标1:
(4)确定Huffman编码:
(5)译码:
例.给定代码序列:
001000111010101011110
文本为:
AAFARADET
4散列存储
6、内排序
1希尔排序
voidShellPass(SeqListR,intd)
{//希尔排序中的一趟排序,d为当前增量
for(i=d+1;i<=n;i++)//将R[d+1..n]分别插入各组当前的有序区
if(R[i].keyR[0]=R[i];j=i-d;//R[0]只是暂存单元,不是哨兵
do{//查找R[i]的插入位置
R[j+d];=R[j];//后移记录
j=j-d;//查找前一记录
}while(j>0&&R[0].keyR[j+d]=R[0];//插入R[i]到正确的位置上
}//endif
}//ShellPass
voidShellSort(SeqListR)
{
intincrement=n;//增量初值,不妨设n>0
do{
increment=increment/3+1;//求下一增量
ShellPass(R,increment);//一趟增量为increment的Shell插入排序
}while(increment>1)
}//ShellSort
2直接插入排序
voidlnsertSort(SeqListR)
{//对顺序表R中的记录R[1..n]按递增序进行插入排序
inti,j;
for(i=2;i<=n;i++)//依次插入R[2],…,R[n]
if(R[i].key//应在原有位置上
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].keyR[j+1]=R[0];//R[i]插入到正确的位置上
}//endif
}//InsertSort
3选择排序
voidSelectSort(SeqListR)
{
inti,j,k;
for(i=1;ik=i;
for(j=i+1;j<=n;j++)//在当前无序区R[i..n]中选key最小的记录R[k]
if(R[j].keyk=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
4交换排序
voidBubbleSort(SeqListR)
{//R(l..n)是待排序的文件,采用自下向上扫描,对R做冒泡排序
inti,j;
Booleanexchange;//交换标志
for(i=1;iexchange=FALSE;//本趟排序开始前,交换标志应为假
for(j=n-1;j>=i;j--)//对当前无序区R[i..n]自下向上扫描
if(R[j+1].keyR[0]=R[j+1];//R[0]不是哨兵,仅做暂存单元
R[j+1]=R[j];
R[j]=R[0];
exchange=TRUE;//发生了交换,故将交换标志置为真
}
if(!
exchange)//本趟排序未发生交换,提前终止算法
return;
}//endfor(外循环)
}//BubbleSort
voidQuickSort(SeqListR,intlow,inthigh)
{//对R[low..high]快速排序
intpivotpos;//划分后的基准记录的位置
if(lowpivotpos=Partition(R,low,high);//对R[low..high]做划分
QuickSort(R,low,pivotpos-1);//对左区间递归排序
QuickSort(R,pivotpos+1,high);//对右区间递归排序
}
}//QuickSort
5归并排序
voidMerge(SeqListR,intlow,intm,inthigh)
{//将两个有序的子文件R[low..m)和R[m+1..high]归并成一个有序的
//子文件R[low..high]
inti=low,j=m+1,p=0;//置初始值
RecType*R1;//R1是局部向量,若p定义为此类型指针速度更快
R1=(ReeType*)malloc((high-low+1)*sizeof(RecType));
if(!
R1)//申请空间失败
Error("Insufficientmemoryavailable!
");
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.不管按行还是按列,都是顺序存储。
按行存储,每行10-5+1共6个元素。
A[10,9]距离A[10,5]之间相差4个元素;A[18,9]与A[10,9]相差8行,共8×6=48个元素;所以A[18,9]与A[10,5]相差4+48=52个元素,共52×4=208个存储单元;A[18,9]的地址应该是1208。
b.更一般的算法:
基地址+(行标之差×每行元素个数+列标之差)×元素所占存储单元
3、n个顶点的连通图最多多少边、最少多少条边?
答:
最多n(n-1)/2,最少n-1
4、设一个无向图有5顶点,度数分别是4,3,3,2,2,求该图边数?
答:
每条边与两个顶点相连接,所以所有顶点上的度数之和就是图中边的两倍,本题中共有4+3+3+2+2=14个边的端点,因而共有14/2=7条边。
6、建立最小堆
建堆过程
6、huffman编码
7、直接排序法过程
用直接排序法将无序列{49,38,65,97,76,13,27}按照从大到小的顺序排为有序列时,每一趟将把当前最大的放到第一位,然后例举出前五趟
就是每一趟将把当前最大的放到第一位.即第一趟{97,49,38,65,76,13,27}
第二趟{97,76,49,38,65,13,27},第三趟{97,76,65,49,38,13,27},
第四趟{97,76,65,49,38,13,27},第五趟{97,76,65,49,38,13,27}
8、
四、编程题
1、二叉树:
二叉树的建立:
#include
#defineElemTypechar
//节点声明,数据域、左孩子指针、右孩子指针
typedefstructBiTNode{
chardata;
structBiTNode*lchild,*rchild;
}BiTNode,*BiTree;
//先序建立二叉树
BiTreeCreateBiTree(){
charch;
BiTreeT;
scanf("%c",&ch);
if(ch=='#')T=NULL;
else{
T=(BiTree)malloc(sizeof(BiTNode));
T->data=ch;
T->lchild=CreateBiTree();
T->rchild=CreateBiTree();
}
returnT;//返回根节点
}
//先序遍历二叉树
voidPreOrderTraverse(BiTreeT){
if(T){
printf("%c",T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
//中序遍历
voidInOrderTraverse(BiTreeT){
if(T){
PreOrderTraverse(T->lchild);
printf("%c",T->data);
PreOrderTraverse(T->rchild);
}
}
//后序遍历
voidPostOrderTraverse(BiTreeT){
if(T){
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
printf("%c",T->data);
}
}
voidmain(){
BiTreeT;
T=CreateBiTree();//建立
PreOrderTraverse(T);//输出
getch();
}
1在二叉树中插入结点x,找中序首点(顺着左支一直走)、尾点(顺右)
//首先执行查找算法,找出被插结点的父亲结点。
判断被插结点是其父亲结点的左、右儿子。
将被插结点作为叶子结点插入。
若二叉树为空。
则首先单独生成根结点。
注意:
新插入的结点总是叶子结点。
//
一
typedefstructnode
{
intdata;
structnode*lchild;
structnode*rchild;
}node;
node*Insert(node*t,intkey)
{
if(t==NULL)
{
node*p;
p=(node*)malloc(sizeof(node));
p->data=key;
p->lchild=NULL;
p->rchild=NULL;
t=p;
}
else
{
if(keydata)
t->lchild=Insert(t->lchild,key);
else
t->rchild=Insert(t->rchild,key);
}
returnt;//important!
}
二
typedefstructNode
{
intdata;
structNode*lc,*rc;
}node,*Link;
voidinsert(Link*L,intn)
{
if(*L==NULL)
{
(*L)=newnode;//若找到插入位置,则新申请节点
(*L)->lc=(*L)->rc=NULL;
(*L)->data=n;
}
else
{
if(n==(*L)->data)//若n与当前节点的值相等,则不需插入了
return;
elseif(n<(*L)->data)//若n比当前节点的值小,则往当前节点的左子树插
insert(&(*L)->lc,n);
else//若n比当前节点的值大,则往当前节点的右子树插
insert(&(*L)->rc,n);
}
}
中序遍历:
typedefstructBiTNode/*结点结构*/
{
intdata;
structBiTNode*lchild,*rchild;
}BiTNode,*BiTree;
voidInorder(BiTreeT)
{/*中序遍历二叉树*/
if(T)
{
①Inorder(T->lchild);/*遍历左子树*/
②printf("%d",T->data);/*访问结点*/
③Inorder(T->rchild);/*遍历右子树*/
}
}
当调用该子函数时:
加入传的就是根节点,他将不断的递归一直到最左的一个叶子节点,就是不断重复①
T位最左叶子节点是那么第一句再递归式就失败了,所有退回上一层输出②,紧接着就执行③开始递归
以此类推在递归第三句时候也是严格按着1,2,3这个顺序执行。
2求一度结点,叶子结点
intcount=0;
voidpreOrder(TTree:
tree){
if(tree!
=NULL){
count++;//先根访问,且计数
//这里显示tree的数据
if(tree->Left!
=NULL)
preOrder(tr