第 2 章 线性表

75
第2第 第第第 1 第2第 第第第 2.1 第第第第第第第第 2.2 第第第第第第第第 2.3 第第第第第第第第第第 2.4 第第第第第第第第第第

Upload: may

Post on 13-Jan-2016

95 views

Category:

Documents


6 download

DESCRIPTION

第 2 章 线性表. 2.1 线性表的基本概念 2.2 线性表的顺序存储 2.3 线性表的链式存储结构 2.4 一元多项式的表示及相加. 2.1 线性表的类型定义. 从本章开始到第四章讨论的线性表、栈、队列和串的逻辑结构都属于线性结构。在线性结构中,元素之间存在一个对一个的相互关系,其逻辑特征为:在数据元素的 非空有限 集中: ( 1 )存在唯一的一个被称为“起始”的数据元素。 ( 2 )存在唯一的一个被称为“终端”的元素。. ( 3 )除起始元素外,其它每个元素有且仅有一个直接前趋。 - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第 2 章  线性表

第 2 章 线性表

1

第 2 章 线性表

• 2.1 线性表的基本概念

• 2.2 线性表的顺序存储

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

• 2.4 一元多项式的表示及相加

Page 2: 第 2 章  线性表

第 2 章 线性表

2

从本章开始到第四章讨论的线性表、栈、队列和串的逻辑结构都属于线性结构。在线性结构中,元素之间存在一个对一个的相互关系,其逻辑特征为:在数据元素的非空有限集中: ( 1 )存在唯一的一个被称为“起始”的数据元素。 ( 2 )存在唯一的一个被称为“终端”的元素。

2.1 线性表的类型定义

Page 3: 第 2 章  线性表

第 2 章 线性表

3

( 3 )除起始元素外,其它每个元素有且仅有一个直接前趋。 ( 4 )除终端元素之外,其它每个元素有且仅有一个直接后继。 本章的基本内容:线性表的逻辑结构定义和各种存储结构、描述方法及其建立在这两种存储结构上的运算实现。

Page 4: 第 2 章  线性表

第 2 章 线性表

4

2.1.1 线性表的逻辑结构

在实际应用中,线性表是最常用而且最简单的一种数据结构。

线性表定义:线性表是由 n 个数据元素组成的有限序列,其中, n 即数据元素的个数,定义为线性表的长度,当 n 为零时称为空表,一般将 n>0 时的线性表记为:

其中, 是第一个数据元素,又称为起始结点, 是最后一个数据元素,又称为终端结点。

1 2( , , , , )i na a a a

1a na

Page 5: 第 2 章  线性表

第 2 章 线性表

5

当 i=1 , 2 ,…, n-1 时, 有且仅有一个直接后继 ;当 i =2 , 3 ,… n 时, 有且仅有一个直接前趋 。注意这里 ( 1≤ i≤

n )仅仅是一个抽象的符号,其具体含义,在不同的情况下各不相同,它可以是一个数,一条记录或一个符号,甚至是更复杂的信息。例如,英文字母表 (A , B ,…… Z) 是一个线性表,职工工资表等。

线性表的结点之间的逻辑关系符合线性结构的逻辑特征,是一种线性结构。

ia1ia

ia1ia

ia

Page 6: 第 2 章  线性表

第 2 章 线性表

6

线性表的特点:

(1)同一线性表中的元素必定具有相同特性;

(2) 相邻数据元素之间存在着序偶关系;

(3) 线性表中元素个数 n(n>=0) 为线性表的长度 , 当n=0 时称为空表;

(4)在非空线性表中,每个数据元素都有一个确定的位置;

(5)线性表的长度可以根据需要增长或缩短。

Page 7: 第 2 章  线性表

第 2 章 线性表

7

抽象数据类型线性表的定义格式

