slide1 n.
Download
Skip this Video
Loading SlideShow in 5 Seconds..
第 2 章 线性表 PowerPoint Presentation
Download Presentation
第 2 章 线性表

Loading in 2 Seconds...

play fullscreen
1 / 112

第 2 章 线性表 - PowerPoint PPT Presentation


  • 100 Views
  • Uploaded on

第 2 章 线性表. 概述. 线性表是最很简单也是最重要的数据结构,它是线性结构。所以我们学习数据结构从线性表开始。从本章开始到第四章都是讨论线性。是整个数据结构的基础 按逻辑结构 、存储结构、算法设计这三个顺序进行分析。. 2.1线性表的逻辑结构. 线性表( Linear List) 是由 n(n≥0) 个数据元素(结点) a 1 ,a 2 ,…,a n 组成的有限序列。 ① 数据元素的个数 n 定义为表的长度( n=0 时称为空表)。 ② 将非空的线性表( n>0) 记作:( a 1 ,a 2 ,…,a n )

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about '第 2 章 线性表' - wynter-cannon


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
slide2
概述

线性表是最很简单也是最重要的数据结构,它是线性结构。所以我们学习数据结构从线性表开始。从本章开始到第四章都是讨论线性。是整个数据结构的基础

按逻辑结构 、存储结构、算法设计这三个顺序进行分析。

slide3
2.1线性表的逻辑结构

线性表(Linear List)是由n(n≥0)个数据元素(结点)a1,a2,…,an组成的有限序列。

① 数据元素的个数n定义为表的长度(n=0时称为空表)。

② 将非空的线性表(n>0)

记作:(a1,a2,…,an)

③ 数据元素ai(1≤i≤n)只是个抽象符号,其具体含义在不同情况下可以不同。

slide5
线性表的例子

【例1】英文字母表(A,B,…,Z)是线性表,表中每个字母是一个数据元素(结点)

【例2】一副扑克牌的点数(2,3,…,10,J,Q,K,A)也是一个线性表,其中数据元素是每张牌的点数和花色

【例3】学生成绩表(见概论中表1.1)中,每个学生及其成绩是一个数据元素,其中数据元素由学号、姓名、各科成绩及平均成绩等数据项组成。

slide6
线性表的特征

有且仅有一个开始结点a1,没有直接前趋,有且仅有一个直接后继a2;

有且仅有一个终结结点an,没有直接后继,有且仅有一个直接前趋an-1;

其余的内部结点ai(2≤i≤n-1)都有且仅有一个直接前趋ai-1和一个ai+1。

slide7
常见的线性表的基本运算

常见的基本运算有:初始化,求表长,求第i个结点,查找,插入和删除等操作。为了尽可能地比较准确地描述些运算过程,我们采用类似于函数的形式对它进行描述。

这些基本运算并不是凭空想出来,而是根据实际应用总结出来的。

slide8
线性表的ADT表示

ADT LinearList {

数据元素:D={ai| ai∈D0,i=1,2,3…n,n≥0,D0某一数据对象}

逻辑结构:R={<ai,ai+1>, ai, ai+1 ∈D0,i=1,2,…,n-1}

基本操作:

1 initlist l
1. InitList(L)

操作前提:L为一个未初始化的线性表。

操作结果:将L初始化为空表。

构造一个空的线性表L,即表的初始化。

2 destroylist l
2. DestroyList(L)

操作前提:线性表L已存在。

操作结果:将L销毁。

3 clearlist l
3. ClearList(L)

操作前提:线性表L已存在。

操作结果:将L置空

4 isemptylist l
4. IsEmptyList(L)

判断L是否为空表。如为空返回true,否则返回faslse

前提:L已存在,

5 listlength l
5. ListLength(L)

求线性表L中的结点个数,即求表长。

6 locate l e
6. Locate(L,e)

确定元素e在线性表L中的位置。(与书上不同)

在L中查找值为x 的结点,并返回该结点在L中的位置。若L中有多个结点的值和x 相同,则返回首次找到的结点位置;若L中没有结点的值为x ,则返回一个特殊值表示查找失败

7 getdata l i
7. GetData(L,i)

操作前提:表L已存在,且i值 合法,返回线性表L中第i个元素的值。

8 inslist l i e
8. InsList(L,i,e)

在线性表L的第i个位置上插入一个元素e,i+1,…,n的结点变为编号为i+1,i+2,…,n+1的结点。这里1≤i≤n+1,而n是原表L的长度。插入后,表L的长度加1。

9 deletelist l i e
9. DeleteList(L,i,e)

删除线性表L的第i个结点,使得原编号为i+1,i+2,…,n的结点变成编号为i,i+1,…,n-1的结点。这里1≤i≤n,而n是原表L的长度。删除后表L的长度减1。并且用e返回这个删除元素的值。

长度减1.

}ADT LinearList

