数据结构 第二章 章节重点概要Word文档格式.docx
《数据结构 第二章 章节重点概要Word文档格式.docx》由会员分享,可在线阅读,更多相关《数据结构 第二章 章节重点概要Word文档格式.docx(18页珍藏版)》请在冰豆网上搜索。
线性表的顺序存储是指在内存中用地址连续的一块存储空间顺序存放线性表的各元素,用这种存储形式存储的线性表称其为顺序表。
设a1的存储地址为Loc(a1),每个数据元素占k个存储单元,则第i个数据元素的地址为:
Loc(ai)=Loc(a1)+(i-1)*k1≤i≤n
其中a1称为基地址。
图2.1给出了顺序表的存储结构。
1
…
i-1
I
n-1
...MAXSIZE1-1
data
a1
a2
ai-1
ai
ai+1
an
图2.1线性表的顺序存储示意图
顺序存储结构可以借助高级语言中的一维数组来实现,一维数组下标与元素在线性表中的序号相对应。
用C语言描述如下:
#defineMAXSIZE1000/*线性表能达到的最大长度*/
typedefstruct
{ElemTypedata[MAXSIZE];
intlast;
}SeqList;
需要说明的是:
元素的序号和数组的下标不是一致的,如:
a1的序号是1,在数组中的下标是0。
2.2.2顺序表基本运算的实现
1.顺序表的初始化
顺序表的初始化即构造一个空表,将L设为指针参数,首先动态分配存储空间,然后,将表中last指针置为-1,表示表中没有数据元素。
算法如下:
SeqList*init_SeqList()
{SeqList*L;
L=(SeqList*)malloc(sizeof(SeqList));
L->
last=-1;
returnL;
}
算法2.1
2.插入运算
线性表的插入是指在表的第i个位置上插入一个值为x的新元素,插入后使原表长为n的表:
(a1,a2,...,ai-1,ai,ai+1,...,an)
成为表长为n+1表:
(a1,a2,...,ai-1,x,ai,ai+1,...,an )。
i的取值范围为1≤i≤n+1。
顺序表上完成这一运算则通过以下步骤进行:
(1)将ai~an顺序向后移动,为新元素让出位置;
(2)将x置入空出的第i个位置;
(3)修改last指针(相当于修改表长),使之仍指向最后一个元素。
intInsert_SeqList(SeqList*L,inti,datatypex)
{ intj;
if(L->
last==MAXSIZE-1)
{printf("表满");
return(-1);
}/*表空间已满,不能插入*/
if(i<
1||i>
L->
last+2) /*检查插入位置的正确性*/
{printf("插入位置错!
");
return(0);
}
for(j=L->
last;
j>
=i-1;
j--)
data[j+1]=L->
data[j];
/*向后移动元素*/
data[i-1]=x;
/*新元素插入*/
last++;
/*last仍指向最后元素*/
return
(1);
/*插入成功*/
算法2.2
在顺序表上做插入操作的时间复杂度为O(n)。
3.删除运算DeleteList(L,i)
线性表的删除运算是指将表中第i个元素从线性表中去掉,删除后使原表长为n的线性表:
(a1,a2,...,ai-1,ai,ai+1,...,an)
成为表长为n-1的线性表:
(a1,a2,...,ai-1,ai+1,...,an)。
i的取值范围为:
1≤i≤n。
顺序表上完成这一运算的步骤如下:
(1)将ai+1~an顺序向前移动。
(2)修改last指针(相当于修改表长)使之仍指向最后一个元素。
intDelete_SeqList(SeqList*L;
inti)
{intj;
if(i<
last+1)/*检查空表及删除位置的合法性*/
{printf("不存在第i个元素");
return(0);
for(j=i;
j<
=L->
j++)
data[j-1]=L->
/*向前移动元素*/
last--;
return
(1);
/*删除成功*/
算法2.3
该算法的时间复杂度为O(n)。
4.按值查找
线性表中的按值查找是指在线性表中查找与给定值x相等的数据元素。
在顺序表中完成该运算最简单的方法是:
从第一个元素a1起依次和x比较,直到找到一个与x相等的数据元素,则返回它在顺序表中的存储下标;
或者查遍整个表都没有找到与x相等的元素,返回-1。
intLocation_SeqList(SeqList*L,datatypex)
{inti=0;
while(i<
=L.last&
&
data[i]!
=x)
i++;
if(i>
last)return-1;
elsereturni;
/*返回存储位置*/
}
算法2.4
本算法时间复杂度为O(n)。
2.2.3顺序表应用举例
例2.1有顺序表A和B,其元素均按从小到大的升序排列,编写一个算法将它们合并成一个顺序表C,要求C的元素也是从小到大的升序排列。
算法思路:
依次扫描A和B的元素,比较当前的元素的值,将较小值的元素赋给C,如此直到一个线性表扫描完毕,然后将未完的另一个顺序表中余下部分赋给C即可。
C的容量要能够容纳A、B两个线性表相加的长度。
voidmerge(SeqListA,SeqListB,SeqList*C)
{inti,j,k;
i=0;
j=0;
k=0;
while(i<
=A.last&
j<
=B.last)/*归并*/
if(A.date[i]<
B.date[j])
C->
data[k++]=A.data[i++];
elseC->
data[k++]=B.data[j++];
while(i<
=A.last)/*插入A剩余部分元素*/
data[k++]=A.data[i++];
while(j<
=B.last)/*插入B剩余部分元素*/
C->
last=k-1;
算法2.5
算法的时间性能是O(m+n),其中m是A的表长,n是B的表长。
2.3线性表的链式表示和实现
顺序表的存储特点是用物理上的相邻实现了逻辑上的相邻,它要求用连续的存储单元顺序存储线性表中各元素,因此,对顺序表插入、删除时需要移动大量数据元素,这影响了运行效率。
本节介绍线性表链式存储结构,它不需要用地址连续的存储单元来实现,因为它不要求逻辑上相邻的两个数据元素物理上也相邻,它是通过“链”建立起数据元素之间的逻辑关系来,因此对线性表的插入、删除不需要移动数据元素,从而提高了运行效率。
2.3.1单链表
链表是将线性表的元素存储在一个链表的结点中,并用该结点中的指针指向线性表的下一个结点,链表中结点顺序与线性表中元素的逻辑顺序一致,但链表中相邻结点的存储位置不一定相邻。
为建立元素之间的线性关系,除了存放数据元素的自身的信息(data)之外,还需要存放其后继元素存储位置的指针(next),这两部分信息组成一个“结点”,结点的结构如图2.2所示。
n个结点的线性表通过每个结点的指针域拉成了一个“链子”,称之为链表。
又因为每个结点中只有一个指向后继的指针,所以称其为单链表。
结点结构定义如下:
typedefstruct
{datatypedata;
structnode*next;
}LNode,*LinkList;
设有线性表(a1,a2,a3,a4,a5,a6,a7,a8),则图2.3给出了其链式存储结构,图2.4给出了其逻辑状态。
将第一个结点的地址160放到一个指针变量如H中,最后一个结点没有后继,其指针域必需置空,表明链表到此结束,这样就可以从第一个结点的地址开始依次访问每一个结点。
通常用“头指针”来标识一个单链表,如单链表H,是指某链表的第一个结点的地址放在了指针变量H中,头指针为“NULL”则表示一个空表。
110
a5
200
150
190
160
a3
210
a6
260
a4
….
240
a8
NULL
…
...
a7
图2.3链式存储结构
2.3.2单链表上基本运算的实现
1.建立单链表
链表是一种动态管理的存储结构,链表中的每个结点占用的存储空间不是预先分配,而是运行时系统根据需求而申请的,因此建立单链表从空表开始,每读入一个数据元素则申请一个结点,然后插入到链表中。
设有线性表L=(25,45,18,76,29)。
(1)头插法建立单链表。
每生成一个结点后都插入在头指针之后。
图2.5表示了头插法生成单链表的过程。
LinkListCreat_LinkList1()
{LinkListH=NULL;
/*空表*/
LNode*s;
intx;
/*设数据元素的类型为int*/
scanf("%d",&
x);
while(x!
=flag)
{s=(LNode*)malloc(sizeof(LNode));
s->
data=x;
next=H;
H=s;
Scanf("%d",&
returnH;
算法2.6
(2)尾插法建立单链表
头插法建立单链表算法实现简单,但读入的数据元素的顺序与生成的链表中元素的顺序是相反的,若希望次序一致,则用尾插入的方法。
因为每次是将新结点插入到链表的尾部,所以需加入一个指针r用来始终指向链表中的尾结点,以便能够将新结点插入到链表的尾部,尾插法建立链表的过程如图2.6所示。
LinkListCreat_LinkList2()
{LinkListH=NULL;
LNode*s,*r=NULL;
/*设数据元素的类型为int*/
if(H==NULL)H=s;
/*第一个结点的处理*/
elser->
next=s;
/*其它结点的处理*/
r=s;
/*r指向新的尾结点*/
r->
next=NULL;
算法2.7
为了方便操作,有时在链表的头部加入一个“头结点”,头结点的类型与数据结点一致,标识链表的头指针变量L中存放该结点的地址,这样即使是空表,头指针变量L也不为空。
头结点的加入完全是为了运算的方便,它的数据域无定义,指针域中存放的是第一个数据结点的地址,空表时为空。
图2.7(a)、(b)分别是带头结点的单链表空表和非空表。
2.求表长
设一个移动指针p和计数器j,初始化后,若p所指结点后面还有结点,p向后移动,计数器加1。
在此以带头结点的单链表为例。
intLength_LinkList1(LinkListH)
{Lnode*p=H;
/*p指向头结点*/
intj=0;
while(p->
next)
{p=p->
next;
j++}/*p所指的是第j个结点*/
returnj;
算法2.8
3.查找操作
在链表中查找第一个值为x的元素。
从链表的第一个元素结点起,判断当前结点其值是否等于x,若是,返回该结点的指针,否则继续后一个,表结束为止。
找不到时返回空。
Lnode*Locate_LinkList(LinkListH,datatypex)
/*在单链表H中查找值为x的结点,找到后返回其指针,否则返回空*/
{Lnode*p=H->
while(p!
=NULL&
p->
data!
p=p->
returnp;
算法2.9
4.插入
要在带头结点的单链表H中第i个结点之前插入数据元素x。
首先找到第i-1个结点;
若存在则由指针q指向该结点,申请新结点s,插入新结点s到q之后,插入过程如图2.8所示。
intInsert_LinkList(LinkListH,inti,datatypex)
{/*在单链表H的第i个位置上插入值为x的元素*/
Lnode*q,*s;
intk;
q=H;
while(q&
k<
i-1)/*查找第i-1个结点*/
{q=q->
k++}
if(q==NULL)
{printf("位置错");
return0;
}/*第i-1个不存在不能插入*/
s=(LNode*)malloc(sizeof(LNode));
/*申请结点*/
next=q->
/*新结点插入在第i-1个结点的后面*/
q->
next=s
return1;
算法2.10
5.删除
要删除单链表L中的第I个结点,首先要找到第i-1个结点,并让q指针指向该结点,再删除第i个结点并释放其存储空间。
删除过程如图2.9所示。
intDel_LinkList(LinkListH,inti)
{/*删除单链表L上的第i个数据结点*/
LinkLists,q=H;
intk=0;
while(q->
next&
k<
k++;
if(q->
next==NULL)
{printf("删除位置有错!
return-1;
s=q->
/*s指向第i个结点*/
next->
/*从链表中删除*/
free(s);
/*释放*s*/
算法2.11
容易看出,算法2.8、2.9、2.10、2.11的时间复杂度都为O(n)。
2.3.3循环链表
对于单链表而言,最后一个结点的指针域是空指针,如果将该链表最后一个结点的指针指向头结点,则使得链表头尾结点相连,就构成了单循环链表。
如图2.10所示。
在单循环链表上的操作基本上与非循环链表相同,只是将原来判断指针是否为NULL变为是否是头指针而已,没有其它较大的变化。
对于单链表只能从头结点开始遍历整个链表,而对于单循环链表则可以从表中任意结点开始遍历整个链表,不仅如此,有时对链表常做的操作是在表尾、表头进行,此时可以改变一下链表的标识方法,不用头指针而用一个指向尾结点的指针R来标识,可以使得操作效率得以提高。
2.3.4双向链表
在单链表和循环链表的结点中只有一个指向其后继结点的指针域next,因此若已知某结点的指针为p,其后继结点的指针则为p->
next,而找其前驱则不是很方便。
如果在每个结点再加一个指向其前驱结点的指针域,结点的结构为如图2.11所示,则找其前驱结点和后继结点一样方便,用这种结点组成的链表称为双向链表。
双向链表结点的定义如下:
typedefstructdlnode
{datatypedata;
structdlnode*prior,*next;
}DLNode,*DLinkList;
和单链表类似,双向链表通常也是用头指针标识,也可以带头结点和做成循环结构,图2.12是带头结点的双向循环链表示意图。
显然通过某结点的指针p即可以直接得到它的后继结点的指针p->
next,也可以直接得到它的前驱结点的指针p->
prior。
设指针p指向双向循环链表中的某一结点,则p->
prior->
next表示的是p结点之前驱结点的后继结点,即与p相等;
同样,p->
prior表示的是p结点之后继结点的前驱结点,也与p相等,所以有以下等式:
p->
next=p=p->
prior
双向链表中结点的插入:
设p指向双向链表中某结点,s指向待插入的值为x的新结点,将s插入到p的前面,插入过程如图2.13所示。
操作如下:
1s->
prior=p->
prior;
2p->
3s->
next=p;
4p->
prior=s;
指针操作的顺序不是唯一的,
但也不是任意的,操作①必须要
放到操作④的前面完成,否则p
的前驱结点的指针就会丢失。
双向链表中结点的删除:
设p指向双向链表中某结点,删除该结点,操作示意图如图2.14所示。
①p->
next=p->
②p->
free(p);
2.3.5单链表应用举例
例2.2已知单链表H,写一算法将其倒置。
即实现如图2.15的操作。
(a)为倒置前,(b)为倒置后。
算法思路:
依次取原链表中的每个结点,将其作为第一个结点插入到新链表中去,指针p用来指向当前结点,p为空时结束。
voidreverse(LinklistH)
{LNode*p;
p=H->
/*p指向第一个数据结点*/
H->
/*将原链表置为空表H*/
while(p)
{q=p;
next=H->
/*将当前结点插到头结点的后面*/
next=q;
算法2.12
该算法只是对链表中顺序扫描一边即完成了倒置,所以时间性能为O(n)。
例2.3已知单链表H,写一算法,删除其重复结点,即实现如图2.16的操作。
(a)为删除前,(b)为删除后。
用指针p指向第一个数据结点,从它的后继结点开始到表的结束,找与其值相同的结点并删除之;
p指向下一个;
依此类推,p指向最后结点时算法结束。
voidpur_LinkList(LinkListH)
{LNode*p,*q,*r;
/*p指向第一个结点*/
if(p==NULL)return;
next)
while(q->
next)/*从*p的后继开始找重复结点*/
{if(q->
data==p->
data)
{r=q->
/*找到重复结点,用r指向,删除*r*/
next=r->
free(r);
}/*if*/
elseq=q->
}/*while(q->
next)*/
/*p指向下一个,继续*/
}/*while(p->
算法2.13
该算法的时间性能为O(n2)。
例2.4设有两个单链表A、B,其中元素递增有序,编写算法将A、B归并成一个按元素值递增(允许有相同值)有序的链表C,要求用A、B中的原结点形成,不能重新申请结点。
利用A、B两表有序的特点,依次进行比较,将当前值较小者摘下,插入到C表的头部,得到的C表则为递增有序的。
LinkListmerge(LinkListA,LinkListB)
/*设A、B均为带头结点的单链表*/
{LinkListC;
LNode*p,*q;
p=A->
q=B->
C=A;
/*C表的头结点*/
free(B);
while(p&
q)
{if(p->
data<
q->
{s=p;
p=p->
else
{s=q;
q=q->
}/*从原AB表上摘下较小者*/
s->
next=C->
/