ADT list {

数据对象:

数据关系:

基本操作:

以下是一些常用操作,以函数方式出现

… …

} ADT list

}0,,...,2,1,|{ nniElemSetaaD ii

},...,2,1,,|,{1 11 niDaaaaR iiii

ElemSet 是某个确定的、将由用户自行定义的、含某个关系运算的数据对象

Page 8: 第 2 章  线性表

第 2 章 线性表

8

常见的线性表的基本运算: ( 1 )置空表 ClearList(L) :将线性表 L 置成空表。 ( 2 )求长度 ListLenght(L) :求给定线性表 L 中数

据元素的个数。 ( 3 )取元素 GetElem(L,i,&e) :用 e 返回线性表 L

中的第 i 个数据元素。 ( 4 )插入 ListInsert(&L,i, e) :在线性表 L 中第 i 个

位置之前插入新的元素 e,L 的长度加 1 。

2.1.2 线性表的运算

Page 9: 第 2 章  线性表

第 2 章 线性表

9

( 5 )删除 ListDelete (&L,i,&e) :删除 L 中第 i 个元素,并用 e 返回其值, L 的长度减 1 。

( 6 )判定 ListEmpty(L) :若 L 为空表,则返回true, 否则返回 false 。

利用基本运算可以实现线性表的其它复杂运算。 需要指出的是,这里给出的只是定义在逻辑结构上

的抽象运算,即只关心这些运算是“做什么”的,至于“怎样实现”则依赖于所选定的存储结构。

Page 10: 第 2 章  线性表

第 2 章 线性表

10

2.2 线性表的顺序表示和实现 顺序表是线性表的顺序存储结构,即按顺序存储

方式构成的线性表的存储结构。其存储方式是:线性表的第一个元素存放在个一片连续的存储空间开始位置处,第二个元素紧跟着存放在第一元素之后…,以此类推。

利用顺序表来存储线性表,可借助数据元素在计算机内的物理位置相邻特性来表示线性表中数据元素之间的线性逻辑关系。

线性表中第 i 个数据元素 的存储位置计算公式为:

L 是每个元素占用的存储单元

liaLOCaLOC i )1()()( 1

ia

Page 11: 第 2 章  线性表

第 2 章 线性表

11

这样,一旦起始地址 LOC ( a1 )(图 2.2 中设为 b )和一个数据元素占用的存储单元的大小( L

值)确定下来,就可求出任一个数据元素的存储地址,显然,这种表便于进行随机访问,因此,顺序表是一种随机存取的结构。

Page 12: 第 2 章  线性表

第 2 章 线性表

12

Page 13: 第 2 章  线性表

第 2 章 线性表

13

在具体实现时,可利用高级程序设计语言中的一维数组即向量来为线性表的顺序存储开辟存储空间,存储空间大小应大于等于线性表长度,用一个整型变量来表示线性表的长度,从而可将顺序表描述为: # define List_INIT 100 typedef int elemtype ; /* elemtype 表示元素类型,此处设为 int */ typedef struct {elemtype vec[List_INIT] ; int lenght ; }sequenlist ;

Page 14: 第 2 章  线性表

第 2 章 线性表

14

在上面描述中,顺序表是一个结构体类型。其中, vec 成员(又称为数据域)指线性表数据元素所占用的存储空间,而 lenght 成员(又称为长度域)则用于存储线性表长度,它表示线性表最后一个元素在向量中的位置, elemtype 则为线性表中数据元素类型。

Page 15: 第 2 章  线性表

第 2 章 线性表

15

本节讨论在定义线性表顺序存储结构之后,如何在这种结构上实现有关数据运算。下面重点讨论线性表的顺序表的建立、数据元素的插入和删除运算。

2.2.2 顺序表上的基本运算

Page 16: 第 2 章  线性表

第 2 章 线性表

16

3. 顺序表的常用算法

算法 1 顺序表的建立 (P23 算法2.3)输入 n 个整数,产生一个存储这些整数的顺序表 A 的函数如下:

void create(A,n)

vector A;

int n;

{

int i;

for (i=1;i<=n;i++)

cin>>A[i];

/* scanf(“%d”,A[i]); */

}

typedef int vector [ m ] 定义了一个新的类型,顺序表中 n 小于或等于 m

main()

{ vector B;

int j,n;

cin>>n;

create(B,n);

for (j=1;j<=n;j++)

cout<<B[j];

/*Printf(“%d”,B[j] ); */

}

Page 17: 第 2 章  线性表

第 2 章 线性表

17

2.插入运算 线性表的插入运算是指在表的第 i 个元

素之前插入一个新的数据元素,插入的结果使得线性表的长度由 n变为 n+1 ,线性表的数据元素间的逻辑关系发生了变化,使得物理存储顺序也发生相应的变化。插入过程如图 2.3 所示。

Page 18: 第 2 章  线性表

第 2 章 线性表

18

12

13

21

24

28

30

42

77

1

2

3

4

5

6

7

8

插入25

12

13

21

24

25

28

30

42

77

1

2

3

4

5

6

7

8

9

插入后

(a) (b)

Page 19: 第 2 章  线性表

第 2 章 线性表

19

算法 2 顺序表的插入 (如图 2.3 )

12

13

21

24

28

30

42

77

1

2

3

4

5

6

7

8

插入25

在一个有 n 个元素的顺序表 A 中的第 i 个元素之前插入一个元素 x 的函数如下:

void insert(A,n,x)

vector A;

int n,x;

{

int i,j;

scanf(“%d”,&i); /* 确定插入位置 */

if(i<1 || i>n) print(“i 值错误! \n”);

else

{ for (j=n;j>=i;j--) A[j+1]=A[j];

A[i]=x;

n++;

} }

Page 20: 第 2 章  线性表

第 2 章 线性表

20

int Insert(Elemtype List[ ],int *num,int i,Elemtype x)

{/* 在顺序表 List[ ] 中, *num 为表尾元素下标位置,在第 i 个元素前插入数据元素 x ,若成功,返回 TRUE ,否则返回 FALSE 。 */

int j;

if (i<0||i>*num+1)

{printf(“Error!”); /* 插入位置出错 */

return FALSE;}

if (*num>=MAXNUM-1)

{printf(“overflow!”);

return FALSE;} /* 表已满 */

for (j=*num;j>=i;j--)

List[j+1]=List[j]; /* 数据元素后移 */

List[i]=x; /* 插入 x*/

(*num)++; /* 长度加 1*/

return TRUE;}

P24【算法 2.4 顺序表的插入】

Page 21: 第 2 章  线性表

第 2 章 线性表

21

在本算法中是从最后一个元素开始后移,直到第 i 个元素为止。该算法时间主要花费在 for循环语句上,执行的次数为 n-i+1 。当 i=1 时,全部元素均参加移动,共需要移动 n次;当 i = n+1 时,不需移动元素。

算法在最坏情况下,时间复杂度为 O(n) ,最好情况下时间复杂度为O(1) 。显然,元素移动的次数直接影响了算法执行时间,平均移动次数。 假设 pi 为在第 i 个元素之前插入一个元素的概率,且为等概率,则平均移动次数的期望值为:

其中,当 1≤i≤n+1 时, p1=p2=… …=pn+1 =

可见,在顺序存储的线性表中插入一个元素,约平均移动线性表中一半的元素,显然,当 n较大时,算法效率较低,算法的平均时间复杂度为O(n) 。

1

1

1

1 2)1(

1

1)1(

n

i

n

iiis

nin

ninpE

1

1

n

Page 22: 第 2 章  线性表

第 2 章 线性表

22

在本算法中是从最后一个元素开始后移,直到第 i 个元素为止。该算法时间主要花费在 for循环语句上,执行的次数为 n-i+1 。当 i=1 时,全部元素均参加移动,共需要移动 n次;当 i = n+1 时,不需移动元素。

算法在最坏情况下,时间复杂度为 O(n) ,最好情况下时间复杂度为 O(1) 。显然,元素移动的次数直接影响了算法执行时间,平均移动次数。 假设 pi 为在第 i 个元素之前插入一个元素的概率,且为等概率,则平均移动次数的期望值为:

其中,当 1≤i≤n+1 时, p1=p2=… …=pn+1 =

1

1

1

1 2)1(

1

1)1(

n

i

n

iiis

nin

ninpE

1

1

n

Page 23: 第 2 章  线性表

第 2 章 线性表

23

可见,在顺序存储的线性表中插入一个元素,约平均移动线性表中一半的元素,显然,当 n较大时,算法效率较低,算法的平均时间复杂度为 O(n) 。

Page 24: 第 2 章  线性表

第 2 章 线性表

24

3.删除运算

删除运算是指将线性表中的第 i 个元素删除,使线性表的长度由 n变成 n-1 ,由: (a1 , a2 ,…, ai-1 , ai , ai+1 ,…, an)

变成为: (a1 , a2 ,…, ai-1 , ai+1 ,…, an)

其中, 1≤i≤n 。 线性表进行删除元素后,仍是一个线性表。删

除过程如图 2.4 所示。

Page 25: 第 2 章  线性表

第 2 章 线性表

25

算法 3 顺序表的删除 (如图 2.4 )具体算法如下:

void delete(L , i)

sequenlist *L ; int i ; { int j ; if ((i<1) ¦¦ (i>(*L).len+1))

printf(“delete fail ! \n”) ; /* 删除位置不正确 */

else

{ *y =(*L).vec[i-1] ; /*y 为一外部变量,用于接收被删除的元素*/

for(j= i ; j<=(*L).len ; j ++)

(*L).vec[j-1]=(*L).vec[j] ; /* 元素前移 */

(*L).Len-- ; /*修改表长 */

}

} /*delete*/

Page 26: 第 2 章  线性表

第 2 章 线性表

26

与插入算法相似,要删除一个元素需向前移动元素,删除第 i 个元素时,将第 i+1 , i+2 ,…, n 个元素依次前移,其移动次数为 n-i ,假设删除线性表中的任一位置上元素的概率相等(等于 1/n ),则删除运算所需的移动元素的平均移动次数如下所示。

由此可见,对线性表删除一个元素时,约需有一半的元素参加移动。

2

1)()(

1

1

1

1

ninin

nE

n

in

n

ids

Page 27: 第 2 章  线性表

第 2 章 线性表

27

显然,该算法的时间复杂度为O (n)。 通过以上讨论可以发现,当表长较大时,对顺

序存储结构进行插入和删除运算,由于要移动元素,运算效率低,但这种存储结构对于随机存取元素却十分方便。

Page 28: 第 2 章  线性表

第 2 章 线性表

28

例如,一个线性表中可能含有重复的元素(值相同),现要求删除重复元素中的多余元素,只保留其中位序最小的一个。如线性表( 5 , 2 , 2 , 3 , 5 , 2 ),经过清除重复元素后变为 ( 5 , 2 , 3 ) 。 假定线性表以顺序存储方式存储,则其算法可描述如下: void purge(sequenlist *L ) { int i=0 , j , k ; while ( i<=(*L).Len-1) { j = i+1 ; while ( j<=(*L).Len-1) if ((*L).vec[i]= =(*L).vec[j]) { for ( k =j ; k<=(*L). Len-1 ; k ++) (*L) .vec [k –1] = (*L) .vec [k ] ; / * 元素前移 */ (*L ) .Len- - ; /*修改表长度 */ } else j + + ; i + + ; } } /* purge */

Page 29: 第 2 章  线性表

第 2 章 线性表

29

算法 4 顺序表的查找 (P26 算法 2.6)

在一个有 n 个元素线性表 A 中查找值为 x 的元素的函数如下: void find(A[ ],n,x)

int n.x;

{

int j;

i=1;

while (i<=n && A[i] !=x ) i++;

if (i<=n)

printf(“找到了! \n”);

else

printf (“没找到 \n”);

}

Page 30: 第 2 章  线性表

第 2 章 线性表

30

算法 5: 顺序表的合并 (P26 算法 2.7)

有两个顺序表 A( 有 m 个元素 ) 和 B( 有 n 个元素 ), 其元素均以从小到大的升序排列 ,编写一个函数将它们合并成一个顺序表 C, 要求 C 的元素也是从小到大的升序排列 .

解 : 本题的解法思想是 : 依次扫描 A 和 B 的元素 ,比较当前的元素的值 , 将较小值的元素赋给 C, 如此直到一个表扫描完毕 ,然后将未完的一个表余下所有元素赋给 C 即可 .

Page 31: 第 2 章  线性表

第 2 章 线性表

31

Void link(int A[ ],int B[ ];int m,n;int C[ ])

{ int i=1,j=1,k=1,s;

while (i<=m && j<=n) /*扫描 A 和 B, 将当前的较小元素赋给 C */

if A[i]<B[j])

{ C[k]=A[I]; i++; k++; }

else

{ C[k]=B[ j]; j++; k++; }

if(j==n) /* 当 A未完时 , 将 A余下的元素赋给 C */

for(s=i+1;s<=m;s++) { C[k]=A[s];k++; }

if (i==m) /* 当 B未完时 , 将 B余下元素赋给 C*/

for (s=j+1;s<=n; s++) { C[k]=B[s]; k++ ; }

}