slide18
说明

这些操作只是从逻辑结构的角度来讨论的,主要用来说明这些运算的功能,是“做什么”,至于如何实现,则要等到讨论存储结构后才考虑。

slide19
2.2 线性表的顺序存储结构

概述

顺序表

顺序表的算法实现

2 2 1
2.2.1. 概述

简单来说了, 本节就是讨论一维数组的使用。这在C语言中已经详细讨论过。

2 2 2
2.2.2. 顺序表

1.定义

2.存储地址公式

3.特点

2 2 11
2.2.1顺序表

顺序存储方法即把线性表的结点按逻辑次序依次存放在一组地址连续的存储单元里的方法

(2) 顺序表(Sequential List)用顺序存储方法存储的线性表简称为顺序表(Sequential List)。

slide23
内存表示

a1,a2,a3,...,ai,...,an

每个结点占C个存储单元。

2 a i
2. 结点ai的存储地址

设线性表中所有结点的类型相同,则每个结点所占用存储空间大小亦相同。假设表中每个结点占用c个存储单元,其中第一个单元的存储地址则是该结点的存储地址,并设表中开始结点a1的存储地址(简称为基地址)是LOC(a1),那么结点ai的存储地址LOC(ai)可通过下式计算:

LOC(ai)= LOC(a1)+(i-1)*c   (1≤i≤n)

slide25
思考题

int a[10],

a[2]= a[3]+a[5];为什么我们在程序中引用数组元素时,没有应用此地址公式?

slide26
顺序表的特点

在顺序表中,每个结点ai的存储地址是该结点在表中的位置i的线性函数。只要知道基地址和每个结点的大小,就可在相同时间内求出任一结点的存储地址。是一种随机存取结构。

顺序表是用向量实现的线性表,向量的下标可以看作结点的相对地址。因此顺序表的的特点是逻辑上相邻的结点其物理位置亦相邻。

slide27
4. 存储结构

#define MaxSize 100

//表空间的大小可根据实际需要而定,这里假设为100

typedef struct StudInfo{

} STUDINFO; 

typedef int ElemType;

//DataType的类型可根据实际情况而定,这里假设为int

  typedef struct {      ElemType elem[MaxSize];//向量data用于存放表结点int last; //最后一个元素的下标     }SeqList;

elemtype
ElemType是什么?

根据实际需要,把某个数据类型定义为ElemType,而且针对不同的数据类型,在程序上机测试,要对程序做相应的修改。

index
序号与下标(index)关系

第1个元素的下标为0

第2个元素的下标为1

…..

第last+1个元素的下标为last

slide30
表的长度与last的关系

表的长度=last+1

2 2 3
2.2.3算法实现

定义了存储结构后,就可以讨论运算实现。

插入操作

删除操作

查找操作

slide32
1. 插入操作

插入运算的逻辑描述  线性表的插入运算是指在表的第i(1≤i≤n+1)个位置上,插入一个新结点x,使长度为n的线性表:           (a1,…,ai-1,ai,…an)变成长度为n+1的线性表:           (a1,…,ai-1,e,ai,…an)要考虑两种极端情况:

① 当线性表已满时,怎么办?

② 当插入位置不正确怎么办?

slide33
插入过程说明

在顺序表中,结点的物理顺序必须和结点的逻辑顺序保持一致,因此必须将表中位置为n ,n-1,…,i上的结点,依次后移到位置n+1,n,…,i+1上,空出第i个位置,然后在该位置上插入新结点x。仅当插入位置i=n+1时,才无须移动结点,直接将e插入表的末尾。

slide34
插入算法

#define OK 1

#define ERROR 0

void InsertList(SeqList *L,ElemType e,int i)

{//将新结点 x插入L所指的顺序表的第i个结点的位置

int j; if (i<1||i>L->last+2) {   printf(“position error”);//非法位置,退出运行

return ERROR;

}   if (L->last>=MaxSize)  {

printf(“Over flow\n”);

return ERROR;

}

slide35
for(k=L->last;k>=i-1;k--)       L->elem[k+1]=L->elem[k];

//结点后移L->elem[i-1]=e;      //插入xL->last++;        //表长加1return OK;    

}//插入算法结束

slide36
算法性能分析

最好情况,插入到表的最后,不需要移动元素,可以直接插入e。

最坏情况,插入到第1个位置,需要移动n次。

一般情况,移动次数与插入位置i有关。

slide37
平均情况

设n+1种等几率情况。

slide38
插入示例

假设在i=3位置插入一个35

slide39
算法分析

现把一个数x插入到第i个位置。假设表里已有n个结点。

slide40
分析

移动结点的次数由表长n和插入位置i决定

算法的时间主要花费在for循环中的结点后移语句上。该语句的执行次数是n-i+1。

当i=n+1:移动结点次数为0,即算法在最好时间复杂度是0(1)。

当i=1:移动结点次数为n,即算法在最坏情况下时间复杂度是0(n)。

那么平均来说时间复杂度为多少呢?

slide41
时间复杂度的推导过程

插入位置有1到n+1,假设机率均等,每个位置的可能性为1/(n+1)。

slide42
平均时间复杂度

EIS(n)=n/2

表长的一半。

所以算法的平均时间复度为O(n)。即与表中已有元素的个数 成正比。

slide43
2. 删除操作

删除操作的逻辑说明

删除操作的算法实现

删除操作的性能分析

slide44
删除运算的逻辑描述

线性表的删除运算是指将表的第i(1≤i≤n)个结点删去,使长度为n的线性表

(a1,…,ai-1,ai,ai+1,…,an)

变成长度为n-1的线性表

(a1,…,ai-1,ai+1,…,an)

注意:当要删除元素的位置i不在表长范围(即i<1或i>L->length)时,为非法位置,不能做正常的删除操作

slide45
顺序表删除操作过程

后面的元素向前移动过程

slide46
删除算法

int DelList(SeqList *L,int i, ElemType *e)

{ //从L所指的顺序表中删除第i个结点aiint k;    if(i<1||i>L->last+1) { printf(“删除位置不存在!”);

return ERROR;

}           

*e= L->elem[i-1];

for(k=i;k<=L->last;j++)           L->elem[k-1]=L->elem[k]; //结点前移L->last--;                //表长减小

return OK;

}

