第 10 章 结构体和链表

52
10 10 第 第第 第第第 第 第第 第第第 10.1 第第 第第第第第第第第第第 10.2 第第 第第第第第第第第第第第 10.3 第第 第第第第第 10.4 第第 第第第第第 10.5 第第 第第第 10.6 第第

Upload: edmund

Post on 19-Jan-2016

115 views

Category:

Documents


0 download

DESCRIPTION

第 10 章 结构体和链表. 10.1 结构体类型的定义与变量说明. 10.2 结构体类型变量的引用与初始化. 10.3 结构体类型与数组. 10.4 结构体类型与指针. 10.5 结构体与函数. 10.6 链表. 10.1 结构体类型的定义与变量说明. 我们所处理的数据并非总是一个简单的整型、实型或字符型数据。如我们要处理的对象是学生,不可能孤立地考虑学生的成绩,而割裂学生成绩与学生其它属性之间的内在联系。 学生的成绩、姓名、学号等是一组逻辑相关的数据,孤立地考虑这些属性,将导致操作的不便或逻辑错误。. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第 10 章 结构体和链表

第第 1010 章 结构体和链表章 结构体和链表第第 1010 章 结构体和链表章 结构体和链表10.1 结构体类型的定义与变量说明10.2 结构体类型变量的引用与初始化10.3 结构体类型与数组10.4 结构体类型与指针10.5 结构体与函数10.6 链表

Page 2: 第 10 章 结构体和链表

10.1 结构体类型定义

10.1 结构体类型的定义与变量说明 我们所处理的数据并非总是一个简单的整型、实型或字符型数据。如我们要处理的对象是学生,不可能孤立地考虑学生的成绩,而割裂学生成绩与学生其它属性之间的内在联系。 学生的成绩、姓名、学号等是一组逻辑相关的数据,孤立地考虑这些属性,将导致操作的不便或逻辑错误。

解决以上问题的方法就是引入结构体类型,将逻辑相关的数据有机组合在一起,称之为结构体。

Page 3: 第 10 章 结构体和链表

一 . 结构体类型的定义

struct 结构体类型名 { 数据成员列表; } ;

结构体类型的一般定义形式为:

定义结构体类型的标识符

用户命名的标识符

结构体类型定义的结束符

10.1 结构体类型定义

Page 4: 第 10 章 结构体和链表

例 10-1 ,一个学生的数据信息包含有学号、姓名、性别、年龄、成绩、住址,可将其定义为一个结构体类型:

struct student { long ID ; /* 学生学号 */ char name[10] ; /* 学生姓名 */ char sex ; /* 学生性别 */ int age ; /* 学生年龄 */ float score ; /* 学生成绩 */ char addr[30] ; /* 学生住址 */ } ; 结构体类型定义仅仅是定义了一个特定的复合数据类型,描述了这一类型数据的公共属性,为了在程序中使用该结构体类型的具体对象,还需要说明这种类型的变量。

10.1 结构体类型定义

Page 5: 第 10 章 结构体和链表

二 . 结构体类型变量的定义 结构体类型变量定义的一般形式:struct 结构体类型名 结构体变量名; 1. 先定义结构体类型再定义结构体变量

struct student stu1 , stu2 ;

ID(4 字节 )

stu1

name

sex(1 字节 )

age(2 字节 )score(4 字节 )

addr

……

……

共 10 个字节

共 30 个字节

IDstu2

……

图 10-1 结构体变量的存储结构

共 51 个字节

10.1 结构体类型定义

Page 6: 第 10 章 结构体和链表

2. 定义结构体类型的同时定义变量

struct student{ long ID ; char name[10] ; char sex ; int age ; float score ; char addr[30] ;}stu1 , stu2 ;

10.1 结构体类型定义

Page 7: 第 10 章 结构体和链表

3. 直接定义结构体变量struct { long ID ; char name[10] ; char sex ; int age ; float score ; char addr[30] ;}stu1 , stu2 ;

结构体变量的三种形式可以任意选用。但在不同函数中定义说明同一类型的结构体变量时,用第三种方法不太方便,一般用第一种和第二种定义形式。

10.1 结构体类型定义

Page 8: 第 10 章 结构体和链表

