自学考试数据结构重点总结02331整理Word格式.docx
《自学考试数据结构重点总结02331整理Word格式.docx》由会员分享,可在线阅读,更多相关《自学考试数据结构重点总结02331整理Word格式.docx(33页珍藏版)》请在冰豆网上搜索。
算法的"
正确性"
是首先要考虑的。
此外,主要考虑如下三点:
①执行算法所耗费的时间,即时间复杂性;
②执行算法所耗费的存储空间,主要是辅助空间,即空间复杂性;
③算法应易于理解、易于编程,易于调试等,即可读性和可操作性。
以上几点最主要的是时间复杂性,时间复杂度常用渐进时间复杂度表示。
7.算法求解问题的输入量称为问题的规模,用一个正整数n表示。
8.常见的时间复杂度按数量级递增排列依次为:
常数阶0
(1)、对数阶0(log2n)、线性阶0(n)、线性对数阶0(nlog2n)、平方阶0(n2)立方阶0(n3)、…、k次方阶0(nk)、指数阶0(2n)和阶乘阶0(n!
)。
9.一个算法的空间复杂度S(n)定义为该算法所耗费的存储空间,它是问题规模n的函数,它包括存储算法本身所占的存储空间、算法的输入输出数据所占的存储空间和算法在运行过程中临时占用的存储空间。
第二章线性表
1.数据的运算是定义在逻辑结构上的,而运算的具体实现是在存储结构上进行的。
2.只要确定了线性表存储的起始位置,线性表中任意一个元素都可随机存取,所以顺序表是一种随机存取结构。
3.常见的线性表的基本运算:
(1)置空表InitList(L)构造一个空的线性表L。
(2)求表长ListLength(L)求线性表L中的结点个数,即求表长。
(3)GetNode(L,i)取线性表L中的第i个元素。
(4)LocateNode(L,x)在L中查找第一个值为x的元素,并返回该元素在L中的位置。
若L中没有元素的值为x,则返回0值。
(5)InsertList(L,i,x)在线性表L的第i个元素之前插入一个值为x的新元素,表L的长度加1。
(6)DeleteList(L,i)删除线性表L的第i个元素,删除后表L的长度减1。
4.顺序存储方法:
把线性表的数据元素按逻辑次序依次存放在一组地址连续的存储单元里的方法。
顺序表(SequentialList):
用顺序存储方法存储的线性表称为顺序表。
顺序表是一种随机存取结构,顺序表的特点是逻辑上相邻的结点其物理位置亦相邻。
5.顺序表上实现的基本运算:
(1)插入:
该算法的平均时间复杂度是O(n),即在顺序表上进行插入运算,平均要移动一半结点(n/2)。
(2)删除:
顺序表上做删除运算,平均要移动表中约一半的结点(n-1)/2,平均时间复杂度也是O(n)。
6.采用链式存储结构可以避免频繁移动大量元素。
一个单链表可由头指针唯一确定,因此单链表可以用头指针的名字来命名。
①生成结点变量的标准函数
p=(ListNode*)malloc(sizeof(ListNode));
//函数malloc分配一个类型为ListNode的结点变量的空间,并将其首地址放入指针变量p中②释放结点变量空间的标准函数free(p);
//释放p所指的结点变量空间③结点分量的访问
方法二:
p-﹥data和p-﹥next
④指针变量p和结点变量*p的关系:
指针变量p的值——结点地址,结点变量*p的值——结点内容
7.建立单链表:
(1)头插法建表:
算法:
p=(ListNode*)malloc(sizeof(ListNode));
①//生成新结点
p->
data=ch;
②//将读入的数据放入新结点的数据域中
next=head;
③
head=p;
④
(2)尾插法建表:
①//生成新结点
if(head==NULL)
//新结点插入空表
else
rear->
next=p;
③//将新结点插到*r之后
rear=p;
④//尾指针指向新表尾
(3)尾插法建带头结点的单链表:
头结点及作用:
头结点是在链表的开始结点之前附加一个结点。
它具有两个优点:
⒈由于开始结点的位置被存放在头结点的指针域中,所以在链表的第一个位置上的操作就和在表的其它位置上操作一致,无须进行特殊处理;
⒉无论链表是否为空,其头指针都是指向头结点的非空指针(空表中头结点的指针域空),因此空表和非空表的处理也就统一了。
头结点数据域的阴影表示该部分不存储信息。
在有的应用中可用于存放表长等附加信息。
具体算法:
r=head;
//
尾指针初值也指向头结点
while((ch=getchar())!
='
\n'
){
s=(ListNode*)malloc(sizeof(ListNode));
//生成新结点
s->
//将读入的数据放入新结点的数据域中
r->
next=s;
r=s;
}
next=NULL;
//终端结点的指针域置空,或空表的头结点指针域置空
以上三个算法的时间复杂度均为O(n)。
8.单链表上的查找:
(带头结点)
(1)按结点序号查找:
序号为0的是头结点。
p=head;
j=0;
//从头结点开始扫描
while(p->
next&
&
j<
i){//顺指针向后扫描,直到p->
next为NULL或i=j为止
p=p->
next;
j++;
if(i==j)
returnp;
//找到了第i个结点
elsereturnNULL;
//当i<
0或i>
0时,找不到第i个结点
时间复杂度:
在等概率假设下,平均时间复杂度为:
为n/2=O(n)
(2)按结点值查找:
ListNode*p=head->
//从开始结点比较。
表非空,p初始值指向开始结点
while(p&
p->
data!
=key)//直到p为NULL或p->
data为key为止
//扫描下一结点
//若p=NULL,则查找失败,否则p指向值为key的结点
时间复杂度为:
O(n)
9.插入运算:
插入运算是将值为x的新结点插入到表的第i个结点的位置上,即插入到ai-1与ai之间。
②
data=x;
③s->
next=p->
next④;
⑤
算法的时间主要耗费在查找结点上,故时间复杂度亦为O(n)。
10.删除运算
r=p->
②//使r指向被删除的结点ai
next=r->
next③;
//将ai从链上摘下
free(r);
④//释放结点ai的空间给存储池
算法的时间复杂度也是O(n)。
p指向被删除的前一个结点。
链表上实现的插入和删除运算,无须移动结点,仅需修改指针。
11.单循环链表—在单链表中,将终端结点的指针域NULL改为指向表头结点或开始结点即可。
判断空链表的条件是head==head->
12.仅设尾指针的单循环链表:
用尾指针rear表示的单循环链表对开始结点a1和终端结点an查找时间都是O
(1)。
而表的操作常常是在表的首尾位置上进行,因此,实用中多采用尾指针表示单循环链表。
判断空链表的条件为rear==rear->
13.循环链表:
循环链表的特点是无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活。
若在尾指针表示的单循环链表上实现,则只需修改指针,无须遍历,其执行时间是O
(1)。
LinkListConnect(LinkListA,LinkListB)
{//假设A,B为非空循环链表的尾指针
LinkListp=A->
//①保存A表的头结点位置
A->
next=B->
next->
//②B表的开始结点链接到A表尾
free(B->
next);
//③释放B表的头结点
B->
//④
returnB;
//返回新循环链表的尾指针
循环链表中没有NULL指针。
涉及遍历操作时,其终止条件就不再是像非循环链表那样判别p或p->next是否为空,而是判别它们是否等于某一指定指针,如头指针或尾指针等。
在单链表中,从一已知结点出发,只能访问到该结点及其后续结点,无法找到该结点之前的其它结点。
而在单循环链表中,从任一结点出发都可访问到表中所有结点,这一优点使某些运算在单循环链表上易于实现。
14.双向链表:
双(向)链表中有两条方向不同的链,即每个结点中除next域存放后继结点地址外,还增加一个指向其直接前趋的指针域prior。
①双链表由头指针head惟一确定的。
②带头结点的双链表的某些运算变得方便。
③将头结点和尾结点链接起来,为双(向)循环链表。
15.双向链表的前插和删除本结点操作
①双链表的前插操作
voidDInsertBefore(DListNode*p,DataTypex){//在带头结点的双链表中,将值为x的新结点插入*p之前,设p≠NULL
DListNode*s=malloc(sizeof(DListNode));
//①
//②
prior=p->
prior;
//③
prior->
//⑤
prior=s;
//⑥
②双链表上删除结点*p自身的操作
voidDDeleteNode(DListNode*p)
{//在带头结点的双链表中,删除结点*p,设*p为非终端结点
free(p);
与单链表上的插入和删除操作不同的是,在双链表中插入和删除必须同时修改两个方向上的指针。
上述两个算法的时间复杂度均为O
(1)。
16.顺序表和链表比较
时间性能:
a、线性表:
经常性的查找;
b、链式存储结构:
经常插入删除操作;
空间性能:
a、对数据量大小事先能够知道的用线性表;
b、数据量变化较大的用链式存储结构。
存储密度越大,存储空间的利用率越高。
显然,顺序表的存储密度是1,链表的存储密度肯定小于1。
第三章栈和队列
1.栈称为后进先出(LastInFirstOut)的线性表,简称为LIFO表。
栈是运算受限的线性表,顺序栈也是用数组表示的。
进栈操作:
进栈时,需要将S->top加1,①S->top==StackSize-1表示栈满
②"
上溢"
现象--当栈满时,再做进栈运算产生空间溢出的现象。
退栈操作:
退栈时,需将S->top减1,①S->top<
0表示空栈
下溢"
现象--当栈空时,做退栈运算产生的溢出现象。
下溢是正常现象,常用作程序控制转移的条件。
空栈时栈顶指针不能是0,只能是-1。
当程序中同时使用两个栈时,可以将两个栈的栈底分别设在顺序存储空间的两端,让两个栈顶各自向中间延伸。
当一个栈中的元素较多而栈使用的空间超过共享空间的一半时,只要另一个栈的元素不多,那么前者就可以占用后者的部分存储空间。
当Top1=Top2-1时,栈满
2.为了克服顺序存储分配固定空间所产生的溢出和空间浪费问题。
可采用链式存储结构来存储栈。
链栈是没有附加头结点的运算受限的单链表。
栈顶指针就是链表的头指针。
链栈中的结点是动态分配的,所以可以不考虑上溢,无须定义StackFull运算
栈的一个重要应用是实现递归,直接调用自己或间接调用自己的函数。
3.允许删除的一端称为队头(Front),允许插入的一端称为队尾(Rear),当队列中没有元素时称为空队列,队列亦称作先进先出(FirstInFirstOut)的线性表,简称为FIFO表。
队列的顺序存储结构称为顺序队列,顺序队列实际上是一个受限的线性表。
顺序队列的基本操作
①入队时:
将新元素插入rear所指的位置,然后将rear加1。
②出队时:
删去front所指的元素,然后将front加1并返回被删元素。
当头尾指针相等时,队列为空。
在非空队列里,头指针始终指向队头元素,而队尾指针始终指向队尾元素的下一位置。
而栈顶指针指向栈顶元素。
4.循环队列:
为充分利用数组空间,克服上溢,可将数组空间想象为一个环状空间,并称这种环状数组表示的队列为循环队列。
循环队列中进行出队、入队操作时,头尾指针仍要加1,朝前移动。
只不过当头尾指针指向向量上界(QueueSize-1)时,其加1操作的结果是指向向量的下界0。
这种循环意义下的加1操作可以描述为:
①方法一:
if(i+1==QueueSize)//i表示front或rear
i=0;
i++;
②方法二--利用"
模运算"
i=(i+1)%QueueSize;
循环队列中,由于入队时尾指针向前追赶头指针;
出队时头指针向前追赶尾指针,造成队空和队满时头尾指针均相等。
因此,无法通过条件Q.front==Q.rear来判别队列是"
空"
还是"
满"
。
解决这个问题的方法至少有三种:
①另设一个标志位以区别队列是空还是满;
②设置一个计数器记录队列中元素的总数(即队列长度)。
③少用一个元素的空间。
约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队列满即尾指针Q.rear所指的单元始终为空。
5.循环队列的基本运算:
①置队空:
Q->
front=Q->
rear=0;
②判队空:
returnQ->
rear==Q->
front;
③判队满:
return(Q->
rear+1)%QueueSize==Q->
④入队Q->
data[Q->
rear]=x;
//新元素插入队尾
rear=(Q->
rear+1)%QueueSize;
⑤出队temp=Q->
front];
front=(Q->
front+1)%QueueSize;
//循环意义下的头指针加1
returntemp;
⑥取队头元素returnQ->
6.队列的链式存储结构简称为链队列。
它是限制仅在表头删除和表尾插入的单链表。
为了简化处理,在队头结点之前附加一个头结点,并设队头指针指向此结点。
链队列的基本运算:
(1)构造空队:
Q->
rear=Q->
front;
rear->
(2)判队空:
returnQ->
(3)入队:
QueueNode*p=(QueueNode*)malloc(sizeof(QueueNode));
//申请新结点
//*p链到原队尾结点后
rear=p;
//队尾指针指向新的尾
(4)出队:
当队列长度大于1时,只需修改头结点指针,尾指针不变
s=Q->
front->
next=s->
x=s->
data;
free(s);
returnx;
当队列长度等于1时,不仅要修改头结点指针,还要修改尾指针
Q->
(5)取队头元素:
因为有头结点,所以用了next
①和链栈类似,无须考虑判队满的运算及上溢。
②在出队算法中,一般只需修改队头指针。
但当原队中只有一个结点时,该结点既是队头也是队尾,故删去此结点时亦需修改尾指针,且删去此结点后队列变空。
7.用计算机来处理计算算术表达式问题,首先要解决的问题是如何将人们习惯书写的中缀表达式转换成后缀表达式。
第四章多维数组和广义表
1.数组的顺序存储方式:
一般采用顺序存储方法表示数组。
(1)行优先顺序
a11,a12,…,a1n,a21,a22,…,a2n,……,am1,am2,…,amn
(2)列优先顺序
a11,a21,…,am1,a12,a22,…,am2,……,a1n,a2n,…,amn
Pascal和C语言是按行优先顺序存储的,而Fortran语言是按列优先顺序存储的。
2.为了节省存储空间,可以对矩阵中有许多值相同或值为零的元素的矩阵,采用压缩存储。
特殊矩阵是指相同值的元素或零元素在矩阵中的分布有一定的规律。
常见的有对称矩阵、三角矩阵。
(1)对称矩阵在一个n阶方阵A中,若元素满足下述性质:
aij=aji0≤i,j≤n-1
称为n阶对称矩阵,它的元素是关于主对角线对称的,所以只需要存储矩阵上三角或下三角元素即可,让两个对称的元素共享一个存储空间。
矩阵元素aij和数组元素sa【k】之间的关系是
k=i×
(i+1)/2+ji≥j0≤k<
n(n+1)/2-1
k=j×
(j+1)/2+ii<j0≤k<
(2)三角矩阵:
以主对角线划分,三角矩阵有上三角和下三角两种。
上三角矩阵是指它的下三角(不包括主角线)中的元素均为常数c或零;
下三角矩阵的主对角线上方均为常数c或零。
一般情况,三角矩阵的常数c均为零。
三角矩阵的压缩存储:
三角矩阵中的重复元素c可共享一个存储空间,其余的元素正好有n×
(n+1)/2个,因此,三角矩阵可压缩存储在一维数组sa[n(n+1)/2+1]中,其中c存放在数组的最后一个元素中。
三角矩阵的压缩存储结构是随机存取结构。
3.稀疏矩阵:
设矩阵Amn中有s个非零元素,若s远远小于矩阵元素的总数,则称A为稀疏矩阵。
为了节省存储单元,可用压缩存储方法只存储非零元素。
由于非零元素的分布一般是没有规律的,因此在存储非零元素的同时,还必须存储非零元素所在的行、列位置,所以可用三元组(i,j,aij)来确定非零元素。
稀疏矩阵进行压缩存储通常有两类方法:
顺序存储(三元组表)和链式存储(十字链表)。
稀疏矩阵的压缩存储会失去随机存取功能。
4.广义表是线性表的推广,又称列表。
广义表是n(n≥0)个元素a1,a2,…,ai,…,an的有限序列。
其中ai或者是原子或者是一个广义表。
①广义表通常用圆括号括起来,用逗号分隔其中的元素。
②为了区分原子和广义表,书写时用大写字母表示广义表,用小写字母表示原子。
③若广义表Ls非空(n≥1),则al是LS的表头,其余元素组成的表(a1,a2,…,an)称为Ls的表尾。
④广义表具有递归和共享的性质
广义表的深度:
一个表展开后所含括号的层数称为广义表的深度。
19.广义表是一种多层次的线性结构,实际上这就是一种树形结构。
任何一个非空广义表的表头可以是原子,也可以是子表,而其表尾必定是子表。
head=(a,b)=a,tail(a,b)=(b)
对非空表A和(y),也可继续分解。
注意:
广义表()和(())不同。
前者是长度为0的空表,对其不能做求表头和表尾的运算;
而后者是长度为l的由空表作元素的广义表,可以分解得到的表头和表尾均是空表()。
广义表是一种有层次的非线性结构,通常采用链式存储结构,每个元素用一个结点表示,结点由3个域构成,其中一个是tag标志位,用来区分结点是原子还是子表,当tag为零时结点是子表,第二个域为slink,用以存放子表的地址;
当tag为1时结点是原子,第二个域为data,用以存放元素值。
第五章树和二叉树
1.树的表示法:
最常用的是树形图表示法;
还有3种嵌套集合、凹形、广义表。
树结构的基本术语
(1)结点的度(Degree)
树中的一个结点拥有的子树数称为该结点的度(Degree)。
一棵树的度是指该树中结点的最大度数。
度为零的结点称为叶子(Leaf)或终端结点。
度不为零的结点称分支结点或非终端结点。
除根结点之外的分支结点统称为内部结点。
根结点又称为开始结点。
(2)①路径(path)若树中存在一个结点序列k1,k2,…,ki,使得ki是ki+1的双亲(1≤i<
j),则称该结点序列是从kl到kj的一条路径(Pat