算法 2.7C语言代码为 :

Page 32: 第 2 章  线性表

第 2 章 线性表

32

1. 基础知识:题集 2.1 ;

2. 算法实现:写出教材算法 2.4 、2.5 (线性表中元素的插入和删除算法)的 C/C++语言程序,并上机调试;

3. 算法设计题: 题集2.10 、 2.11 。

作 业

Page 33: 第 2 章  线性表

第 2 章 线性表

33

线性表顺序存储结构的特点: ( 1 )逻辑关系上相邻的两个元素在物理位置上也是相

邻的 ,因此可以随机存取表中任意元素 ( 可用地址公式 ).

( 2 ) 无需为表示数据元素之间的关系而增加额外存储空间;

( 3 )可以方便地随机存取表中任一元素。 ( 4 )必须预先为线性表分配空间,表容量难以扩充,

必须按线性表最大可能的长度分配空间,有可能造成存储空间浪费。

2.3 线性表的链式表示和实现

Page 34: 第 2 章  线性表

第 2 章 线性表

34

( 5 )插入和删除运算时必须移动大量元素,效率较低;

为避免大量元素的移动,本节讨论线性表的另一种存储结构,即链式存储结构,是最常用的存储方法之一,它不仅可以表示线性表,而且可以表示各种复杂的非线性数据结构。这种结构按不同的分类方法可分为单链表、循环链表和双向链表等。