三 . 结构体类型的嵌套 结构体类型的嵌套是指结构体的成员是一个结构体类型。 若定义学生信息为结构体,其成员分别为:学号、姓名、性别、出生年月、成绩。其中出生年月包括出生的年、月、日三个数据,这些数据可以用另一个结构体类型表示。

例如,定义 student 结构体。( 1)先定义 date 结构体:struct date{int year; int month; int day ; } ;

( 2)再定义 student 结构体:struct student{ long ID ; char name[10] ; char sex ; struct date birthday ; float score ;};

10.1 结构体类型定义

Page 9: 第 10 章 结构体和链表

10.2 结构体类型变量的引用与初始化

一 . 结构体类型变量的引用 对一个结构体类型变量的引用是通过引用它的每一个成员来实现的。

引用运算符有两个: . -> 其中,“ ->”为结构体指针运算符,

引用一个结构体变量的成员有两种方法:结构体变量名、指向结构体的指针变量

结构体成员运算符“ .”在所有运算符中优先级最高 . 结构体变量不能作为一个整体进行输入输出 ,只能对其成员分别输出 。

10.2 结构体变量引用

Page 10: 第 10 章 结构体和链表

用结构体变量名引用其成员的一般形式:结构体变量名 .成员名

其中,“ .”称为结构体成员运算符,将结构体变量名与成员名连接起来,它具有最高级别的优先级。 结构体变量可以单独引用其成员,也可作为一个整体引用,还可以引用结构体变量或成员的地址。

1. 单独引用结构体变量的成员 struct clock{ int hour,minute,second ; }; struct date{ int year, month, day ; struct clock time ; } ;

struct date today ;today.year=2004 ;today.month=4 ;today.day=12 ;today.time.hour=16 ;today.time.minute=47 ;today.time.second=15 ;

10.2 结构体变量引用

Page 11: 第 10 章 结构体和链表

2. 结构体变量作为一个整体引用 结构体变量不可以作为整体进行输入输出,但可以作为函数的参数或返回值而被整体引用,也可以将一个结构体变量作为一个整体赋给另一个具有相同类型的结构体变量。

struct date { int year , month , day ; } ; struct date nextday(day)

struct date day ; {struct date temp ; ...

return(temp) ; }

函数 nextday 的形参 day 为结构体类型,它将整体接受同类型实参的值

10.2 结构体变量引用

Page 12: 第 10 章 结构体和链表

3. 引用结构体变量的地址或成员的地址

引用结构体变量的成员地址,要在结构体成员引用的前面再加“ &”运算符 .

结构体变量 a的成员 t赋值:

scanf(”%d”, &a.t) ;

引用结构体变量的地址 ,在结构体变量的前面直接加“ &”:

printf("%X", &a) ;

10.2 结构体变量引用

Page 13: 第 10 章 结构体和链表

二 . 结构体类型变量的初始化

结构体变量可以在说明的同时初始化。

struct clock { int hour , minute , second ; } ; struct date { int year , month , day ; struct clock time ; } ; struct date today={2004 , 4 , 12 , 17 , 4 ,30} ;struct date today={2004 , 4 , 12 , {17 , 4 , 3

0}} ; 10.2 结构体变量引用

Page 14: 第 10 章 结构体和链表

10.3 结构体类型与数组

一 . 结构体数组的定义

1. 先定义结构体类型,后定义结构体数组 struct student students[100] ;

2. 结构体数组与结构体类型同时定义

struct student { long ID ; char name[10] ; int age ; float score[3] ; }students[100] ;

3. 不定义结构体类型名,直接定义结构体数组 struct { long ID ; char name[10] ; int age ; float score[3] ; }students[100] ;

10.3 结构体与数组

Page 15: 第 10 章 结构体和链表

二 . 结构体数组的初始化与结构体数组元素的引用

1. 结构体数组的初始化 struct person

{long ID ; char name[10] ;

char sex ; struct date birthdate ; char department[30] ; float salary ; char address[30] ; }employee[3]={{1001 ,”张山”,’ M’ , 1990 , 3 , 5 ,”计算中心”, 545 ,”长沙” } , {1002 ,”李红”,’ F’ , 1989 , 10 , 1 ,”信息学院”, 643 ,”武汉” } , {1003 ,”王武”,’ M’ , 1987 , 4 , 15 ,”人事处”, 745 ,”广州” }} ; 10.3 结构体与数组

Page 16: 第 10 章 结构体和链表

