第一部分 各章内容总结
第1章 概 述
1.数据:凡能被计算机存储、加工的对象统称为数据。
2.数据元素:是数据的基本单位,在程序中作为一个整体而加以考虑和处理。根据需要,数据元素有时被称为元素、结点、顶点和记录。
3.数据项:数据元素一般是由数据项组成的,数据项是数据的不可分割的最小表示单位。
4.逻辑关系:是指数据元素之间的关联方式或称“邻接关系”
5.逻辑结构:数据元素之间逻辑关系的的整体称为逻辑结构
6.四种逻辑结构的特点:
①集合中任何两个结点之间都没有逻辑关系,组织结构松散。②线性结构中结点按逻辑关系一次排列成一条“锁链”。 ③树型结构具有分支、层次特性,其形态有点象自然界中的树。④图状结构最复杂,其中的各个结点按逻辑关系互相缠绕,任何两个结点都可以邻接。
7.运算:是指在逻辑结构上施加的操作,即对逻辑结构的加工。根据操作的效果,运算可分为加工型运算和引用型运算。查找、读取(排序)是引用型运算,插入、删除和更新(修改)是加工型运算。
8.数据的存储结构:数据的机内表示称为数据的存储结构。包括以下三个主要部分:①存储结点,每个存储结点存放一个数据元素②数据元素之间关联方式的表示,也就是逻辑结构的机内表示③附加设施。
9.四种基本存储方式
①顺序存储方式:每个存储结点只含一个数据元素,所有存储结点存放在一块连续的存储区里,用存储结点的位置关系表示数据元素之间的逻辑关系。②链式存储方式:每个存储结点不仅含有一个数据元素,还包含一个指针,每个指针指向一个与本结点有逻辑关系的结点,即用附加的指针表示逻辑关系。③索引存储方式:每个存储结点只含一个数据元素,所有存储结点连续存放,此外增设一个索引表以指示各存储结点的存储位置或区间端点。④散列存储方式:每个存储结点只含一个数据元素,各个结点均匀分布在存储区里,用散列函数指示各存储结点的存储位置或区间端点。
10.算法:对特定问题求解步骤的一种描述,是指令的有限序列。一个算法具有以下5个特性:①有穷性②确定性③可行性④有0个或多个输入⑤有一个或多个输出。
11.最坏情况时间复杂性或最坏情况时间复杂度:算法在所有输入下的计算量的最大值。
12.平均时间复杂性或平均时间复杂度:算法在所有输入下的计算量的加权平均值。
第2章 线性表
13.线性结构:是n(n≥0)个结点的有穷序列,表示如下:(a1 ,a2 ,… ,ai ,ai+1 ,… ,an)其中:a1 称为起始结点,an称为终端结点,i称为ai在线性表中的序号或位置。对任意一对相邻结点ai,ai+1(1≤i<n),ai称为ai+1的直接前驱,ai+1称为ai的直接后继。
14.线性结构的基本特征:若至少有一个结点,则除起始结点没有直接前驱外,其它结点有且仅有一个直接前驱;除终端结点没有直接后继外,其它结点有且仅有一个直接后继。
15.线性表的逻辑结构是线性结构。所含结点的个数称为线性表的长度。表长为0的线性表称为空表。
16.顺序表:是线性表的顺序存储结构,即按顺序存储方式构造的线性表的存储结构。其特点是逻辑结构中相邻的结点在存储结构中仍相邻。顺序表的类型定义如下:
#define maxsize 顺序表的容量
typedef struct
{datatype data[maxsize];
int last;
} sqlist;
sqlist L;
17.基本运算在顺序表上的实现:
①插入算法
void insert_sqlist(sqlist L,datatype x,int i)
/*将x插入到顺序表L的第i-1个位置*/
{
if(L.last==maxsize) error("表满");
if((i<1)||(i>L.last+1)) error("非法位置");
for(j=L.last;j>=i;j--)
L.data[j]=L.data[j-1];
L.data[i-1]=x;
L.last=L.last+1;
}
插入算法分析:结点移动为基本操作;
当i=n+1时,移动次数为0 ;当i=1时,移动次数为n,达到最大;
平均移动次数为n/2 ;平均时间复杂度为 O(n)
②删除算法
void delete_sqlist(sqlist L,int i)
/*删除顺序表L中第i个位置上的结点*/
{
if((i<1)||(i>L.last)) error("非法位置");
for(j=i+1;j<=L.last;j++)
L.data[j-2]=L.data[j-1];
L.last=L.last-1;
}
删除算法分析:结点移动为基本操作;
当i=n时,移动次数为0 ;当i=1时,移动次数为n-1,达到最大;
平均移动次数为(n-1)/2 ;平均时间复杂度为 O(n)
③定位算法
int locate_sqlist(sqlist L,datatype X)
/*在顺序表中从前往后查找第一个值等于X的结点,
若找到返回该结点的序号,否则返回0*/
{
i=1;
while((i<=L.last)&&(L.data[i-1]!=X)) i++;
if(i<=L.last) return(i)
else return(0)
}
18.单链表,线性链表:是线性表的链接存储结构,基本思想是用指针表示结点间的逻辑关系。单链表的一个存储结点包含两个部分:数据域(data)和指针域(next),结点类型定义如下:
typedef struct node *pointer;
struct node
{datatype data;
pointer next;
};
typedef pointer lklist;
带头结点的单链表如下:
头结点 首结点 尾结点
head
19.基本运算在单链表上的实现:
①求表长
int length_lklist(lklist head)
/*求单链表head的长度,p是pointer类型的变量*/
{p=head;
j=0;
while(p->next)!=NULL)
{p=p->next; j++; }
return(j);
}
②按序号查找
pointer find_lklist(lklist head ,int i)
/*在单链表head查找第i个结点,若找到则返回其指针,否则返回NULL*/
{p=head;j=0;
while((p->next!=NULL)&&(j
{p=p->next; j++; }
if(i==j) return(p);
else return(NULL);
}
③定位(查找)
int locate_lklist(lklist head,datatype x)
/*求单链表head中第一个值等于X的结点的序号,不存在结果为0*/
{
p=head;j=0;
while(((p->next!=NULL)&&(p->data!=X))
{ p=p->next; j++; }
if(p->data==X) return(j);
else return(0);
④删除
void delete_lklist(lklist head,int i)
/*删除单链表head的第i个结点*/
{
p=find_lklist(head,i-1);
if((p!=NULL)&&(p->next!=NULL)
{q=p->next;
p->next=q->next;
free(q);
}
else error("不存在第i个结点");
}
⑤插入
void insert_lklist(lklist head,datatype x,int i)
/*在单链表head的第i个位置上插入一个值为X的新结点*/
{
p=find_lklist(head,i-1);
if(p==NULL) error("不存在第i个位置");
else {
s=malloc(size);s->data=x;
s->next=p->next;
p->next=s;
}
}
⑥清除单链表上的重复结点
void purge_lklist(lklist head)
/*清除单链表head中的多余重复结点*/
{
p=head->next;
if(p==NULL) return;
while(p->next!=NULL)
{
q=p;
while(q->next!=NULL)
if(q->next->data==p->data)
{
r=q->next;
q->next= q->next->next;
free(r);
}
else q=q->next;
p=p->next;
}
}
⑦设计一个算法:int ascending(lklist head),判断单链表(带头结点)中数据域的数据是否为升序,若是升序,则返回1,否则返回0。
算法如下:
int ascending(lklist head)
{p=head->next;
j=1;
while(p!=NULL && p->next!=NULL && j==1)
{
if(p->data > p->next->data) j=0;
p=p->next;
}
return(j);
}
20.双链表的删除和插入:
设p指向待删除结点,使用以下2个语句可删除p所指结点
p->prior->next=p->next;
p->next->prior=p->prior;
设要在p所指结点的后面插入一个新结点 *q,则需修改4个指针
q->prior=p;
q->next=p->next;
p->next->prior=q;
p->next=q;
21.字符串:以字符为数据元素,以线性结构为逻辑结构的数据称为字符串。
22.串的存储:串可以顺序存储(有紧缩和非紧缩两种格式),也可以链接存储(一个结点可以存储一个字符,也可以存储多个字符)
第3章 栈、队列和数组
23.栈的基本概念
栈是一种“特殊的”线性表,这种线性表上的插入和删除运算限定在表的一端进行。允许插入和删除的这一端称为栈顶,另一端称为栈底。栈修改的原则是“后进先出”,所以栈又称为后进先出表(LIFO表),在栈顶进行插入称为进栈(或入栈),在栈顶进行删除称为退栈(或出栈)。
24.栈的基本运算:至少包括以下5种:
①初始化InitStack(S)②进栈Push(S,X)③退栈Pop(s)④读栈顶Top(S,X)⑤判栈空Empty(S)
25.栈的顺序实现:顺序栈类型定义如下:
#define sqstaxk_maxsize 栈的容量
typedef struct sqstack
{datatype data[sqstack_maxsize];
int top;
} SqStackTp;
①当栈顶下标top==0时,栈空;②栈空时作退栈运算,则产生下溢;③当栈顶下标top== sqstack_maxsize时,栈满;④栈满时作进栈运算,则产生上溢。
26.栈的链接实现:栈的链接实现称为链栈,组织形式同单链表,单链表的第一个结点就是链栈栈顶结点。设ls时栈顶指针(单链表的头指针),则栈空的条件是:ls==NULL;另外不会栈满。
27.队列的基本概念
队列也是一种运算受限的线性表,在这种线性表上,插入限定在表的一端进行,删除限定在表的另一端进行。允许插入的一端称为队尾,允许删除的一端称为队头。队列的修改原则是“先进先出”,所以队列又称为先进先出表(FIFO表)在队尾进行插入称为入队,在队头进行删除称为出队。
28.队列的基本运算:至少包括以下5种:
①初始化InitQueue(Q)②入队列EnQueue(Q,X)③出队OutQueue(Q)④读队头GetQueue(Q,X)⑤判队列空EmptyQueue(Q)
29.队列的顺序实现:顺序队列类型定义如下:
#define maxsize 队列的容量
typedef struct SqQueue
{datatype data[maxsize];
int front,rear;
} SqQueueTp;
SqQueueTp sq;
将队列设想成一个循环表,即设想数组的首尾相连:sq.data[0]接在sq.data[maxsize-1]之后,这种存储结构称为循环队列。
①循环队列的入队操作为
sq.rear=(sq.rear+1)%maxsize;
sq.data[sq.rear]=x;
②循环队列的出队操作为
sq.front=(sq.front+1)%maxsize;
③循环队列的队满条件是
(sq.rear+1)%maxsize==sq.front
④循环队列的队空条件是
sq.rear==sq.front
30.队列的链接实现:队列的链接实现称为链队,实际上是一个同时带头指针和尾指针的单链表,头指针指向队头结点,尾指针指向队尾结点。设lq是一链队,则队空的条件:lq.front==lq.rear;另外不会队满。
31.数组:二维数组DataType A[m][n]可看成是由m个行向量或n个列向量组成的线性表。这种线性表的每一个数据元素本身也是一个线性表。数组通常只有两种基本运算:读和写。
32.数组的存储结构:通常采用顺序存储结构。对二维(或多维)数组有两种存储方式:一种是以列序为主序的存储方式,另一种以行序为主序的存储方式。C语言中的二维数组是以行序为主序存储的,且下标从0开始,每个元素使用k个单元,所以二维数组DataType a[m][n]的任一元素a[i][j]的存储位置(地址)可有下式确定:loc[i,j]=loc[0,0]+(n*i+j)*k
第4章 树
33.树的基本概念
树是n(n>0)个结点的有穷集合,满足:
⑴有且仅有一个称为根的结点;
⑵其余结点分为m(m≥0)个互不相交的非空集合T1 ,T2 ,… ,Tm ,这些集合中的每一个都是一棵树,称为根的子树。
树上的有关名词和术语:结点的度、叶子或终端结点、非终端结点或分支点、树的度、双亲或父结点、孩子或子结点、兄弟、祖先、子孙、结点的层数、树的高度或深度。
34.二叉树的基本概念
二叉树是结点的有穷集合,它或者是空集,或者同时满足下述两个条件:
⑴有且仅有一个称为根的结点;
⑵其余结点分为两个互不相交的集合T1 、T2 , T1与T2都是二叉树树,并且T1与T2有顺序关系(T1在T2之前),它们分别称为根的左子树和右子树。
35.二叉树与树的区别:第一,二叉树可以是空集,这种二叉树称为空二叉树;第二,二叉树的任一结点都有两棵子树(可以是空子树)并且这两棵子树之间有顺序关系。
36.二叉树有5中基本形态;(空、只有根、只有非空左子树、只有非空右子树、左右子树非空)。
37.二叉树的性质
性质1:二叉树第i(i≥1)层上至多有2i-1个结点。
性质2:深度为k(k≥1)的二叉树上至多有2k-1个结点。
性质3:对任何一棵二叉树,若2度结点数为n2 ,则叶子结点数n0=n2+1 。
深度为k(k≥1)且有2k-1个结点二叉树称为满二叉树。如果在一棵深度为k(k≥1)的满二叉树上删去第k层最右边的连续j(0≤j<2k-1)个结点,就得到一棵深度为k的完全二叉树。
性质4:具有n个结点的完全二叉树的深度度为 [log2n]+1 。
性质5:如果将一棵有n个结点的完全二叉树按层编号,则对任一编号为i(1≤i≤n)的结点x有:
⑴若i=1,则结点x是根;若i>1,则x的双亲编号为[i/2];
⑵若2i>n,则结点x无左孩子(且无右孩子);否则,x的左孩子编号为2i。
⑶若2i+1>n,则结点x无右孩子;否则,x的右孩子编号为2i+1。
38.二叉树存储结构:通常有两类存储结构,顺序存储结构和链式存储构。最常用的是二叉链表和三叉链表。还有线索链表二叉链表的结点类型定义如下:
typedef struct btnode *bitreptr;
struct btnode
{
datatype data;
bitreptr lchild, rchild;
}
bitreptr root;
例如:画出右上图所示二叉树的二叉链表表示。
解:所给二叉树的二叉链表表示为:
39.二叉树的遍历:遍历一棵二叉树就是按某种次序系统地“访问”二叉树上的所有结点,使每个结点恰好被“访问”一次。遍历方法有:先根遍历、中根遍历、后根遍历。
例如:给出上述二叉树的先根遍历、中根遍历、后根遍历序列。
解:上述二叉树的先根序列:ABDGHECFIJ;中根序列:GDHBEACIJF;后根序列:GHDEBJIFCA
40.二叉树的三种遍历算法
①先根遍历
void preorder(bitreptr r)
/*先根遍历根指针为r的二叉树*/
{if(r!=NULL)
{ visit(r);
preorder(r->lchild);
preorder(r->rchild);
}
}
②中根遍历
void inorder(bitreptr r)
/*中根遍历根指针为r的二叉树*/
{if(r!=NULL)
{ inorder(r->lchild);
visit(r);
inorder(r->rchild);
}
}
③后根遍历
void postorder(bitreptr r)
/*后根遍历根指针为r的二叉树*/
{if(r!=NULL)
{ postorder(r->lchild);
postorder(r->rchild);
visit(r);
}
}
41.二叉树算法举例:
①以二叉链表作存储结构,求二叉树的叶子数
void countleft(bitreptr t,int *count)
/*假定 *count的初值为0*/
{if(t!=NULL)
{if((t->lchild==NULL)&&(t->rchild==NULL)
*count++;
countleaf(t->lchild,count);
countleaf(t->rchild,count);
}
}
②以二叉链表作存储结构,求二叉树的深度
int depth(bitreptr t)
/*求根为t 的二叉树的深度*/
{int dep1,dep2;
if(t==NULL) return(0);
else { dep1=depth(t->lchild);
dep2= depth(t->rchild);
if(dep1>dep2) return(dep1+1);
else return(dep2+1);
}
}
③以二叉链表作存储结构,交换二叉树中所有结点的左、右子树。
void swap(bitreptr r)
/*后根遍历二叉树,将所有结点的左、右指针交换*/
{if(r!=NULL)
{ swap(r->lchild);
swap(r->rchild);
p=r->lchild;r->lchild=r->rchild; r->rchild=p;
}
}
④以二叉链表作存储结构,复制一棵给定的二叉树。
bitreptr copybitree(bitreptr r)
{if(r!=NULL)
{p=(bitreptr)malloc(sizeof(btnode));
p->data=r->data;
p->lchild=copybitree(r->lchild);
p->rchild=copybitree(r->rchild);
return(p);
}
else return(NULL);
}
42.树的存储结构:树的三种常用存储结构:
①双亲表示法 ②孩子链表表示法 ③孩子兄弟链表表示法
(a) 树T (b) 树T的双亲链表表示 (c)树T的孩子兄弟链表表示
(d) 树T的孩子链表表示
43.树林与二叉树之间转换
44.哈夫曼树(Huffman树)
设T是一棵判定树,其终端结点为:n1 ,n2 ,…,nk 。每个终端结点ni对应的百分比为pi,通常将称为ni的权。在假定ni的祖先数为li 。这样按T分类的平均比较次数为:
给定一组值:p1 , …,pk ,如果构造一棵有k棵叶子且分别以这些值为权的判定树,使得其平均比较次数最少。满足该条件的判定树称为哈夫曼树。n个终端结点的哈夫曼树有2n-1个结点。
例如:给定权值:2,3,4,7,8,9,构造相应的哈夫曼树并求其加权路径长度WPL。
解:哈夫曼树如下:
该哈夫曼树的加权路径长度WPL=7×2+8×2+9×2+4×3+2×4+3×4=80
第5章 图
45.图的基本概念和术语
图G有两个集合V和E组成,记为G=(V,E),这里,V是顶点的集合,E是边的集合,而边是V中顶点的偶对。若顶点的偶对是有序的,则称此图是有向图,有序对用尖括号括起来,即
无向完全图:任何两点间都有边相关联的无向图,n个顶点的无向完全图有n(n-1)/2条边。
有向完全图:任何两点间都有弧相关联的有向图,n个顶点的有向完全图有n(n-1)条边。
权:图的边或弧所附带的数值叫权,每条边或弧都带权的图称为带权图或网。
度:无向图中与顶点V相关联的边的数目,记为D(V)。若G是有向图,则把以顶点V为终点的弧的数目称为V的入度,记为ID(V) ;把以顶点V为始点的弧的数目称为V的出度,记为OD(V); 有向图中顶点V的度定义为D(V)=ID(V)+OD(V)。
子图:设G=(V,E)是一个图,若E'是E的子集,V'是V的子集,使的E'中的边与V'中的顶点相关联,则图G'=(V',E')称为图G的子图。
路径:顶点v到顶点v'的路径是一个顶点序列(v=vi0,vi1,vi2,…,vin=v'),其中(vij-1,vij)∈E,1≤j≤n。
连通:若从顶点v到顶点v'有路径,则称v和v'是连通的。
连通图:对图中的任意两个顶点Vi,Vj∈V,且v和v'是连通的,则称图G是连通图。
连通分量:定义为无向图中的极大连通子图。
图的生成树:是含有该连通图全部顶点的一个极小连通子图。n个顶点连通图的生成树有n-1条边,如果G的一个子图G'的边数大于n-1,则G'中一定有环;相反,如果G'的边数小于n-1,则G'一定不连通。
46.图的存储结构:两种主要的存储结构是邻接矩阵和邻接表。
用邻接矩阵表示法来表示一个具有n个顶点的图时,除了用邻接矩阵的n×n个元素存储顶点间相邻关系外,往往还需要另设一个数组存储n个顶点的信息。类型定义如下:
#define vnum 20
typedef struct graph
{VertexType vexs[nmum];
int arcs[vnum][vnum];
int vexnum,arcnum;
} GraphTp;
在邻接矩阵中求结点的度:对于无向图,顶点vi的度是邻接矩阵中第i行(或第i列)的元素之和。对于有向图,顶点vi的出度O(vi)是邻接矩阵中第i行的元素之和, 顶点vi的入度I(vi)是邻接矩阵中第i列的元素之和。
例如:请画出有向图G1和无向图G2的邻接矩阵表示。
(a)有向图G1 (b)无向图G2 (c)有向图G1的邻接矩阵 (d)无向图G2的邻接矩阵
邻接表是图的一种链式存储结构。在邻接表中,对图中每个顶点建立一个单链表,n个顶点就要建立n条链表,链表中的每一个结点称为表结点;另外为每个单链表设一个表头结点。邻接表的类型定义如下:
#define vnum 20
typedef struct arcnode
{int adjvex;
struct arcnode *nextarc;
} ArcNodeTp;
typedef struct vexnode
{int vextex;
ArcNodeTp *fristarc;
} AdjList[vnum];
typedef struct graph
{AdjList adjlist;
int vexnum,arcnum;
} GraphTp;
若一个无向图有n个顶点,e条表,则它的邻接表需n个头结点和2e个表结点。
在邻接表中求结点的度:无向图中顶点vi的度恰为第i个单链表中的结点数。对有向图,第i个单链表中的结点个数只是顶点vi的出度;在所有单链表中,其邻接点域值为i的结点的个数是顶点i的入度。
(e) 无向图G2的邻接表 (f) 有向图G1的邻接表和逆邻接表
47.图的遍历:从图中某一顶点出发访遍图中其余顶点,且使每个顶点仅被访问一次。这一过程称为图的遍历。有两种基本方法:深度优先搜索和广度优先搜索。
深度优先搜索:从图中某个顶点vi出发,访问出发点vi,然后任选一个vi的未访问过的邻接点vj,以vj为新的出发点继续进行深度优先搜索,直到图中所有顶点都被访问过。算法如下:
Dfs(GraphTp g ,int v)
/*图g的存储结构为邻接表,进行深度优先搜索,数组visited的所有元素初始化为0*/
{ArcNodeTp *p;
printf("%d",v);
visited[v]=1;
p=g.adjlist[v].fristarc;
while(p!=NULL)
{if(!visited[p->adjvex]) Dfs(g,p->adivex);
p=p->nextarc;
}
}
广度优先搜索:从图中某个顶点vi出发,在访问了vi之后依次访问vi的所有邻接点,然后分别从这些邻接点出发广度优先搜索遍历图的其它顶点,直至所有顶点都被访问过。算法如下:
Bfs(GraphTp g ,int v)
/*图g的存储结构为邻接表,进行广度优先搜索,数组visited的所有元素初始化为0*/
{QuePtrTp Q;
ArcNodeTp *p;
InitQueue(&Q);
printf("%d",v);
visited[v]=1;
EnQueue(&Q,v);
while(!EmptyQueue(Q))
{OutQueue(&Q,&v);
p=g.adjlist[v].fristarc;
while(p!=NULL)
{if(!visited[p->adjvex])
{printf("%d",p->adjvex);
visited[p->adjvex]=1;
EnQueue(&Q, p->adjvex);
}
p=p->nextarc;
}
}
48.最小生成树:一个图的最小生成树是该图所有生成树中权最小的生成树。n个顶点无向图的最小生成树有n个顶点和n-1条边。如下图中右图是左图的最小生成树。
49.拓扑排序:设G=(V,E)是一个具有n个顶点的有向图,V中顶点的序列V1,V2,…,Vn称为一个拓扑序列,当且仅当该顶点序列满足下列条件:若在有向图G中,从顶点Vi到Vj有一条路径,则在序列中顶点Vi必排在顶点Vj之前。找一个有向图拓扑序列的过程称为拓扑排序。
拓扑排序的基本步骤如下:
①从图中选一个入度为0的顶点,输出该顶点;
②从图中删除该顶点及其相关联的弧;
③重复①、②,直到所有顶点均被输出。
第6章 查找表
50.查找表及有关概念:是一种以集合为逻辑结构,以查找为核心的运算,同时包括其它运算的数据结构。查找表可分为静态查找表(不插入和删除)和动态查找表(进行插入和删除)
关键字:用来标识数据元素的数据项称为关键字,简称键。该数据项的值称为键值。
查找:根据给定的某个值K,在查找表中寻找一个其键值等于K的数据元素。若找到一个这样的数据元素,则称查找成功,否则称查找不成功。
51.顺序表上的查找:以顺序表作为查找表的存储结构。顺序查找算法如下
int search_sqtable(sqtable R,keytype K)
/*在表R从后往前查找键值为K的元素,返回元素的序号*/
{R.item[0].key=K;
i=R.n;
while(R.item[i].key!=K) i--;
return(i);
}
顺序查找算法的平均查找长度为:ASL=(n+1)/2 SS等概率
52.有序表上的查找:以顺序表作为查找表的存储结构,且按关键字有序(升序)。判定树为非完全二叉树P220最下面
二分查找算法如下:
int binserach(sqtable R,keytype K)
{low=1;hig=R.n;
while(low<=hig)
{mid=(low+hig)/2;
if(R.item[mid].key==K) return(mid);
else if(R.item[mid].key>K) hig=mid-1;
else low=mid+1;
}
return(0);
}
例如:给定有序表D=(15,17,18,22,35,51,60,88,93),用二分查找法在D中查找88,试用图示法表示出查找过程。
解:在D查找88的过程如下所示:
mid=0;high=8;
15,17,18,22,35,51,60,88,93
↑ ↑ ↑
low mid high
mid=(low+high)/2=4; D[4]<88; low=mid+1=5;
15,17,18,22,35,51,60,88,93
↑ ↑ ↑
low mid high
mid=(low+high)/2=6; D[6]<88; low=mid+1=7;
15,17,18,22,35,51,60,88,93
↑ ↑
low high
mid=(low+high)/2=7; D[7]=88; 查找成功。
53.索引顺序表上的查找:索引顺序表是按索引存储方式构造的一种存储结构。一个索引顺序表由两部分组成:一个顺序表和一个索引表。索引顺序表的特点表现为两个方面:①顺序表中的数据元素“按块有序”;②索引表反映了这些“块”的有关特性。在索引顺序表上的查找分两个阶段:①确定待查元素所在的块;②在块内查找待查的元素。所以称为分块查找。分块查找的平均查找长度为:ASL=(n/s+s)/2+1 ;当s(块内元素的个数)取n的平方根时,ASL最小。
54.二叉排序树或者二叉查找树:一棵二叉排序树或者是一棵空树,或者是一棵同时满足下列条件的二叉树:
①若它的左子树不空,则左子树上所有结点的键值均小于它的根结点的键值;
②若它的右子树不空,则右子树上所有结点的键值均大于它的根结点的键值;
③它的左、右子树也分别为一棵二叉排序树。
二叉排序树的性质:中根遍历一棵二叉排序树所得的结点访问序列是键值的递增序列。
在二叉排序树上进行查找,若查找成功,则是从根结点出发走了一条从根结点到待查结点的路径;若查找不成功,则是从根结点出发走了一条从根结点到某个叶子的路径。
在随机情况下,含有n个结点的二叉排序树的平均查找长度为:1+4log2n
例如:给定表(37,21,6,51,32,86,23,60,33)
按元素在表中的次序将它们依次插入一棵初始为空的二排序叉树,画出插入完成之后的二叉排序树。
解:略
55.平衡二叉排序树:(简称AVL树)或者是一棵空树,或者是任一结点的左子树与右子树的高度至多差1的二叉排序树。对于二叉排序树上的任何结点,其平衡因子定义为该结点左子树的高度减去该结点右子树的高度。
56.散列表(哈希表):“散列”既是一种存储方式,又是一种查找方法。这种查找方法称为散列查找。按散列存储方式构造的存储结构称为散列表。散列函数是一种将键值映射为散列表中的存储位置的函数。由散列函数决定的数据元素在列表中的存储位置称为散列地址。散列的基本思想是通过由散列函数决定的键值与散列地址之间的对应关系来实现存储组织和查找运算。
设有散列函数H和键值ki、kj,若ki≠kj,且H(ki)=H(kj),则称ki、kj是(相对于H的)同义词。对应的两元素Xi和Xj要存入同一个散列表,这种情况称为冲突。
散列函数的构造方法:①数字分析法②除余法③平方取中法④基数转换法⑤随机数法
开散列表的组织方法:设选定的散列函数为H,H的值域为0~n-1。设置一个“地址向量”pointer HP[n],其中的每个指针HP[i]指向一个单链表,该单链表用于存储所有散列地址为i的数据元素,即所有散列地址为i的同义词。每一个这样的单链表称为一个同义词子表。由地址向量以及向量中每个指针所指向的同义词子表构成的存储结构称为开散列表。
闭散列表:是一个一维数组,将所有的数据元素存入闭散列表的不同存储结点中。解决冲突的方法是在需要时为每个键值K生成一个散列地址序列d0、d1、d2、…、di、…、dm-1。其中d0=H(K)是K的散列地址,所有di (0
①线性探测法:后继散列地址序列为 d+1,d+2,…,m-1,0,1,…,d-1
堆积:非同义词之间对同一个散列地址的争夺现象成为堆积。以下两种方法可以减少堆积。
②二次探测法:后继散列地址序列为 (d0+12)%m,(d0-12)%m,(d0+22)%m,(d0-22)%m,…
③多重散列法:设立多个散列函数Hi。
④公共溢出区法:此时,散列表由基本表和溢出表两个表组成。插入首先在基本表上进行,若发生冲突,则将同义词存入溢出表。
散列表的装填因子:α=表中填入的数据元素数/表的容量。
例如:设散列表的容量为14,散列函数是H(key)=key % 11 ,表中已存储有4个结点如下:
0 1 2 3 4 5 6 7 8 9 10 11 12 13
15 |
38 |
61 |
84 |
用线性探测法处理冲突,则关键字为48的结点的地址是 8 。
用二次探测法处理冲突,则关键字为48的结点的地址是 3 。
第7章 文 件
60.文件:文件时由特性相同的记录所构成的集合,其中一个记录就是一个数据元素,这就是数据的逻辑结构。记录是文件的基本存取单位。
61.磁盘组的读写过程:①选定柱面②选定磁道③找物理记录④读写信息。前三步是定位操作。
62.文件在外存储器上的的组织结构:主要有三种:顺序文件、散列文件、索引文件。
在顺序文件中:所有逻辑记录在存储介质中的实际顺序与它们进入存储器的顺序一致。
索引文件:由索引表和主文件两部分组成。
散列文件:是用散列技术组织成的文件,其组织方法类似于散列表,存储介质是外存储器。
第8章 排 序
63.排序:假设含n个记录的序列为{R1,R2, …,Rn},其相应的键值序列为{k1,k2, …,kn}。将这些记录重新排列为{R1',R2', …,Rn' },使得相应的键值满足条件k1'≤k2'≤ …≤kn'。这样一种运算称为排序。假定待排序的序列中存在多个记录具有相同的键值,若经过排序,这些记录的相对次序仍然不变,则称这种排序方法是稳定的;否则称为不稳定的。
排序可分为:内部排序和外部排序,内部排序方法主要有:插入排序、交换排序、选择排序、归并排序(、基数排序)。要求排成递增序列,且使用如下的存储结构:
typedef struct
{int key;
anytype otheritem;
} records;
typedef records list[n+1]; /*n为待排序记录的总数*/
64.直接插入排序:基本思想是,依次将每个记录插入到一个有序的子序列中去。算法如下:
void straightsort(int n,list r)
{for(i=2;i<=n;i++)
{r[0]=r[i];
j=i-1;
while(r[0].key<[r[j].key)
{r[j+1]=r[j];
j--; }
r[j+1]=r[0]; }
}
直接插入排序过程如下:
初始键值序列:[46] 58 15 45 90 18 10 62
i=2 [46 58] 15 45 90 18 10 62
i=3 [15 46 58] 45 90 18 10 62
i=4 [15 45 46 58] 90 18 10 62
i=5 [15 45 46 58 90] 18 10 62
i=6 [15 18 45 46 58 90] 10 62
i=7 [10 15 18 45 46 58 90] 62
i=8 [10 15 18 45 46 58 62 90]
当序列中的记录“基本有序”或n值较小时,直接插入排序是最佳的排序方法。
直接插入排序方法是稳定的,时间复杂度为O(n2)。空间复杂度为O(1)。折半二路表插入
65.冒泡排序:其具体做法是:先将第一个记录与第二个记录的键值比较,若r[1].key>r[2].key,则将两个记录交换。然后比较第二个记录和第三个记录的键值,依次类推,直到第n-1条记录和第n条记录比较交换,这称一趟起泡,此时,键值最大的记录被换到最后。然后对前n-1条记录进行同样的操作,则具有次大键值的记录被交换至第n-1个位置上。重复以上过程,直到没有记录需要交换为止,则完成排序。冒泡排序算法如下:
void bubblesort(int n,list r)
{for(i=1;i<=n-1;i++)
{flag=1;
for(j=1;j<=n-i;j++)
if(r[j+1].key {flag=0; p=r[j];r[j]=r[j+1];r[j+1]=p; } if(flag) return; }} 冒泡排序方法是稳定的,时间复杂度为O(n2)。空间复杂度为O(1)。 66.快速排序:基本思想是:在待排序的n个记录中任取一个记录(比如就取第一个记录),以该记录的键值为标准,将所有记录分为两组,使得第一组中各记录的键值均小于或等于该键值,第二组中各记录的键值均大于该键值,该记录就排在这两组的中间(也是该记录的最终位置),此称为一趟快速排序(一次划分),对所分成的两组分别重复上述方法。一趟快速排序算法如下: int quickpass(list r,int h,int p) /*对r[h],r[h+1],...r[p]子序列进行一趟快速排序*/ {i=h;j=p;x=r[i]; while(i {while((r[j].key>=x.key)&&(i if(i {r[i]=r[j]; i++; while((r[i].key<=x.key)&&(i if(i { r[j]=r[i]; j--; }}} r[i]=x; return i; } 完成快速排序可写成如下算法: void quicksort(list r,int s,int t) /*对r[s],r[s+1],...r[t]进行快速排序*/ { if(s {i=quickpass(r,s,t); quicksort(r,s,i-1); quicksort(r,i+1,t); } } 当对整个文件r[n+1]进行快速排序时,只需调用quicksort(r,1,n)即可。 快速排序过程如下:(方扩号表示无序区) 初始关键字 [70 73 69 23 93 18 11 68] 第一趟排序之后 [68 11 69 23 18] 70 [93 73] 第二趟排序之后 [18 11 23] 68 [69] 70 [93 73] 第三趟排序之后 11 18 23 68 69 70 [93 73] 第三趟排序之后 11 18 23 68 69 70 73 93 就平均时间性能而言,是最佳的,其时间复杂度为O(n·log2n)。对已基本有序的序列,该算法的效率很低,近似于O(n2)。另外,n较小时,该算法效果也不明显,n较大时,效果好。快速排序是。不稳定的 67.直接选择排序:其做法是:首先在所有的记录中选出键值最小的记录,把它与第一个记录交换;然后在其余的记录中再选出记录键值最小的记录与第二个记录交换;依次类推,直到所有记录排序完成。直接选择排序算法如下: void select(list r,int n) {for(i=1;i<=n-1;i++) {k=i; for(j=i+1;j<=n;j++) if(r[j].key if(k!=i) {p=r[k]; r[k]=r[i];r[i]=p;} } } 直接选择排序过程如下:(方扩号表示无序区) 初始关键字 [70 73 69 23 93 18 11 68] 11 [70 73 69 23 93 18 68] 11 18 [73 69 23 93 70 68] 11 18 23 [73 69 93 70 68] 11 18 23 68 [73 69 93 70] 11 18 23 68 69 [73 93 70] 11 18 23 68 69 70 73 93 直接选择排序算法的时间复杂度为O(n2)。直接选择排序是不稳定的 68.堆排序:堆的定义如下:堆是一键值序列{k1,k2, …,kn},对所有i=1,2,…,[n/2],满足: ki≤k2i且ki≤k2i+1 堆排序的基本思想是对一组待排序记录的键值,首先按堆的定义排成一个堆,称为建堆。这就找到最小的键值;然后将最小的键值取出,调整剩下的键值成为一个新堆,便得到次最小的键值;如此反复,直到最大键值也被取出。不稳定 69.归并排序:基本思想是:将有序的子序列进行合并,从而得到有序的序列。 二路归并排序过程如下: 初始序列 [26] [5] [77] [1] [61] [11] [59] [15] [48] [19] [5 26] [1 77] [11 61] [15 59] [19 48] [1 5 26 77] [11 15 59 61] [19 48] [1 5 11 15 26 59 61 77] [19 48] [1 5 11 15 19 26 48 59 61 77] 二路归并排序算法的时间复杂度为O(n·log2n)。需要附加一倍的存储开销。排序是稳定的。