slide47
时间复杂度

Ede(n)=(n-1)/2

所以平均时间复杂度为O(n)

slide48
3. 查找操作

按序号查找

GetData(L,i)

return L.elem[i-1];

按内容查找

Locate(L,e)

locate l e
Locate(L,e)

int Locate(SeqList L,ElemType e)

{ int i=0;

while((i<=L.last) && (L.elem[i]!=e))

i++;

if(i<=L.last)

return i+1;

else

return -1;

}

slide51
例子 两个有序表的合并

LA和LB是两个顺序表,且按非递减有序排列。把它们合并成一个非递减有序排列。例如LA=(2,2,3),LB=(1,3,3,4),合并后为LC=(1,2,2,3,3,3,4)。

slide52
void merge(SeqList *La,SeqList *Lb,SeqList *Lc)

{

int i=0,j=0,k=0;

while(i<=La->last && j<=Lb->last)

if(La->elem[i]<=Lb->elem[j])

{

Lc->elem[k++]=La->elem[i++];

}

else

{

Lc->elem[k++]=Lb->elem[j++];

}

slide53
while(i<=La->last)

{

Lc->elem[k++]=La->elem[i];

}

while(j<=Lb->last)

{

Lc->elem[k++]= Lb->elem[j++];

}

Lc->last = La->last+Lb->last+1;

}

slide54
时间复杂度

O(La->last+Lb->last)

slide55
顺序表的优点

存储效率高,存储结构本身已表示数据的逻辑结构,无需额外的存储空间表示逻辑结构。

随机访问某个元素。

slide56
顺序表的缺点

插入和删除操作需要移动大量的结点。

存储空间分配是静态分配。表的大小要事先确定,这样会造成程序不能适应 实际情况的变化。

如果事先把表长确定太大,可能会浪费存储空间。

如果把表长确定得太小,可能造成数据溢出。

slide57
2.3 线性表的链式存储结构

顺序表的优点:随机存取结构,简单(用数组)方便。

顺序表的缺点:插入和删除需要移动大量的元素(n/2).

这了避免大量结点移动,我们采用了线性表的另一种结构:链式存储结构

slide58
链式存储结构

简称为链表,它不仅可以用来表示线性结构,也可以表示非线性结构。

是最常见的存储结构之一。

链式存储结构的具体形式有多种:单链表,双链表,循环链表等。我们主要讨论单链表。

2 3 1
2.3.1单链表

链表的结构示意图。

现在一个元素包括二部分内容:一部分是数据本身,另一部分是下一个元素的地址。

slide60
链表结点的存储结构

data是数据域,存放数据信息本身

next是指针域(又称链域)存放下一个结构的地址。