图 10-2 结构体数组的存储结构

employee[1] 1002 ID ( 4 字节)" 李红 " name ( 10 字节)

10

sex ( 1 字节)

1birthdate(date 类型, 6 字节 )

" 信息学院 "salary ( 4 字节)643address ( 30 个字节)

'F'1989

" 武汉 "

department ( 30 个字节)

共 85 个字节

employee[2] 1003 ID ( 4 字节)" 王武 " name ( 10 字节)

4

sex ( 1 字节)

15birthdate(date 类型, 6 字节 )

" 人事处 "salary ( 4 字节)745address ( 30 个字节)

'M'1987

" 广州 "

department ( 30 个字节)

共 85 个字节

employee[0] 1001 ID ( 4 字节)" 张山 " name ( 10 字节)

3

sex ( 1 字节)

5birthdate(date 类型, 6 字节 )

" 计算中心 "salary ( 4 字节)545address ( 30 个字节)

'M'1990

" 长沙 "

department ( 30 个字节)

共 85 个字节

10.3 结构体与数组

Page 17: 第 10 章 结构体和链表

2. 结构体数组元素的引用 一个结构体数组元素相当于一个结构体变量,元素成员的访问使用数组元素的下标来实现。

结构体数组元素成员的访问形式:结构体数组名 [元素下标 ].结构体成员名

可以将一个结构体数组元素整体赋给同一结构体数组的另一个元素,或赋给同一结构体类型变量。

employee[0].ID=1001;

employee[1]=employee[2] ;

与结构体变量一样,结构体数组元素也不能作为一个整体进行输入输出,只能以单个成员的形式实现。

10.3 结构体与数组

Page 18: 第 10 章 结构体和链表

例 10-2 计算一个班学生的三门课程的平均成绩,并输出该班学生姓名及平均成绩。

分析:

将学生姓名、三门成绩、平均成绩定义为一个 student 结构体类型,使每个学生的各项数据组合成一个整体进行操作。

若有 n名学生,则需要 n个 student 结构体类型变量,定义一个结构体数组 stu存放 n 个学生的信息。

程序见教材

10.3 结构体与数组

Page 19: 第 10 章 结构体和链表

10.4 结构体类型与指针

当结构体很大时,结构体变量的整体赋值效率是相当低的。

结构体变量名就是分配给该变量的内存空间的起始地址,我们可以利用指向结构体变量的指针,实现在不同程序段对同一内存区域的结构体变量的数据成员执行各种操作。

10.4 结构体与指针

Page 20: 第 10 章 结构体和链表

一 . 指向结构体变量的指针

指向结构体变量的指针也称为结构体指针,它保存了结构体变量的存储首地址。

1. 结构体指针的定义形式:

struct 结构体类型名 * 指针变量名;

struct student stu , *p ; p=&stu ;

10.4 结构体与指针

Page 21: 第 10 章 结构体和链表

2. 结构体变量成员的三种访问方法

( 1)结构体变量 .成员名

stu.ID

( 2) (* 结构体指针 ).成员名

(*p).ID

( 3)结构体指针 ->成员名

p->ID 结构体指针 ->结构体成员

结构体指针运算符“ ->”

10.4 结构体与指针

Page 22: 第 10 章 结构体和链表

二 . 指向结构体数组的指针

struct person { char name[10] ; int age ; } ; struct person *p , s , boy[3]={”Zhang” ,

18 ,” Wang” , 20 ,” Li” , 17} ;

对于已定义的结构体数组,若用一个变量来存放该结构体数组在内存中的首地址,则该变量为指向结构体数组的指针变量。

例如,定义结构体类型 person 和结构体指针变量 p 。

p=boy ;定义了结构体数组 boy 和结构体指针变量 p ,且 p指向数组 boy 的首地址。

10.4 结构体与指针

Page 23: 第 10 章 结构体和链表

将结构体变量与结构体数组的首地址赋给结构体指针的不同之处:

p=&s ; /*s 为结构体变量 */

p=boy ; /*boy 为结构体数组, boy 为数组的首地址 */ 结构体指针也可以指向结构体数组的一个元素,这时结构体指针变量的值是该结构数组元素的首地址。

注意:

若要将结构体成员的地址赋给结构体指针 p,则必须使用强制类型转换操作,转换形式为:

