线性表 教案.docx
《线性表 教案.docx》由会员分享,可在线阅读,更多相关《线性表 教案.docx(16页珍藏版)》请在冰豆网上搜索。
线性表教案
线性表
【教学目的】:
1、了解数据结构的概念
2、掌握时间复杂度的概念和计算方法
【教学方法】:
理论教学
【教学课时】:
2课时(复习)
【教学内容】:
一、基础知识和算法
线性表及其特点
线性表是n个数据元素的有限序列。
线性结构的特点:
①“第一个”②“最后一个”③前驱④后继。
1.顺序表——线性表的顺序存储结构
特点
a)逻辑上相邻的元素在物理位置上相邻。
b)随机访问。
类型定义
简而言之,“数组+长度”。
constintMAXSIZE=线性表最大长度;
typedefstruct{
DataTypeelem[MAXSIZE];
intlength;
}SqList;
注:
a)SqList为类型名,可换用其他写法。
b)DataType是数据元素的类型,根据需要确定。
c)MAXSIZE根据需要确定。
如
constintMAXSIZE=64;
d)课本上的SqList类型可在需要时增加存储空间,在上面这种定义下不可以。
(这样做避免了动态内存分配,明显减少了算法的复杂程度,容易理解。
而且,原来Pascal版本的《数据结构》(严蔚敏)就是这样做的。
)
e)课本上的SqList类型定义中listsize表示已经分配的空间大小(容纳数据元素的个数)。
当插入元素而遇到L.length==L.listsize时,用realloc(L.elem,L.listsize+增量)重新分配内存,而realloc()函数在必要的时候自动复制原来的元素到新分配的空间中。
基本形态
顺序表空
条件L.length==0
不允许删除操作
顺序表满
条件L.length==MAXSIZE
不允许插入操作
不空也不满
可以插入,删除
基本算法——遍历
顺序访问所有元素
for(i=0;ivisit(L.elem[i]);
查找元素x
for(i=0;iif(L.elem[i]==x)break;
if(i找到;
else
未找到;
插入算法ListInsert(&L,i,x)
前提:
表不满
合理的插入范围:
1≤i≤L.length+1
注:
位序i在C/C++中对应于下标i-1。
步骤
第i至最后所有元素后移一个元素
在第i个位置插入元素x
表长增1
算法
boolListInsert(SqList&L,inti,DataTypex){
if(L.length==MAXSIZE||i<1||i>L.length+1)returnfalse;//失败
//元素后移
for(j=L.length-1;j>=i-1;j--)//这里j为下标,从L.length-1到i-1
L.elem[j+1]=L.elem[j];//若作为位序,有如何修改?
//插入x
L.elem[i-1]=x;
//表长增1
L.length++;
returntrue;//插入成功
}
删除算法ListDelete(&L,i,&x)
前提:
表非空
合理的删除范围:
1≤i≤L.length
步骤
取出第i个元素
第i个元素之后的元素向前移动一个位置
表长减1
算法
boolListDelete(SqList&L,inti,DataType&x)
{
if(L.length==0||i<1||i>L.length)returnfalse;//失败
x=L.elem[i-1];
for(j=i;jL.elem[j-1]=L.elem[j];
L.length--;
returntrue;//删除成功
}
算法分析
表2.1顺序表插入和删除算法的分析
插入
删除
基本操作
平均移动次数
移动元素
移动元素
时间复杂度
O(n)
O(n)
尾端操作
插入第n+1个元素,不移动
删除第n个元素,不移动
插入、删除需移动大量元素O(n);但在尾端插入、删除效率高O
(1)。
其他算法
InitList(&L),ClearList(&L)
L.length=0;
ListEmpty(L)
returnL.length==0;
ListLength(L)
returnL.length;
GetElem(L,i,&e)
e=L.elem[i-1];
2.单链表——线性表的链式存储结构之一
概念
线性链表,单链表,结点;数据域,指针域;头指针,头结点。
特点
用指针表示数据之间的逻辑关系(逻辑相邻的元素物理位置不一定相邻)。
类型定义
简而言之,“数据+指针”。
typedefstructLNode{
DataTypedata;
structLNode*next;
}LNode,*LinkList;
基本形态
带头结点的单链表的基本形态有:
单链表空
条件:
L->next==0
单链表不空
条件:
L->next!
=0
基本算法(遍历)
顺序访问所有元素
借助指针,“顺藤摸瓜”(沿着链表访问结点)。
p=L->next;//注意起始位置的考虑
while(p!
=NULL){//判表尾,另外(p!
=0)或(p)均可
visit(p->data);//访问:
可以换成各种操作
p=p->next;//指针沿着链表向后移动
}
例:
打印单链表中的数据。
voidPrintLinkList(LinkListL)
{
p=L->next;
while(p!
=NULL){
print(p->data);//访问:
打印数据域
p=p->next;
}
}
查找元素x
//在单链表L中查找元素x
//若找到,返回指向该结点的指针;否则返回空指针
LinkListFind(LinkListL,DataTypex)
{
p=L->next;
while(p!
=NULL){
if(p->data==x)returnp;//找到x
p=p->next;
}
returnNULL;//未找到
}
//在单链表L中查找元素x
//若找到,返回该元素的位序;否则返回0
intFind(LinkListL,DataTypex)
{
p=L->next;j=1;
while(p!
=NULL){
if(p->data==x)returnj;//找到x
p=p->next;j++;//计数器随指针改变
}
return0;//未找到
}
前一个算法的另一种写法:
p=L->next;
while(p&&p->data!
=x)
p=p->next;
if(p&&p->data==x)returnp;
elsereturn0;
或者
p=L->next;
while(p&&p->data!
=x)p=p->next;
returnp;//为什么
查找第i个元素
LinkListGet(LinkListL,inti)
{
p=L->next;j=1;
while(p&&j
p=p->next;j++;
}
if(p&&j==i)returnp;
elsereturn0;
}
查找第i-1个元素
p=L;j=0;
while(p&&jp=p->next;j++;
}
if(p&&j==i-1)returnp;
elsereturn0;
插入算法ListInsert(&L,i,x)
技巧:
画图辅助分析。
思路:
先查找第i-1个元素
若找到,在其后插入新结点
boolListInsert(LinkList&L,inti,DataTypex)
{
//查找第i-1个元素p
p=L;j=0;
while(p&&jp=p->next;j++;
}
//若找到,在p后插入x
if(p&&j==i-1){
s=(LinkList)malloc(sizeof(LNode));
s->data=x;
s->next=p->next;//①
p->next=s;//②
returntrue;//插入成功
}
else
returnfalse;//插入失败
}
注意:
a)要让p指向第i-1个而不是第i个元素(否则,不容易找到前驱以便插入)。
b)能够插入的条件:
p&&j==i-1。
即使第i个元素不存在,只要存在第i-1个元素,仍然可以插入第i个元素。
c)新建结点时需要动态分配内存。
s=(LinkList)malloc(sizeof(LNode));
若检查是否分配成功,可用
if(s==NULL)exit
(1);//分配失败则终止程序
d)完成插入的步骤:
①②。
技巧:
先修改新结点的指针域。
删除算法ListDelete(&L,i,&x)
思路:
先查找第i-1个元素
若找到且其后存在第i个元素,则用x返回数据,并删除之
boolListDelete(LinkList&L,inti,int&x)
{
//查找第i-1个元素p
p=L;j=0;
while(p&&jp=p->next;j++;
}
//若存在第i个元素,则用x返回数据,并删除之
if(p&&j==i-1&&p->next){//可以删除
s=p->next;//①
p->next=s->next;//②
x=s->data;
free(s);
returntrue;
}
else
returnfalse;
}
注意:
a)要求p找到第i-1个而非第i个元素。
为什么?
b)能够进行删除的条件:
p&&j==i-1&&p->next。
条件中的p->next就是要保证第i个元素存在,否则无法删除。
若写成p->next&&j==i-1也不妥,因为此时(循环结束时)可能有p==NULL,所以必须先确定p不空。
技巧:
将条件中的“大前提”放在前面。
该条件也不可以写成p->next&&p&&j==i-1,因为先有p!
=0才有p->next,上式颠倒了这一关系。
c)释放结点的方法。
free(s);
d)完成删除的步骤:
①②。
建立链表的两种方法
思路:
建立空表(头结点);
依次插入数据结点(每次插入表尾得(a1,a2,…,an),每次插入表头得(an,…,a2,a1))。
顺序建表
voidCreateLinkList(LinkList&L,intn)
{
//建立空表
L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;//空表
p=L;//用p指向表尾
//插入元素
for(i=0;iscanf(x);
s=(LinkList)malloc(sizeof(LNode));
s->data=x;
//插入表尾
s->next=p->next;
p->next=s;
p=s;//新的表尾
}
}
逆序建表
voidCreateLinkList(LinkList&L,intn)
{
//建立空表
L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;//空表
//插入元素
for(i=0;iscanf(x);
s=(LinkList)malloc(sizeof(LNode));
s->data=x;
//插入表头
s->next=L->next;
L->next=s;
}
}
3.循环链表
特点
最后一个结点的指针指向头结点。
类型定义
同单链表。
基本形态
空表:
L->next==L。
非空表。
与单链表的联系
判断表尾的方法不同:
单链表用p==NULL;循环链表用p==L。
其余操作相同。
4.双向循环链表
特点
一个结点包含指向后继(next)和指向前驱(prior)两个指针,两个方向又分别构成循环链表。
类型定义
typedefstructDuLNode{
DataTypedata;
structDuLNode*prior,*next;//两个指针
}DuLNode,*DuLinkList;
基本形态
空表:
用后向指针判断L->next==L,或者用前向指针判断L->prior==L。
非空表。
与单链表和循环链表的联系
最大不同:
前驱容易求得,可以向前遍历。
判断表尾的方法与循环链表相同:
p==L。
插入和删除时需要修改两个方向的指针。
插入和删除
需要修改两个方向的指针。
例如:
(见下表)
表2.2双向循环链表的插入和删除
p之后插入s
p之前插入s
删除p之后继s
删除p
s->next=p->next;
p->next=s;
s->prior=p;
s->next->prior=s;
s->prior=p->prior;
p->prior=s;
s->next=p;
s->prior->next=s;
s=p->next;
p->next=s->next;
p->next->prior=p;
p->prior->next=p->next;
p->next->prior=p->prior;
5.顺序表与单链表的比较
表2.3顺序表和单链表的比较
顺序表
单链表
以地址相邻表示关系
用指针表示关系
随机访问,取元素O
(1)
顺序访问,取元素O(n)
插入、删除需要移动元素O(n)
插入、删除不用移动元素O(n)(用于查找位置)
总结:
需要反复插入、删除,宜采用链表;反复提取,很少插入、删除,宜采用顺序表。
二、习题
2.1将顺序表中的元素反转顺序。
2.2在非递减有序的顺序表中插入元素x,并保持有序。
2.3删除顺序表中所有等于x的元素。
2.4编写算法实现顺序表元素唯一化(即使顺序表中重复的元素只保留一个),给出算法的时间复杂度。
2.5非递减有序的顺序表元素唯一化(参见习题2.4),要求算法的时间复杂度为O(n)。
2.6将单链表就地逆置,即不另外开辟结点空间,而将链表元素翻转顺序。
2.7采用插入法将单链表中的元素排序。
2.8采用选择法将单链表中的元素排序。
2.9将两个非递减有序的单链表归并成一个,仍并保持非递减有序。