线性表的每个结点就是通过指针域把n个逻辑上相邻的结点链接起来。

由于每个结点只有一个指针域,所以称单链表。

slide61
结点定义举例

Typedef struct Node

{

ElemType data;

struct Node *next;

} Node, *LinkList;

Node *,LinkList本质上是一样的,但是一般来说用LinkList表示整个链表,用Node *表示某个结点。

slide62
链表的表头结点

在链表操作时,经常需要判断第一个结点是否存在。这种判断既浪费运行时间,也增加编程的复杂性。为此,在建立链表时,先建立一个结点,放在链表最前面,这个结点不代表实际元素。在后续的链表处理过程中,这个表头结点永远存,给其他操作带来许多便利。

2 3 2
2.3.2单链表的基本操作

初始化链表

建立链表

往链表中插入一个结点

在链表删除一个结点

遍历链表

slide64
1. 初始化单链表

void InitList(LinkList *L)

{

*L=(LinkList ) malloc(sizeof(Node));

*L->next = NULL;

}

slide65
2. 建立单链表

头插入法

尾插入法

slide66
void CreateFromHead(LinkList L)

{ Node *s;

char c; int flag=1;

while(flag)

{ c=getchar();

if(c!=‘$’)

{ s = (Node *) malloc(sizeof(Node));

s->data= c;

s->next= L->next;

L->next =s;

}

else

flag =0;

}

slide67
尾插入法

新建立的结点插入到链表的末尾

slide68
void CreateFromTail(LinkList L)

{ Node *r,*s;

int flag =1;

r=L;

while(flag)

{ c = getchar();

if(c!=‘$’)

{ s=(Node *) malloc(sizeof(Node));

s->data =c;

r->next=s;

r=s;

}

else

{ flag =0;

r->next =NULL;

}

}

slide69
3.查找

按序号查找

按值查找

slide70
求序号查找

//L是链表,i是元素的序号,1≤i ≤n,如果找到,返回该位置的结点,仔细分析,如果i=0,会怎么样?

Node *GetNode(LinkList L,int i)

{ int j;

Node *p;

if(i<=0) return NULL;

p=L;j=0;

while((p->next!=NULL) &&(j<i)

{ p=p->next;

j++;

}

if(i==j)

return p;

else //什么情况出现这种情况

return NULL;

}

slide71
按值查找

Node *Locate(LinkList L,ElemType key)

{

Node *p;

p=L->next;

while(p!=NULL)

if(p->data!=key)

p=p->next;

else

break;

return p; //思考题,找不到时,p为什么值?

}

slide72
4. 求单链表的长度

int ListLength(LinkList L)

{ Node *p;

p=L->next;

j=0;

while(p)

{ p=p->next;

j++;

}

return j;

}

slide73
单链表插入操作

问题说明:在单链表中第i个位置插入一个数据元素e。

操作的三个步骤:找到i-1元素的指针;

申请新结点;插入链表。

slide74

e1

e2

e5

e4

e3

ex

^

slide75
算法

int InsList(LinkList L,int i,ElemType e)

{ Node *pre,*s;

int k;

if(i<1) return ERROR;

pre=L; k=0;

while(pre!=NULL && k<i-1)

{ pre =pre->next;

k++;

}//这里最好使用前面的GetNode函数,同学们练习

slide76
if(!pre)

{ printf(“can’t find the position”);

return ERROR;

}

s=(Node * ) malloc(sizeof(Node));

s->data =e;

s->next = pre->next;

pre->next =s;

return OK;

}

slide78
int DelList(LinkList L,int i,ElemType *e)

{

Node *pre,*r;

int k;

pre = L; k=0;

while(pre->next!=NULL && k<i-1)

{ pre=pre->next;

k++;

}

slide79
if(pre->next==NULL)

{ printf(“the node to be delete doesn’t exist”);

return ERROR;

}

r=pre->next;

pre->next = r->nxt;

e=r->data;

free(r );

return OK;

}

slide81
LinkList MergeLinkList(LinkList La,LinkList Lb)

{ Node *pa,*pb;

LinkList Lc;

pa=La->next; pb=Lb->next;

Lc = La;

Lc->next=NULL; r=Lc;

while(pa!=NULL && pb!=NULL)

{ if(pa->data<=pb->data)

{ r->next =pa ; r=pa; pa=pa->next;}

else

{ r->next=pb; r=pb; pb=pb->next;}

slide82
if(pa)

r->next =pa;

if(pb)

r->nex=pb;

free(Lb);

return Lc;

}

slide83
循环链表

循环链表是指头尾相连的链表,其实现算法与单链表类似,但是判断当前结点p是否为尾结点不一样,p!=NULL,或 p->next!=NULL

对于循环链表

p!=head,或p->next!= head

slide84
循环链表的尾指针

经常在尾结点设置链表指针。

请大家讨论

slide85
把两个循环链表合并成一个

LinkList merge_l(LinkList La,LinkList Lb)

{ Node *p,*q;

p=La; q=Lb;

while(p->next!=La) p=p->next;

while(q->next!=Lb) q=q->next;

q->next = La;

p->next =Lb->next;

free(Lb);

return Lb;

}

slide86
在尾结点设置指针

一般循环链表在尾结点设立一个指针,重做上面的这个例子。

slide87
例2-3 循环链表的合并

有两个带表头的循环链表La,Lb,将它们合并为一个循环链表,其头指针为La。

slide88
LinkList merge_2(LinkList Ra,LinkList Rb)

{ Node *p;

p=Ra->next;

Ra->next=Rb->next->next;

free(Rb->next);

Rb->next=p;

return Rb;

}

slide89
LinkList merge_1(LinkList La,LinkList Lb)

{ Node *p,*q;

p=La; q= Lb;

while(p->next!=La) p=p->next;

while(q->next!=Lb) q=q->next;

q->next =La;

p->next = Lb->next;

free(Lb);

Return La;

}

slide90
双向链表

prior

prior

prior

prior

data

data

data

data

next

next

next

next

slide91
双向链表

typedef struct DNode

{ ElemType data;

struct DNode *prior,*next;

} DNode,*DoubleList;

slide92
带表头的双向链表

prior

prior

prior

data

data

data

next

next

next

slide93
双向链表的优点

既可以向前找,又可以向后找,因此查找前驱结点非常方便。

某个结点为p

p->prior->next == p;

p->next->prior == p;

slide94
双向链表的插入操作

在p结点之前插入一个结点s

s->data=e;

s->prior = p->prior;

p->prior->next =s;

s->next =p;

p->next =s;

slide95
双向链表的删除操作

删除p结点

p->prior->next = p->next;

p->next->prior = p->prior;

free(p);

slide97
线性表的应用

一元多项式的运算

slide99
存储结构

struct Polynode

{ double coef;

int exp;

PolyNode *next;

} PolyNode,*PolyList;

slide100
1.多项式链表的建立

Polylist Polycreate()

{ Polynode *head,*rear,*s;

head = (Polynode *) malloc(sizeof(Polynode));

rear =head;

scanf(“%d,%d”,&c,&e);

while(c!=0)

{ s = (Polynode *) malloc(sizeof(Polynode));

s->coef =c; s->exp =e;

rear->next=s; rear=s;

scanf(“%d,%d”,&c,&e);

}

rear->next =NULL;

return head;

}

slide101
两个多项式相加

void Polyadd(Polylist pa,PolyList pb)

{ Polynode *p,*q,*tail,*temp;

int sum;

p=pa->next;

q=pb->next;

tail=pa;

slide102
while(p!=NULL && q!=NULL)

{ if(p->exp<q->exp)

{ tail->next=p; tail =p; p=p->next;}

else if(p->exp==q->exp)

{ sum =p->coef+q->coef;

if(sum!=0)

{ p->coef =sum;

tail->next=p; tail=p;

p=p->next;

temp=q; q=q->next; free(temp);

}

slide103
else

{

temp=p; p=p->next; free(temp);

temp=q;q=q->next; free(temp);

}

}

else

{ tail->next = q; tail=q;q=q->next;}

}

slide104
if(p!=NULL)

tail->next =p;

else

tail->next =q;

}

slide106
分配一个结点

ListNode *p;//悬挂状态

p=(ListNode*)malloc(sizeof(ListNode));

slide108
链表置逆

void ReverseLink(LinkList L)

{ p=L->next;

L->next=NULL;

while(p)

{ q=p->next;

p->next=L->next; L->next =p;

p=q;

}

}

slide109
二进制加1

二进制用链表表示,每一个结点代表一位,第一个结点代表最高位,依次存放,最后一个结点。

slide110
二进制加1

void BinAdd(LinkList L)

{ Node *q,*r,*temp,*s;

q=L->next; r=L;

while(q!=NULL)

{ if(q->data==0)

r=q;

q=q->next ;

}

slide111

If( r!=L)

r->data=1;

Else

{ temp=r->next;

s=(Node *) malloc(sizeof(Node));

s->data =1 ;s->next =temp;r->next =s;

r=s;

}

r=r->next;

slide112

while(r!=NULL)

{ r->data=0;

r=r->next;

}

}