p=(struct 结构体类型名 *)&结构体数组元素 . 成员名

10.4 结构体与指针

Page 24: 第 10 章 结构体和链表

10.5 结构体与函数C 语言中,在函数之间传递结构体变量有两种方法:(1) 以结构体变量作为参数,直接传递结构体变量的值 .

(2) 以结构体指针作为参数,传递结构体变量的地址。

1. 结构体变量作为函数的参数

在 ANSI C标准中允许用结构变量作为函数参数进行整体传送,即直接将实参结构体变量的各个成员的值逐个传递给形参结构体变量的对应成员。注意,实参与形参必须是相同结构体类型的变量。

10.5 结构体与函数

Page 25: 第 10 章 结构体和链表

struct stu{int num ; char *name ; char sex ; float score ; } boy[5] ;

int pass(struct stu p)/* 函数说明 */

for(i=0 ; i<5 ; i++) {s+=boy[i].score ; if(!pass(boy[i])) c+=1 ; /* 以结构体数组元素作为实参 */}

具体程序请参考教材 205 页例 10-8.

函数 pass 的参数传递是一个值传递过程

10.5 结构体与函数

Page 26: 第 10 章 结构体和链表

2. 指向结构体变量的指针作为函数的参数

因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。这时由实参传递给函数形参的只是地址,从而减少了时间和空间的开销。

通过结构体变量的整体传递可以实现函数参数的传递,但这要将结构体变量的全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。

10.5 结构体与函数

Page 27: 第 10 章 结构体和链表

struct stu{int num ; char *name ; char sex ; float score ; } boy[5],*p ;

ave(p) ; void ave(struct stu *p)

与普通数组的特性相同,结构体数组名也是该数组的首地址,在进行参数传递时也可直接将结构体数组名传递给作为函数形参的结构体指针变量。如以上函数调用语句可写成: ave(boy) ;

10.5 结构体与函数

Page 28: 第 10 章 结构体和链表

3. 结构体类型的函数 若函数的返回值仍是结构体类型,则称该函数为结构体类型函数。其一般定义形式:

struct 结构体类型 函数名 ( 形参表 ){ 函数体 }

其中结构体类型必须已定义。 例 10-3 每个学生有三门成绩,用函数输入学生的成绩并求每个学生的平均成绩。 (教材 207页例 10-11)分析:( 1)定义一个返回值为学生结构体类型的函数,用于输入学生信息; ( 2)在主函数 main 中调用结构体函数,得到学生结构体或学生结构体的地址。

10.5 结构体与函数

Page 29: 第 10 章 结构体和链表

10.6 链表 C 语言中,变量存储空间的分配分为静态分配和动态分配。 静态存储分配: 先在程序说明部分进行变量的说明,

然后在程序编译时分配适当的存储单元。这些存储单元一经分配,在它的生存期内是固定不变的。

动态存储分配: 在程序执行期间,通过“申请”分配指定的存储空间来存储数据,当有闲置不用的存储空间时,又可以随时将其释放。

10.6 链表

Page 30: 第 10 章 结构体和链表

ANSI C标准为动态分配系统定义了四个函数:malloc 、 calloc 、 free 和 realloc 。

用户可以通过调用 C 语言的标准库函数来实现动态存储分配,从而得到或释放指定数目的内存空间。

这些函数在头文件 alloc.h及 stdlib.h 中声明。

10.6 链表

Page 31: 第 10 章 结构体和链表

一 . 链表概述

数组 顺序存储结构

随机存取 逻辑关系上相邻的两个元素在物理位置上也相邻

1. 数组的致命弱点:

(1) 在对数组进行插入或删除操作时,需移动大量数组元素

(2) 在数组的长度是固定的而且必须预先定义,数组的长度难以缩放,对长度变化较大的数据对象要预先按最大空间分配,使存储空间不能得到充分利用

10.6 链表

Page 32: 第 10 章 结构体和链表

2. 链表存储结构是一种动态数据结构

特点 :

(1) 它包含的数据对象的个数及其相互关系可以按需要改变 .(2)存储空间是程序根据需要在程序运行过程中向系统申请获得 .(3) 不要求逻辑上相邻的元素在物理位置上也相邻 .(4)没有顺序存储结构所具有的弱点 .

10.6 链表

Page 33: 第 10 章 结构体和链表

图 10-4 单向链表的逻辑状态