Page 35: 第 2 章  线性表

第 2 章 线性表

35

线性表的链式存储结构特点是 : 逻辑关系上相邻的两个元素 , 在物理位置上不一定相邻 , 不能随机存取链中数据 .

2.3.1 线性链表

a. 一个结点——数据元素 ai 的存储映象。

ai

存入的数据 指向其后继

如:链表中存入数据 6,9, 3, 7

6 9 3 7 ^Head(头指针 )

Page 36: 第 2 章  线性表

第 2 章 线性表

36

Page 37: 第 2 章  线性表

第 2 章 线性表

37

这样,利用每个结点的指针域就可以将 n 个结点按其逻辑次序连成一个链表,这种链表中每个结点只含一个指针域,故称为线性链表或单链表。例如,线性表( red , orange , white , yellow , green ,blue ),其单链表的存储方式如图 2.5 所示。 由于单链表中第一个结点无直接前趋,可另设一个头指针 head 存储第一个结点的地址。同样,最后一个结点无直接后继,其指针域为空值即为 NULL ,有时用 ^ 表示。整个单链表可由头指针唯一地确定。单链表是一种非随机存取的存储结构。

Page 38: 第 2 章  线性表

第 2 章 线性表

38

也可以将图 2.5 中的单链表直观地画成如图 2.6