Qian Sun Li Zhou Wu Wang

Head 7 13 1 43 25 37

以上链表结构中只有一个方向的指针,因此又称为单链表,简称为链表。

一般地,用户可根据链表存放的信息如存放学生信息就称为学生链表,存放职工信息就称为职工链表。

10.6 链表

Page 34: 第 10 章 结构体和链表

在单链表,通常称它的数据元素为结点,每个结点都是一个结构体,至少包括两个成员:存储数据元素信息的成员称为数据域;存储直接后继结点存储位置的成员称为指针域 .

显然,链表结点的指针域存放的地址类型与它自身的类型是相同的。

这就是 C 语言中较为特殊的递归结构体或自引用结构体,这种结构体具指向自身结构体的指针,一般在实现链表、树等数据结构时会用到这种特殊的结构体。

10.6 链表

Page 35: 第 10 章 结构体和链表

每个链表都有一个“头指针” head ,整个链表的访问必须从头指针开始进行,头指针指示链表中的第一个结点的存储位置,习惯上将“头指针” head 指示的链表简称为链表 head ,下同。同时,由于最后一个数据元素没有直接后继结点,则链表中最后一个结点的指针为“空”( NULL,即空地址)。

表 10-1 链表的存储地址与指针域的关系

存储地址

数据域 指针域

head   7

1 Li 43

7 Qian 13

13 Sun 1

25 Wu 37

37 Wang NULL

43 Zhou 25

10.6 链表

Page 36: 第 10 章 结构体和链表

数据元素之间的逻辑关系是由结点中的指针指示的,逻辑上相邻的两个数据元素其存储的物理位置不要求紧邻,即链表中的数据元素在内存中不是顺序存放的,要访问其数据元素不能像数组一样按下标去查找。要找一个元素,必须先找到上一个元素,根据上一个元素的指针域才能找到下一个元素。

因此,链表的数据元素访问必须从头指针开始,逐个访问链表的每个结点,直到元素的指针域为空为止。

10.6 链表

Page 37: 第 10 章 结构体和链表

要使用链表,首先应定义结点的类型,再定义相应的结构体变量。例如,前面链表中结点的结构类型可以定义为:

struct student {char name[10] ; struct student *next ; } ; 其中, next 为指针变量,其类型为结构体类型 student ,它可存储一个 student 结构体类型变量的地址,即实现链表中指向下一个结点的指针域。 这是一个递归定义,它在结构体 student 的定义未完成时又引用它定义其它的变量(指针变量)。

10.6 链表

Page 38: 第 10 章 结构体和链表

引入链表后,用户就可以根据需要在程序的运行过程中动态分配存储空间。动态存储分配需要利用以下C语言库函数。

(1) 函数 malloc

函数功能:

函数原型:void *malloc(unsigned int size) ;

在内存的动态存储区中分配一个长度为 size 的连续存储空间。其中,形参 size 为无符号整数,是函数 malloc 要求分配存储空间的字节个数。函数返回值为一个指针,它指向所分配存储空间的起始地址。若函数返回值为 0,则表示未能成功申请到内存空间。函数类型为 void ,表示返回的指针不指向任何具体的类型 .

10.6 链表

Page 39: 第 10 章 结构体和链表

int *p ; long *lp ;p=(int *)malloc(8) ;lp=(long *)malloc(12) ;head=(struct student *)malloc(sizeof(struct student)) ;

例如:

malloc(8) ;

通过函数 malloc 向系统申请 8个字节的内存空间,其起始地址通过函数值返回。若要求用一个指针变量(具有某种类型)指向这个起始地址,则需要显式进行类型转换。例如:

10.6 链表

Page 40: 第 10 章 结构体和链表

(2) 函数 calloc

函数原型:

void calloc(unsigned int n , unsigned int size) ;

函数功能: 在内存的动态存储区域中分配 n 个长度为 size的连续存储空间。函数的返回值为分配域的起始地址;如果分配不成功,则返回值为 0。

例如:int *p ;

p= ( int * ) calloc(3 , 8) ;

分配 3 个 8字节的的连续存储空间,并将其起始地址赋给整型指针 p。

10.6 链表

Page 41: 第 10 章 结构体和链表