所示,其中箭头用来表示链域中的指针。

Page 39: 第 2 章  线性表

第 2 章 线性表

39

b. 结构指针定义 单链表可以用 C语言中的指针数据类型实现,描述如下: typedef int elemtype ; typedef struct node /* 结点类型定义 */ { elemtype data ; /* 数据域 */ struct node *next ; /* 定义指针域 */ } linklist ; linklist * head , *p ; /* head , p 为单链表指针类型 */ 另外,利用 C语言中的指针数据类型要注意指针变量和结点变量,其关系如图 2.7 所示。

Page 40: 第 2 章  线性表

第 2 章 线性表

40

单链表上的基本运算 为了便于实现各种运算,常在单链表的第一个结点之前增设一个附加的结点,称为头结点,其它的结点称为表结点。本章若无特殊说明,均采用带头结点的单链表,如图 2.8 的 (a) , (b) 所示。

Page 41: 第 2 章  线性表

第 2 章 线性表

41

1. 建立一个单链表 ( 1 ) 算法 2.8 C语言程序

输入一系列整数,以 0标志结束,将这些整数作为 data域建立一个单链表的函数:

void crea()

{ node *head,*p,*s;

int x,cycle=1; /*cycle 是循环变量 */

head=(node *) malloc(sizeof(node)); /* 建立头结点,由 dead 所指向 */

p=head;

while (cycle)

{ scanf(“%d”,&x);

if (x!=0)

{ s=(node *) malloc(sizef(node)); /* 建立下一个结点,由 s 所指向 */

Page 42: 第 2 章  线性表

第 2 章 线性表

42

s->data=x; p->next=s; p=s; } /* 把 s 结点链接到前面建立的单链表中 */

else cycle=0;

}

head=head->next; /* 删除头结点 */

p->next=NULL;

}

如果输入的整数序列是:

6 10 3 6 7 5 0

Page 43: 第 2 章  线性表

第 2 章 线性表

43

linklist * creatlist ( ) /* 函数返回一个指向单链表表头的指针 */ { char ch ; int x ; linklist *head , *r , *p ; p = ( linklist * ) malloc ( sizeof ( linklist )); head = p ; p->next =NULL ; /* 生成头结点 */ r = p ; /* 建立单链表表尾指针 */ ch = getchar ( ) ; /*ch 为建立与否标志 */ while ( ch ! = ‘ ? ’ ) /*‘? ’ 为输入结束标志符 */ { scanf ( “ % d ” , &x ) ; /*读入新数据元素 */ p = ( linklist * ) malloc (sizeof (linklist )) ; p->data = x ; p ->next = NULL ; /*生成新结点 */ r->next = p ; /* 连接新结点 */

r = r->next ; /*修改尾指针 */ ch = getchar ( ) ; /*读入建立与否的标志 */ } return ( head ) ; /* 返回表头指针 head */ } /* creatlist */

( 2 )建立带头结点的单链表

Page 44: 第 2 章  线性表

第 2 章 线性表

44

( 3 )建立不带头结点的单链表 linklist * creatlist ( ) /* 函数返回一个指向链表表头的指针 */ {char ch ; int x ; linklist *head , *p , *r head =NULL ; r =NULL ; /* 设立尾指针 r ,其初值为空 */ ch = getchar ( ) ; /*读入建立与否标志 */ while ( ch != ‘ ? ’ ) /*‘ ? ’ 为输入结束标志符 */ { p=(linklist *)malloc(sizeof(linklist)) ; /*申请新结点空间 */ scanf ( “ % d ” , &x ) ; p->data = x ; if (head = = NULL) head = p ; else r->next = p ; /* 非空表,则新结点 *p 插入到 *r 之后 */ r = p ; /*尾指针移动,指向新的表尾 */ ch = getchar ( ) ; /* 读入建立与否的标志 */ } if ( r! = NULL ) r->next = NULL ; return head ; /* 返回表头指针 head */ } /* creatlist */

在算法中引入头结点可以不必区分空表与非空表,可以统一空链表与非空链表的处理。 上述两个算法的时间复杂度都为 O(n) 。

Page 45: 第 2 章  线性表

第 2 章 线性表

45

插入结点的指针变化图 2.9 所示。指针的修改操作为:① s->next=p->next ; ② p->next=s ; 上述指针进行相互赋值的语句顺序不能颠倒,若次序变化为:① p->next=s ; ② s->next=p->next ;则会导致错误。 同样,要删除单链表结点 *p 的后继结点也很简单,只要用一个指针指向被删除结点,修改 *p 的指针域,最后释放结点 *p 即可,如图 2.10 所示。删除一个结点 * p 的后继结点的指针操作为: p->next = p->next ->next ; 不难发现,在单链表存储结构中进行元素的插入和删除时,只需要修改有关的指针内容,而不需要移动元素。

2.单链表上元素的插入和删除运算

Page 46: 第 2 章  线性表

第 2 章 线性表

46

Page 47: 第 2 章  线性表

第 2 章 线性表

47

( a ) insert (linklist *p ,elemtype x) /* 将值为 x 的新结点插入 *p 之后 */

{ linklist *s ; s = ( linklist * ) malloc ( sizeof( linklist)) ; /* 生成 x 的新结点 *s */ s->data=x ; s->next=p->next ; /* 新结点链入单链表 */ p->next=s ; } /*insert*/

( 1 ) 插入算法

Page 48: 第 2 章  线性表

第 2 章 线性表

48