(3) 函数 free函数原型:void free(void *ptr) ;函数功能: 释放由指针变量 ptr 为所指示的内存区域。其中, ptr 一个指针变量,指向最近一次调用函数 malloc 或 calloc 时所分配的连续存储空间的首地址。通过函数 free 将已分配的内存区域交还系统,使系统可以重新对其进行分配。例如:long *p;p=(long *)malloc(8) ;

...

free(p);

10.6 链表

Page 42: 第 10 章 结构体和链表

将 ptr 所指向的存储空间重新分配大小为 size 的存储空间,并返回重新分配后的存储空间的首地址。其中, ptr 指向原先用函数 malloc 分配的内存区域的起始地址。函数 realloc 将 ptr 指向的存储区的大小改为size 个字节,可以使原先分配的存储区域扩大或缩小。其函数返回值是一个指针,即为新的存储区域的首地址。

(4) 函数 realloc函数原型:void *realloc(void *ptr , unsigned int size) ;函数功能:

注意:新的首地址不一定与原先定义的首地址相同,因为为了增加空间,存储区会进行必要的移动。

10.6 链表

Page 43: 第 10 章 结构体和链表

二 . 建立链表

1. 尾插法建立单链表所谓尾插法,是指新插入的结点总是放在链表尾部。一般地,链表的建立过程是在一个空链表的基础上逐步插入新的结点而成的。所谓空链表,即链表没有一个结点,这时链表的头指针为空。

链表与数组不同,不是程序定义时就可建立,而是在程序运行过程中一个结点一个结点地建立起来的,并在结点之间形式链接关系。

建立单向链表的方法有尾插法与头插法两种。

因此,链表的建立是一个动态存储空间分配和形成链接关系的过程。

10.6 链表

Page 44: 第 10 章 结构体和链表

用尾插法建立链表的过程如下:( 1 )建立一个空链表,即 head=NULL ;这里 head 为头指针,它指向

链表的第一个结点。为了操作的方便,还定义一个指向链表尾结点的指针 last ,显然 last 的初始值也为空(即 NULL )。如图 10-5 ( a )所示。

为了描述的方便,将“指针 head 所指向的结点”简称为“ head 结点”,将“指针 p 所指向的结点”简称为“ p 结点”,下同。

( 2 )生成新结点 p ,对新结点 p 的数据域和指针域赋值。由于新插入的结点总是尾结点,则它的后继为空,故“ p->next=NULL ;”。如图 10-5( b )所示。

( 3 )将 p 结点插入到链表:如果链表为空(即 head=NUUL ),则 p 结点为头结点,也是尾结点,即

:head=p ; last=p ;

如图 10-5 ( c )所示。否则应将 p 结点插入到 last 结点之后,并使 p 结点成为当前的尾结点,即

:last->next=p ; last=p ;

如图 10-5 ( d )所示。( 4 )重复( 2 ) ~ ( 3 ),继续插入新结点直到结束。

10.6 链表

Page 45: 第 10 章 结构体和链表

所谓头插法,是指新插入的结点总是作为链表的第一个结点。用头插法建立链表的过程如下:( 1 )建立一个空链表,即 head=NULL ;与尾插法不同的是这里不需定义尾指针。( 2 )生成新结点 p ,对新结点 p 的数据域赋值。由于新插入的结点成为头结点,其指针域不必赋值。( 3 )将 p 结点插入到链表:

先 p 结点的后继为当前的头结点,然后使 p 结点成为当前的头结点,即:

p->next=head ; head=p ;( 4 )重复( 2 ) ~ ( 3 ),继续插入新结点直到结束。

2. 头插法建立单链表

10.6 链表

Page 46: 第 10 章 结构体和链表

三 . 链表的访问

1. 输出链表结点将链表中各结点的数据依次输出。输出链表结点的操作过程如下:( 1)取得链表头结点的地址 head ;( 2)用一个跟踪指针 p指向头结点,即 p=head ;( 3)输出 p所指结点的成员值;( 4)移动指针 p,使它指向它的后继结点。 即 p=p->next ;( 5)重复( 3)( 4),直到指针 p为空。

10.6 链表

Page 47: 第 10 章 结构体和链表

2. 统计链表结点的个数一般情况下,各个单链表中结点个数是随机的,要想知道表中结点数目,必须从表头开始访问到表尾,逐个统计出结点数目。

3. 查找链表的某个结点在链表上查找符合某个条件的结点,也必须从链表头开始访问链表。( 1)查找链表中第 n个结点,若找到,则返回它的地址,否则返回空指针。

( 2)在链表中查找指定值的结点,若找到,则返回它的地址,否则返回空指针。

10.6 链表

Page 48: 第 10 章 结构体和链表

四 . 链表的插入操作

所谓链表的插入操作就是将一个结点插入到一个已有链表的某个位置上。 1. 在第 n个结点之后插入 1个新结点 (x)( 1 )输入 n 和 x (一般在调用函数中处理);( 2 )申请一个新结点, q 指针指向新结点, q 的值为 x (一般在调用函数中处理);( 3 ) p=head , r=NULL , r 为 p 的前驱结点, i=0 ;( 4 ) i++ , r=p , p=p->next , p 结点往前移动一个结点;( 5 )若 i<n 且 p!=NULL ,则重复( 4 );( 6 )若 i==0 ,则链表为空,没有结点, q 结点作为链表的第 1 个结点插入:

q->next=head , head=q ;( 7 )若 i<n 且 p==NULL ,则链表不足 n 个,将 q 结点插入到链表尾 r 结点之后:

r->next=q , q->next=NULL ;( 8 )否则,将 q 结点插入到第 n 个结点之后,即插入到 r 结点与 p 结点之间:

r->next=q , q->next=p ;( 9 )返回链表头 head 。

10.6 链表

Page 49: 第 10 章 结构体和链表

2. 指定值的结点之后插入一个新结点( 1 ) q 指针指向新结点;( 2 ) p=head , r 指向 p 结点的前一个结点;( 3 )若 head==NULL ,则链表为空,没有结点, q 结点作为链表的第 1 个结点插入:

head=q ; q->next=NULL ;( 4 ) r=p , p=p->next , p 、 r 结点往前移动 1 个结点;( 5 )若 p->score!=x 且 p!=NULL ,则重复( 4 );( 6 )若 p==NULL ,则未找到 score 为 x 的结点,将 q 结点插入到链表尾 r 结点之后:

r->next=q , q->next=NULL ;( 7 )否则,将 q 结点插入到第 n 个结点之后,即插入到 r 结点与p 结点之间:q->next=p->next , p->next=q ;( 8 )返回链表头指针 head 。

10.6 链表

Page 50: 第 10 章 结构体和链表

五 . 链表的删除操作

从一个链表中删除一个结点,并不是真正从内存中把它抹掉,而是把它从链表中分离出来,即改变链表的链接关系。

1.删除第 n个结点 将以 head 为头的链表中的第 n个结点从链表中移出,其后继的结点往前移,使之仍为一个链表,只是结点数目比原来少 1 。

10.6 链表

Page 51: 第 10 章 结构体和链表

例 : 编写函数,删除学生链表 head 中的第 n个结点。分析:( 1) p=head , q指针指向 p所指结点的前 1个结点;( 2) i为访问过的结点数目;( 3) i++, q=p , p=p->next , p、 q 移动 1 个结点;( 4)若 p!=NULL 且 i<n-1 ,重复( 3)( 5)若 n==1 ,则删除第 1个结点,将下一个结点作为链表头结点: head=head->next ;( 6)若 head==NULL,链表为空,不能删除;( 7)若 p==NULL,第 n个结点不存在,不能删除;( 8)找到第 n个结点,删除 p 结点:

q->next=p->next ; p 的前 1个结点的 next 值赋值为 p的 next域;( 9)返回 head 。

10.6 链表

Page 52: 第 10 章 结构体和链表

2. 删除指定结点例 : 编写函数,删除链表 head 中值为 x的结点。分析:( 1) p=head , q指针指向 p所指结点的前 1个结点;( 2)若 head==NULL,链表为空,不能删除;( 3) q=p , p=p->next , p、 q 移动 1 个结点;( 4)若 p->next!=NULL 且 p->score!=x ,重复( 3)( 5)若 n==1 ,则删除第 1个结点,将下一个结点作为链表头结点: head=head->next ;( 6)若 p->score==x ,则找到 score 值为 x的结点,删除 p结点:

若 p==head 为第 1个结点,让 head 指向下一个结点, head=p->next ; 否则, q->next=p->next ; p 的前 1个结点的 next 值赋值为 p的 next域;( 7)返回 head 。

10.6 链表