( b ) void insert (linklist *head,elemtype k,elemtype x) /* 在单链表中值为 k 的结点之前插入一个值为 x 的新结点 */ { linklist *q , *p , *r ; r =(linklist *)malloc( sizeof( linklist)) ; / *生成新结点 */ r->data = x ; if ( head->next = =NULL) { head->next = r ; r->next = NULL ; } else { q = head->next ; p = head->next->next ; while ( p ! = NULL ) { if ( p->data ! = k ) */ { q = p ; p = p->next ; } else break ;

Page 49: 第 2 章  线性表

第 2 章 线性表

49

if ( p ! = NULL ) { q ->next = r ; r->next = p ; } else /* 在链表表尾处插入新结点 */ { q ->next = r ; r->next =NULL ; } } } /* insert */ 该算法的时间主要耗费在查找值为 k 的结点位置上,算法时间复杂度为 O(n) 。

Page 50: 第 2 章  线性表

第 2 章 线性表

50

算法 2.9 单链表的插入( C语言函数)

在单链表中第 i 个结点( i>=0) 之后插入一个元素为 x 的结点。

void inser(head,i)

node *head;

int i,x;

{ node *s,*p;

int j;

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

/* 建立一个待插入的结点 s*/

scanf(“%s”,&x); s->data=x;

if (i==0) /* 如果 i=0 ,则将 s 所指结点插入到表头后返回 */

{ s->next=head;

head=s;}

else

{ p=head; j=i;

/* 在单链表中查找第 i 个结点,由p 指向 */

while (p!=NULL && j<i)

{ j++; p=p->next;}

if(p!=NULL) /* 若找查成功 , 则把s 插入到其后 */

{ s->next=p->next;

p->next=s;}

else

printf(“未找到! \n”); } }

Page 51: 第 2 章  线性表

第 2 章 线性表

51

( a ) delete (linklist *p ) /* 删除 *p 结点的后继结点 */ { linklist *r ; r = p->next ; if(r!=NULL) {p->next = r->next ; free (r) ; } } /*delete*/( b ) linklist *delete(linklist *head , int i) /* 删除单链表 head 的第 i 个结点 */ { int j = 0 ; linklist *p , *s , *q ; p = head ; j = 0 ; while (( p->next != NULL)&& (j< i-1)} { p = p->next ; j ++ ; }

( 2 )删除算法

Page 52: 第 2 章  线性表

第 2 章 线性表

52

if ( p->next!=NULL ) { q = p->next ; p->next = p->next->next ; free (q) ; } else return NULL ; s = head ; return s ; } /* delete */ 该算法时间复杂度为 O(n) 。

Page 53: 第 2 章  线性表

第 2 章 线性表

53

一般情况下,可以按某个元素的序号或按某给定值两种方法检索。 ( 1 )按值检索

按值检索,即是检索单链表中是否存在值为给定值 k 的结点,整个过程可以通过结点的值和给定值的比较而实现。 linklist *locate ( linklist *head , elemtype k ) { linklist *s ; s= head->next ; /* 从第一个结点起开始比较 */ while ( s!= NULL ) /*扫描整个链表 */ if ( s->data != k)

s = s->next ; else

break ; return s ; /* 返回结点的位置或 NULL*/ } /*Locate*/

算法时间复杂度为 O(n) 。

3. 单链表上元素的检索

Page 54: 第 2 章  线性表

第 2 章 线性表

54

即利用被访问结点的序号而检索或查找结点的过程。

linklist *get(linklist *head , int i) { int j ; linklist *p ; p = head ; j = 0 ; while ( (p->next != NULL )&&( i > j )) {p = p->next ; j ++ ; } if (i= =j) return p ; else return NULL ; } /*get */

该算法中最坏情况下的时间复杂度为 O(n) 。

( 2 ) 按序号检索

Page 55: 第 2 章  线性表

第 2 章 线性表

55

例,将两个递增的单链表合并为一个递增单链表,且要求不重新开辟空间,其算法可描述如下:

void *union (linklist *heada , linklist *headb ) /* 合并单链表 heada 与 headb */ { linklist *p , *q , *r , *u ; p = heada->next ; q = headb->next ; r = heada ; while ( p ! = NULL )&& (q ! = NULL ) { if ( p->data > q->data ; { u = q->next ; r->next = q ; r = q ; q->next = p ; q = u ; }

Page 56: 第 2 章  线性表

第 2 章 线性表

56

else if ( p->data < = q->data ) { r = p ; p = p->next ; } } if ( q ! = NULL) r->next = q ; } /* union */

Page 57: 第 2 章  线性表

第 2 章 线性表

57

1. 基础知识:题集2.1 、 2.4 、 2.5 、 2.6 、 2.7 ;

2. 算法实现:写出教材算法2.10 单链表的删除 C/C++语言程序,并上机调试通过 ;

2. 算法设计题 : 2.15 、 2.16 。

作 业

Page 58: 第 2 章  线性表

第 2 章 线性表

58

如果将单链表最后一个结点的指针域改为存放链表中头结点(或第一个表结点)的地址值,就使得整个链表构成一个环,如图 2.12 所示,这样的链表称为循环链表。显然,它是一种首尾相接的链表,从循环链表中任一个结点出发都可访问到表中所有其它结点。对单链表的链接方式稍作一些改变,就可构成一个新的存储结构——单循环链表。同样,也有多重链的循环链表。在循环链表中,为了使空表和非空表的处理一致,同样可设置一个头结点。

2.3.2 循环链表

Page 59: 第 2 章  线性表

第 2 章 线性表

59

循环链表的基本运算与单链表基本一致 ,差别只在于最后一个结点的循环处理上。只要将单链表算法中出现 NULL 处改为头指针即可。 例如,将单循环链表倒置或逆置。 linklist *invert (head ) linklist *head ; {linklist *p , *q , *s; q = head ; p= head->next ; while ( p ! = head ) { s = q ; q = p ; p = p->next ; q->next = s ; } p->next = q ; return head ; } /* invert */

Page 60: 第 2 章  线性表

第 2 章 线性表

60

在单循环链表中,从任一个已知结点出发可以找到其前趋结点,但时间耗费需 O(n ),若希望能快速确定一个结点的直接前趋,可以再加上一个指针域存储其直接前趋的信息,

这样就可以构成双向链表,它有两个方向不同的链,如果将每个方向的链表构成一个环,则可以构成双向循环链表。 双向链表的 C语言描述:

typedef struct dupnode { elemtype data ; struct dupnode *next, *prior ; } dulinklist ; dulinklist *head ;

双向循环链表也可由头指针 head 唯一确定,同样可增加头结点,使得双链表的某些运算简便一些,如图 2.14 所示。

2.3.3 双向链表

Page 61: 第 2 章  线性表

第 2 章 线性表

61

Page 62: 第 2 章  线性表

第 2 章 线性表

62

由于双向链表在其结构中,每个结点既有指向其直接前趋的指针域,又有指向其直接后继的指针域,因此比起单链表来,要在一个双向链表中检索某一个已知结点的直接前趋和后继结点要方便得多。

结点 *p 可通过多种方式访问,设指针 p 指向双向链表中某个结点,则有: p->next->prior = p = p->prior->next

双向链表中结点的插入情况如图 2.16 所示。

Page 63: 第 2 章  线性表

第 2 章 线性表

63

在 *p 结点之前插入结点 *s 的正确步骤应为: ① p->prior->next = s ;② s->prior = p->prior ;③ s->next = p ;④ p->prior = s ; 由于双向链表中每个结点涉及两个指针的运算,对于某些运算要注意运算顺序。 在上面的步骤中指针 p->prior 的修改必须在 *p 结点的前趋结点 *(p->prior) 的后继指针修改之后方可改动,否则会不能正确地插入结点。 在双向链表中删除一个结点 *p 的指针变化如图 2.15 所示。指针修改的运算步骤为: ① p->prior->next= p->next ; ② p->next->prior =p->prior ;

Page 64: 第 2 章  线性表

第 2 章 线性表

64

void indulist (dulinklist *head ,int i, elemtype x)/* 在双向循环链表 head 中的第 i 个结点之前插入值为 x 的新结点 */ { dulinklist *p , *s ;int j ; p = head ; j = 0 ; while (( p->next! = head)&&(j < i-1)) {p = p->next ; j++ ; } if (( i >0)&&(j= =i-1)) { s = malloc (sizeof(dulinklist)) ; s->data = x ; s->next = p->next ; s->prior = p ; p->next = s ; s->next->prior = s ; } else printf ( “error ! \n” ) /* indulist */

1.插入算法(算法 2.18 )

Page 65: 第 2 章  线性表

第 2 章 线性表

65

void deledulist (dulinklist *head,int i) /* 删除双向链表 head 中的第 i 个结点 */ { dulinklist *p ; int j ; p = head ; j = 0 ; while ((p->next! = head)&&(j < i)) { p = p->next ; j ++ ; } if (( i > 0)&&(j = = i)) {p->prior->next = p->next ; p->next->prior = p->prior ; free(p) ; } else printf (“error\n”); } /*deledulist*/ 以上两个算法的时间复杂度都为 O(n) 。

2.删除算法(算法 2.19 )

Page 66: 第 2 章  线性表

第 2 章 线性表

66

1. 写出算法 2.18 双链循环表的插入 C语言程序代码,并调试通过。

作业

Page 67: 第 2 章  线性表

第 2 章 线性表

67

2.4 一元多项式的表示及相加

1.一元多项式

它由 n+1 个系数惟一确定,因此,在计算机里,它可用一个线性表 P 来表示:

nnn xpxpxppxP 2

210)(

)1(),,,,( 210 nppppP

每一项的指数 i ,都隐含在其系数 的序号里。ip

2. 顺序存储结构的多项式相加

设另有一多项式 )2()()( 110 mm qqqqxQ

不失一般性,设 m<n ,则两个多项式相加的结果

Page 68: 第 2 章  线性表

第 2 章 线性表

68

)()( xQxPR mnm

可用线性表 R 表示如下:

),,,,,,( 11221100 nmmmm pqpqpqpqpqpR

3. 多项式的另一种表示方法:

对于如下的多项式,使用线性 (1) 的方法来表示显然浪费存储空间。

2000010000 231)( xxxS

用线性表存放系数,须用一长度为 20001 的线性表来表示,表中仅有 3 个非零元素。

Page 69: 第 2 章  线性表

第 2 章 线性表

69

)),(,),(),,(( 2211 mm epepep

4. 链式存储结构中的一个结点的定义:

coef exp link

设有二多项式 A 和 B :

878

17817

9228)(

5937)(

xxxxB

xxxxA

Struct pnode

{ int coef; /* 系数 */

int exp; /* 指数 */

struct pnode *link;

}

若用一个长度为 m 且每个元素有两个数据项(系数项和指数项)的线性表便可惟一确定一个多项式。

Page 70: 第 2 章  线性表

第 2 章 线性表

70

用线性链表方法表示这两个多项:-1 7 0 3 1 9 8 5 17 ^A

qa

-1 8 1 22 7 -9 8 ^B

qb

两个一元多项式相加运算规则:对于两个多项式中所有指数相同的项,对应系数相加,若其和不为零,则构成“和多项式”中的一项;若有指数不相同的项,则分别抄到“和多项式”中去。

-1 7 0 11 1 22 7 5 17 ^C

qc

Page 71: 第 2 章  线性表

第 2 章 线性表

71

struct pnode *padd(qa,qb)

struct pnode *qa,*qb

{ struct pnode *qc,*p,*q,*s;

int x;

p=qa;q=qb;

qc=(struct pnode *) malloc (sizeof(struct pnode));

r=qc;

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

{ if(p->exp==q->exp /* 两结点指数相等时,将两系数相加生成新结点插入 C 中*/

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

if(x!=0)

{ s=(struct pnod *) malloc (sizeof(struct pnode));

P43 算法 2.23 多项式加法的 C语言程序

Page 72: 第 2 章  线性表

第 2 章 线性表

72

s->coef=x; s->exp=p->exp;

r->link=s; r=s; }

p=p->link; q=q->link; }

else /* 两结点的指数不相等时,将其中较小系数的结点复制成一新结点插入 C 中 */

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

{ s=(struct pnode *) malloc (size (struct pnode));

s->coef=q->coef;

s->exp=q->exp;

r->link=s; r=s; q=q->link; }

else

{s=(struct pnode *) malloc (size (struct pnode));

Page 73: 第 2 章  线性表

第 2 章 线性表

73

s->coef=p->coef; s->exp=p->exp; r->link=s; r=s; p=p->link; }} while (p!=NULL) {s=(struct pnode *) malloc (size (struct pnode)); s->coef=p->coef; s->exp=p->exp; r->link=s; r=s; p=p->link; } while (q!=NULL) { s=(struct pnode *) malloc (size (struct pnode)); s->coef=q->coef; s->exp=q->exp; r->link=s; r=s; q=q->link; }

Page 74: 第 2 章  线性表

第 2 章 线性表

74

r->link=NULL; /* 最后结点的 link域置空 */

s=qc; /* 删除 C 的头结点 */

qc=qc->link;

free(s);

return(qc);

}

Page 75: 第 2 章  线性表

第 2 章 线性表

75

1. 基础知识:题集 2.8 、 2.9 ; 2. 算法实现:在 C++ 系统中实现教材算法 2.23 。

作 业