1、数据结构 第2章 线性表,第2章 线性表,2.1 线性表的定义及基本运算 2.2 线性表的顺序存储结构及其运算 2.3 线性表的链接存储结构及其运算 2.4 顺序表和链表的比较 2.5 线性表的简单应用举例,线性结构的特点 : (1)存在唯一的一个被称作“第一”的数据元素; (2)存在唯一的一个被称作“最后一个”的数据元素; (3)除第一个元素之外,结构中的每一个数据元素均只有一个前驱; (4)除最后一个之外,结构中每个数据元素均只有一个后继。 根据上述特点,线性结构中的所有元素按它们之间的关系可以排成一个线性序列:k1,k2,kn。这样的线性序列称为线性表。,2.1 线性表的定义及基本运算,
2、2.1.1 线性表的定义 2.1.2 线性表的基本运算,2.1.1 线性表的定义,线性表是由n(n0)个数据元素(结点)a1, a2, a3, , an组成的有限序列。通常,我们把非空的线性表(n0)记为: A=(a1, a2, a3, , an) A是线性表的表名。一个线性表可以用一个标识符来命名。 ai(1in)是表中的数据元素。 n是线性表中数据元素的个数,也称为线性表的长度。n=0的线性表称为空表,此时,表中不包含任何数据元素。,【例2.1】26个大写英文字母组成的字母表(A, B, C, D, , Z)就是一个线性表。其中字母表中的“A”是第一个数据元素,“Z”是最后一个数据元素;“
3、A”是“B”的直接前驱,“B”是“A”的直接后继该线性表的长度为26。【例2.2】有一组实验数据(41, 21, 34, 53, 62, 71, 75, 81, 76, 45),这也是一个线性表,数据之间有着一定的顺序(可能是随时间推移取得的实验数据)。这个线性表的长度为10。数据元素34的直接前驱是21,而直接后继是53。在一定的意义下,这些元素之间的相互位置关系是不能变动的。,【例2.3】学生成绩统计表也是一个线性表,见表2.1。在线性表中每个学生的成绩是一个数据元素,它由学号、姓名、数学、外语、物理、总分这6个数据项组成。该线性表的长度为5。,2.1.2 线性表的基本运算,线性表上常用的
4、基本运算有以下9种: 线性表的初始化(initiate):将线性表设置成一个空表。 求表的长度(length):求线性表的长度。 取出表的元素(getdata):访问线性表中的第i个元素。 查找运算(search):查找线性表中具有某个特征值的数据元素。, 插入运算(insert):在线性表中的第i个元素之前或之后插入一个新元素。 删除运算(delete):删除线性表中第i个元素或满足给定条件的第一个元素。 排序运算(sort):将线性表中的所有元素按给定的关键字进行排序。 归并运算(catenate):把两个线性表合并为一个线性表。 分离运算(separate):将线性表按某一要求分解成两个
5、或几个线性表。,线性表常用的存储方式有两种:顺序存储方式和链接存储方式(见第1章中例题1-7和例题1-8)。 用顺序存储方式实现的线性表称为顺序表(或称为向量); 用链接存储方式实现的线性表称为链表。 本节将分别介绍这两种存储结构,以及在这两种存储结构上如何实现线性表的基本运算。 注意:每种数据结构的运算,都与其存储结构有着密切的关系。这是学习数据结构要牢记的要点。线性表的基本运算和数据存储方式是密切相关的。,2.2 线性表的顺序存储结构及运算,2.2.1 线性表的顺序存储结构 2.2.2 顺序表的基本运算 2.2.3 插入和删除运算的时间分析 2.2.4 顺序表的优点和缺点,【例1.7】请用
6、顺序存储方式表示一周7天,假设一周7天的数据结构为: B=(D, R) D=Sun, Mon, Tue, Wed, Thu, Fri, Sat R=,若采用顺序存储方法将一周7天存储在计算机中,其顺序存储结构如图1.7所示。,图1.7 线性结构的顺序存储结构示例,2.2.1 线性表的顺序存储结构,线性表的顺序存储方法是:将线性表的所有元素按其逻辑顺序依次存放在内存中一组连续的存储单元中,也就是将线性表的所有元素连续地存放到计算机中相邻的内存单元中,以保证线性表元素逻辑上的有序性。 顺序表的特点是:其逻辑关系相邻的两个结点在物理位置上也相邻,结点的逻辑次序和物理次序一致。 线性表的顺序存储结构可
7、用数组来实现。,线性表的顺序存储结构可用数组来实现。数组元素的类型就是线性表中数据元素的类型,数组的大小,最好大于线性表的长度。因此,顺序存储结构是把线性表中每个元素a1, a2, a3, , an依次存放到数组下标为0, 1, 2, n1的位置上。假设用数组dataMAXSIZE存储线性表A =(a1, a2, a3, , an)其顺序存储结构如图2.1所示。,图2.1 顺序存储结构示意图,n,last,sequenlist,由于线性表中所有结点的数据类型是相同的,因此每个结点占用的存储空间也是相同的。假设每个结点占用d个存储单元,若线性表中第一个结点a1的存储地址为LOC(a1),那么结点
8、ai的存储地址LOC(ai)可以通过下面的公式计算得到:LOC(ai)=LOC(a1)+(i1)d(1in)(2.1)datai的地址= data0+id(1in)式中,LOC(a1)是线性表第一个元素的存储地址,称为线性表的存储首地址或基址。,i为元素的位置编号,i为下标编号,顺序存储结构的特点是:在线性表中,每个结点ai的存储地址是该结点在表中位置i的线性函数,只要知道基地址和每个结点占用存储单元的个数,利用地址计算公式就可以直接计算出任一结点的存储地址,从而实现线性表中数据元素的快速存取,其算法的时间复杂度为O(1),与线性表的长度无关。由此可知,顺序表是一种具有很高的存取效率的随机存取
9、结构。 采用顺序存储结构表示线性表时,如果将存储数据元素的数组和存储线性表实际长度的变量同时存放在结构类型sequenlist中,则顺序表的类型定义如下:,#define MAXSIZE 1000 typedef int datatype; typedef struct selist datatype dataMAXSIZE; int last; sequenlist; /* 顺序结构类型为sequenlist */ sequenlist L,L1,*LP; /* 定义变量 */ data一维数组存放线性表的元素,线性表中第1, 2, last个元素分别存放在数组第0, 1, last1位置上
10、; MAXSIZE数组data能容纳元素的最大值,称顺序表容量; last线性表当前的实际长度; datatype 线性表元素类型,应视具体情况而定。,顺序表的类型定义如下:,【例2.3】学生成绩统计表也是一个线性表,见表2.1。在线性表中每个学生的成绩是一个数据元素,它由学号、姓名、数学、外语、物理、总分这6个数据项组成。该线性表的长度为5。,图2.2 数据元素为记录的线性表的顺序存储示意图,例如,用顺序表存储表2.1所示的学生成绩统计表时,其顺序存储分配情况如图2.2所示:,n,last,sequenlist,# define MAX 500 typedef struct node /*
11、定义学生记录结构类型 */ char no10; /* 定义学生的学号 */char name10; /* 定义学生的姓名 */float score5; /* 定义学生各科成绩 */ datatype; /* 学生记录为datatype */ typedef struct selist datatype dataMAX; /* data存放学生成绩统计表 */int last; /* last为学生成绩表中人数 */sequenlist; /* 顺序表类型为sequenlist */ sequenlist L,L1,*LP; /* 定义变量*/,学生成绩统计表的存储结构的类型说明如下:,2.
12、2.2 顺序表的基本运算,定义线性表的顺序存储结构之后,就可以讨论在该存储结构上如何实现线性表的基本运算了。顺序表的基本运算有:初始化线性表运算 插入运算 删除运算 查找运算 遍历运算,初始化线性表运算,由表结构可知, L所指向的顺序表是否为空取决于其元素个数是否为0,因此,只要将L所指向的顺序表中的长度值置为0就实现了建空表的功能。因此算法中的语句部分只要一条语句L-last0即可。算法如下: void initial_List( sequenlist *L) L-last=0; ,1在顺序表中插入一个新结点x,线性表的插入运算是指在表的第i(1in+1)个位置上,插入一个新的结点x,使长度
13、为n的线性表: ( a1, , ai1 , ai , ai+1 , an ) 插入x( a1, , ai1, x, ai, ai+1, an ) 如果能插入,需要依次执行下列操作: 将aian往后移一“格”; 将x插入到第i个位置上,实现插入; 修改表的长度:因为表长度是顺序表不可分割的一个分量。 在执行插入时需要注意: 插入的条件(检查参数); 移动的次序;,在顺序表中插入一个新结点的过程如下: 检查顺序表的存储空间是否已满,若满则停止插入,退出程序运行;检查插入位置是否正确,若不正确停止插入,退出程序运行. 将第in个结点之间的所有结点依次向后移动一个位置,空出第i个位置; 将新结点x插入
14、第i个位置; 修改线性表的长度,使其加1; 若插入成功,则函数返回值为1,否则函数值返回值为0。,在顺序表中给定i位置上插入值为x的结点的算法-1,int insert_listseq(sequenlist * l, datatype x, int i) /* l是sequenlist类型指针变量 */* 给出在顺序表中的插入位置i */ /* 给出插入结点数据x */ int j;if (*l).last=MAXSIZE) /* 检查顺序表的长度 */ printf(“nt溢出错误!n“); /* 打印溢出错误信息 */return (NULL); /* 插入失败函数返回0 */ else
15、if (i(*l).last+1) printf(“nt该位置不存在!n“); return(NULL); /* 结点插入失败,函数返回0 */,i为序号,/* 在顺序表中给定的位置上插入值为x的结点的算法-2 */,else /* 在第i个结点ai 位置插入值为x的结点 */for(j=(*l).last-1;j=i-1;j-) (*l).dataj+1=(*l).dataj; /* 将结点依次向后移动 */(*l).datai-1=x; /* 将x插入第i个结点(*l).datai-1 */(*l).last=(*l).last+1; /* 将线性表的长度加1 */return(1); /
16、* 结点插入成功,函数返回1 */* INSERT_LISTSEQ */,2在顺序表中删除给定位置的结点,线性表的删除运算是指将表中第i(1in)个结点删去, ( a1, , ai1, ai, ai+1, , an )删除ai( a1, , ai1, ai+1, , an ) 若要删除表中第i个结点,就必须把表中第i+1个结点到第n个结点之间的所有结点依次向前移动一个位置,以覆盖其前一个位置上的内容,使线性表的长度变成n1。,在顺序表中删除给定位置的结点的过程如下: 检查给定结点的删除位置是否正确,若删除位置有错,则显示出错信息,退出程序运行; 把表中第i+1n个结点之间的所有结点依次向前移动
17、一个位置; 将线性表的长度减1; 若删除成功,函数返回1,否则函数返回0。,int delete_address(sequenlist *l, int i) /*删除第i个位置上的结点算法 */ int j;if (i(*l).last) printf(“nt该结点不存在!n“); return (NULL); else for(j=i-1;j(*l).last-1;j+)(*l).dataj=(*l).dataj+1; (*l).last-;return(1); /* DELETE_ADDRESS */,在顺序表中删除某给定位置上结点的算法如下:,i为序号,3在顺序表中删除给定值为x的结点,
18、在顺序表中删除某个值为x的结点的过程是: 首先在顺序表中查找值等于x的结点; 若查找成功,则删除该结点,即将其后面的所有元素均向前移动一个位置,然后将顺序表的长度减1,函数返回1; 若查找失败,则函数返回0。,应为i(下标),/* 在顺序表中删除值为x的结点的算法 */,int delete_data(lsequenlist *l, int x) int i=0, j, len; len=(*l).last;while(x!=(*l).datai) /* 删除失败则函数返回0 */* DELETE_DATA */,i为下标,4在顺序表中查找关键字为key的结点,查找运算是在具有n个结点的顺序表
19、中,查找关键字为key的元素。 若查找成功,则函数返回该关键字在表中位置; 若查找失败,则函数返回0。,在顺序表中的查找过程如下: 从顺序表的第一个结点(即数组下标为0的结点)开始依次向后查找; 若下标为i 的结点值等于key,则查找成功,函数返回结点key在表中的位置i+1; 若查找失败,即表中不存在关键字为key的结点,则函数返回0。,在顺序表中查找结点关键字为key的算法如下:int search_listseq(sequenlist *l, datatype key) /* 在顺序表中查找关键字为key结点 */ int i=0; datatype x;x=(*l).datai;whi
20、le (i(*l).last) /* 若查找失败,则返回0 */* SEARCH_LISTSEQ */,i为下标,5顺序表的遍历运算,遍历:从线性表的第一个元素开始,依次访问线性表的所有元素并且仅访问一次。顺序表的遍历:依次访问数组data0datalast1中的每一个元素。访问时可以根据需要进行任意的处理,例如,在此仅打印该元素的值。,void print_listseq(sequenlist *l) /* 顺序表的遍历算法 */ int i, n=(*l).last; /* n是顺序表的实际长度 */clrscr(); /* clrscr为清屏幕函数 */for (i=0; in; i+)
21、 printf(“tdata%2d=%4d“, i, (*l).datai); /* 打印顺序表元素 */if (i+1)%4=0) printf(“n“); /* 控制每行元素个数 */* PRINT_LISTSEQ */,顺序表的遍历算法如下:,2.2.3 顺序表插入和删除运算的时间分析,在线性表顺序存储结构中某个位置上插入和删除一个数据元素时,其插入与删除算法的主要执行时间都耗费在移动数据元素上,而移动元素的个数则取决于插入或删除元素的位置。 假设pi是在第i个元素之前插入一个元素的概率,则在长度为n的线性表中插入一个元素时,所需移动元素次数的期望值(平均次数)应为:,假设pi是在第i个
22、元素之前插入一个元素的概率,则在长度为n的线性表中插入一个元素时,所需移动元素次数的期望值(平均次数)应为:同理,假设qi是删除第i个元素的概率,则在长度为n的线性表中删除一个元素所需移动元素次数的期望值(平均次数)应为:,假定在线性表任何位置上插入或删除元素都是等概率的,即按机会均等考虑,可能进行插入的位置为i=1, 2, , n+1,共n+1个位置,则pi=1/(n+1); 若删除位置为i =1, 2, , n,共n个位置,则 qi = 1/n。 在等概率情况下,上式可以分别简化为:若顺序表的长度为n,则顺序表的插入算法和删除算法的时间复杂度均为O(n)。,2.2.4 顺序表的优点和缺点,
23、线性表的顺序存储结构是用物理位置上的邻接关系来表示结点之间的逻辑关系的。这个特点使顺序表具有以下的优缺点。 顺序表的优点是:结构简单,便于随机访问表中任一数据元素。 顺序表有以下3个缺点: 插入和删除运算不方便。 浪费存储空间。 顺序表的存储空间不容易扩充。 可见,在进行频繁的插入和删除运算时,不宜采用顺序存储结构。,2.3 线性表的链接存储结构及其运算,2.3.1 单链表 2.3.2 单链表上的基本运算 2.3.3 单链表上查找、插入和删除运算的时间分析 2.3.4 循环链表 2.3.5 双向链表,线性表的链接存储结构称为链表。常见的链表有3种: 单链表 循环链表 双链表 本节将首先讨论单链
24、表的存储结构、单链表的基本运算及单链表上其他较复杂的运算,然后再讨论循环链表和双向链表的存储结构及该结构上基本运算的实现。,2.3 线性表的链接存储结构及其运算,2.3.1 单链表,线性表的链接存储方法:用一组地址任意的存储单元来存放表中各个数据元素,这组存储单元可以是连续的,也可以是不连续的。线性表链接存储结构的特点是:数据元素的逻辑次序和物理次序不一定相同。,为了保证线性表各数据元素之间逻辑上的连续性,在存储数据元素ai时,除了要存储数据元素本身的信息外,还必须附加一个或多个指针用于指向该结点的后继结点ai+1或前驱结点ai1的存储地址(或位置)。链表正是通过每个结点的指针域,将线性表n个
25、结点按其逻辑顺序链接在一起的。每个结点有一个指针域的链表称为单链表,有两个指针域的链表称为双向链表,有多个指针域的链表则称为多向链表。,在单链表中,每个结点是由两部分组成的,如图2.3所示。 图2.3 单链表结点结构 其中: data是数据域,用来存放结点本身的信息; next是指针域或链域,用来存放本结点的直接后继结点所在的地址或者位置。,【例1.8】假设一周7天的数据结构为: B=(D, R) D=Sun, Mon, Tue, Wed, Thu, Fri, Sat R=,请用链接存储方法将一周7天存储在计算机中,其链接存储结构如图1.8所示。,图1.8 线性结构的链接存储结构示例,例如,对
26、表2.1所示的学生成绩统计表,若用单链表表示,则对应的链接存储结构如图2.4所示。图2.4 单链表的一般图示法,图2.4 单链表的一般图示法 图2.4中:箭头表示结点的指针域。链表中最后一个结点的指针域不指向任何结点称为空指针,通常用“”或NULL表示。 head是指向单链表第一个结点的指针,称为头指针。一旦知道头指针,就可以顺藤摸瓜找到链表中的所有结点。因此,图2.4所示的单链表也称为head链表。 若指针head为空指针即head=NULL,则该链表称为空表。,假设线性表的数据元素类型为datatype,则单链表类型定义如下: typedef struct snode /* 学生记录为结构
27、类型 */ char no10 ; /* 学生的学号 */char name10; /* 学生的姓名 */float score5; /* 学生各科成绩*/ datatype; /* 记录类型为datatype */ typedef struct node /* 结点类型定义 */ datatype data; /* 结点的数据域 */struct node *next; /* 结点的指针域 */ linklist; /* linklist为单链表类型 */ linklist *head; /* head是单链表头指针 */,为了便于实现链表的各种运算,通常在单链表第一个结点之前再增加一个类型
28、相同的结点,该结点称为表头结点,而其他结点则称为表结点。 表头结点的数据域可以存放一个特殊的标志信息或链表的长度,也可以不存放任何数据。在表结点中,第一个结点和最后一个结点分别称为首结点和尾结点。例如,一个带头结点的单链表A=(a1 , a2 , a3 , an)如图2.5(a)所示,一个带头结点的空链表则如图2.5(b)所示。,图2.5 带头结点的单链表的示意图,2.3.2 单链表上的基本运算,带头结点的单链表上的基本运算有: 链表的建立 尾插法建立链表 头插法建立链表 查找 按值查找 按位置查找 插入 删除 判断链表是否为空表,1单链表的建立运算,建立带表头结点的单链表的常用方法:尾插法建
29、立链表 头插法建立链表若用尾插法建立链表,则链表中结点的输出次序与输入顺序相同; 若用头插法建立链表,则链表中结点的输出次序与输入顺序相反。,(1)用尾插法建立链表,基本思想:生成新结点,将读入的数据存入新结点的数据域中,把新结点插入到当前链表的尾结点之后,重复上述过程,直到输入结束标志为止。,2006年9月,数据结构 第2章线性表,57,用尾插法建立带头结点的单链表的过程如下: 调用malloc函数,生成一个头结点head; 调用malloc函数,开辟新的存储单元p; 给新结点的数据域赋值,将新结点的指针域设置为空; 将新结点链接到链表的尾结点rear之后,修改表尾指针rear; 重复上述步
30、骤,直至输入结束标志0为止。 其插入过程如图2.6所示。,动态开辟和释放存储单元:,1. malloc( ):函数原型:void *malloc(unsigned size)功能:在内存的动态存储区中分配一个长度为size的连续空间.此函数的值是一个指向分配域起始地址的指针. 2. free( )函数原型:void free(void *p)功能:释放由p指向的内存区.函数原型在stdlib.h中,2006年9月,数据结构 第2章线性表,59,图2.6 将新结点p插到单链表head的表尾,head,rear,rear,rear,rear,rear,p,p,p,p,1,2,3,4,data,ne
31、xt,head=(struct stu *)malloc(sizeof(struct node); rear=head; scanf (“%d”,若 x!=0 p=(struct stu *)malloc(sizeof(struct node); p-data=x; rear-next=p; rear=p; scanf (“%d”,类型定义:,typedef struct node int data;struct node next;linklist;,2006年9月,数据结构 第2章线性表,61,/* 建立单链表算法用尾插法建立带头结点单链表-1 */ linklist *hrear_cre
32、at() int x; linklist *head, *p, *rear; /* head, rear为头指针和尾指针 */head=(linklist*)malloc(sizeof(linklist); /* 建单链表头结点 */head-data=-999;rear=head; /* 尾指针的初值为头结点head */clear(); /* 自定义函数是清屏、定位和设置颜色 */printf(“tt请随机输入互不相同的正整数以0作为结:nntt“);scanf(“%d“, /* 读入第一个结点的值 */,/* 建立单链表算法用尾插法建立带头结点单链表-2 */ while (x!=0)
33、/* 输入数据,以0为结束符 */ p=(struct node*)malloc(LEN); /* 生成一个新结点 */p-data=x; /* 给新结点的数据域赋值 */rear-next=p; /* 新结点插入到表尾*rear之后 */rear=p; /* 将尾指针rear指向新的尾结点 */scanf(“%d“, /* 函数返回单链表的头指针head */* CREATE_HEADREAR */,输出链表(遍历链表):,2006年9月,数据结构 第2章线性表,64,head,1,2,3,4,p,p,p,p,p=0,void print(linklist * head) linklist
34、*p;p=head-next;while(p!=NULL)printf(“%d n”,p-data);p=p-next; ,单链表的建立与输出,void main( ) linklist *head;head=creat( );print(head); ,2006年9月,数据结构 第2章线性表,65,(2)用头插法建立链表,基本思想:生成新结点,将读入的数据存入新结点的数据域中,把新结点作为第一个表结点插入到当前链表的表头结点之后,重复上述过程,直到输入结束标志为止。,2006年9月,数据结构 第2章线性表,66,用头插法新建一个带头结点的单链表过程如下: 调用malloc函数,建立链表的头结
35、点head; 调用malloc函数,建立新的结点p; 给新结点的数据域赋值,将新结点的指针域指向head所指的结点; 将链表头结点head的指针域修改为新结点p; 重复上述步骤,直至输入结束标志0时为止。,图2.7 将新结点p插到单链表head的表头,/* 建立单链表算法用头插法建立带头结点单链表-1 */ linklist * hhead_creat() datatype x; linklist *head, *p; /* head为头指针 */head=(linklist *)malloc(sizeof(linklist);head-data=-999; /* 给表头结点数据域赋值 */h
36、ead-next=NULL;clear(); /* 函数是清屏光标定位和设置颜色 */printf(“nt请随机输入一组正整数以0结束输入:nt“);scanf(“%d“, /* 输入第一个结点数据值 */,/* 建立单链表算法用头插法建立带头结点单链表-2 */while (x!=0) /* 输入数据,以0为结束符 */ p=(struct node*)malloc(LEN); /* 生成新结点 */p-data=x; /* 给新结点的数据域赋值 */p-next=head-next; /* 将新结点插入表头结点head之后 */head-next=p;scanf(“%d“, /* 函数返回
37、链表头指针head */ /* CREATE_HEADHEAD*/,2单链表的查找运算,链表的查找:由于逻辑相邻的结点并没有存储在物理相邻的单元中,所以链表的遍历或查找运算,不能像顺序表那样随机访问任意一个结点,而只能从链表的头指针head出发,顺着链域next逐个结点往下搜索,直到找到所需要的结点为止或者当链表为空时结束查找。 (1)按值查找运算 (2)按序号查找运算(不讲),(1)按值查找运算,按值查找运算是在带头结点的单链表中查找是否存在给定值为keyx的结点。 按值查找运算的基本思想:从链表的头结点开始,依次将链表中结点的数据域与keyx进行比较,若找到给定值keyx,则查找成功,函数
38、返回该结点的位置;若没有找到给定值keyx,则查找失败,函数返回NULL。,/* 带头结点的单链表的查找算法按值查找运算 */ /* 查找值为keyx的结点,若找到则返回该结点的位置,反之则返回0 */ linklist *key_search(head, keyx) linklist *head; datatype keyx; linklist *p=head-next; /* 从头结点开始扫描 */while(p!=NULL) /* 查找失败,函数返回空指针 */* KEY_SEARCH */,3单链表的插入运算 (在单链表中值为keyx结点后或前插入新结点),假设指针p指向单链表中值为k
39、eyx结点,指针s指向值为x的新待插结点。若将新结点s插入结点p之后,则称为后插;若将新结点s插到结点p之前,则称为前插。 (1)后插运算 (2)前插运算,(1)后插运算,在链表中某个结点p之后插入值为x的新结点s,其插入过程如下: 生成一个新结点s,将x值赋给s结点的数据域; 在单链表上查找新结点s的插入位置p; 修改有关结点的指针域,将s结点的后继指向原p结点的后继结点,而p结点的后继则指向s结点。,图2.8 单链表上插入运算的过程示意图,/* 带头结点的单链表上的后插运算 */ /*在带头结点单链表中插入值为x结点并保持链表有序性 */ linklist *data_insert(lin
40、klist *head, int x, int keyx) linklist *s, *p;s=(struct node*)malloc(LEN); /* 建立新结点 */s-data=x; /* 将x值赋给sdata */p=key_search (head, keyx); /* 寻找结点值为x插入位置p */s-next=p-next; /* 新结点s后继指向原p结点后继 */p-next=s; /* p结点的后继指向新结点s */return(head); /* 返回带头结点的单链表头指针*/ /* INSERT_DATA */,/* 带头结点的单链表上的后插运算 */ /* 在带头结点
41、的有序表中查找结点的合适的插入位置 */ linklist *findnode(head, x) linklist *head; int x; linklist *p=head;while(p-next!=NULL) /* 返回结点的合适的插入位置 */ /* FINDNODE */,(2)在单链表中值为keyx结点前插入值为x的新结点 其插入过程如下: 从表头结点开始查找值为keyx结点,若找到,p指向该结点,q 指向p的前驱结点; 若找到值为keyx结点,则: 生成一个新结点s,将x值赋给新结点s的数据域; 修改有关结点的指针域,将s结点的后继指向原q结点的后继,q结点的后继则指向新插入结
42、点s。,图2.8 单链表上插入运算的过程示意图,/* 在带头结点的单链表的值为keyx结点前插入值为x的结点 */ linklist *address_insert(head, x, keyx) linklist *head; int x, keyx; linklist *s, *q; /* q为p的前驱结点 */s=(struct node*)malloc(LEN); /* 建立新结点s */s-data=x; /* 将x值赋给sdata */q= keyx _bsearch(head, keyx); /* 寻找结点前驱结点q */s-next=q-next; /* 新结点s后继指向原q结点
43、后继 */q-next=s; /* q结点的后继指向新结点s */return(head); /* 返回带头结点单链表头指针*/ /* INSERT_DATA */,带头结点单链表上的前插运算,4单链表的删除运算,假设在链表中删除数据值为x=34的结点,并由系统收回其占用的存储空间,单链表中删除某个指定结点过程如下: 假设有两个指针p和q,p指向要删除的结点,q指向p的前驱结点; 从链表head的头结点开始,依次向后进行搜索,当q-next=p 并且p-data=x时,则待删除结点p的前驱结点q被找到; 修改p的前驱结点q的指针域,将q的后继指向被删除结点p的后继结点; 删除p结点并释放该结点
44、所占用的存储空间。,图2.9 链表的删除示意图,/* 在链表中查找该结点,若存在则删除之,反之则提示错误信息 */ linklist *key_delete(linklist *head, int x) linklist *p, *q; /* p是被删除结点,q是p的前驱结点 */q=head; q=head-next;while(p!=NULL) /* KEY_DELETE */,带头结点单链表的删除算法删除值为x的结点,【例2.4】编写算法,在带头结点的单链表中删除第k个结点。 【算法分析】要使删除运算简单,就必须找到第k个被删除结点前驱结点,即第k1个结点p,然后再删除p的后继结点。/*
45、 带头结点单链表的删除函数删除第k个位置结点 */ linklist *no_delete(head) linklist *head; linklist *p, *no_search();int k;printf(“ntt请输入删除结点的位置k=“); scanf(“%d“, ,p=no_search(head, k-1); /* 寻找第k个结点前驱p */ if (p!=NULL) p-next=p-next-next; /* 若该结点存在,则删除该结点 */return(head); /* 返回链表头指针*/ else printf(“ntt要删除的第%d个结点不存在,请重新输入数据! n
46、 “, k);return (NULL); /* 返回空指针*/* DELETE_NUMBER */,/* 带头结点单链表的删除函数删除第k个位置结点 */,【例2.5】编写算法,将带头结点的单链表head逆置,要求利用原表空间就地逆置。 【算法分析】我们可将单链表的逆置运算看成这样的一个过程:先依次将原链表结点删除,然后用头插法建一个新表。过程如下: 利用原表的结点空间,建立逆置链表头结点; 删除原链表的第一个表结点,并保存原链表的下一个结点指针; 将原链表删除的第一个结点,用头插法插入逆置后的新链表中; 重复上述删除和插入操作,直到原链表为空时结束。,void reverse_linkli
47、st(head) linklist *head; linklist *p, *s;if (head=NULL) head=hrear_creat(head);p=head-next; /* 指针p指向表的第一个结点 */head-next=NULL; /* 将逆置后链初态设置为空表 */while(p!=NULL) s=p; /* s指向原表当前准备逆转结点 */p=p-next; /* 将s表头结点从原链表中删除 */s-next=head-next;head-next=s; /* 用头插法将s插到逆置表中 */ /* REVERSE_LINKLIST */,【例2.5】实现带头结点单链表上的逆置算法,2.3.3 单链表上查找、插入和删除运算的时间分析,假设Pi是单链表上查找第i个数据元素的概率,在长度为n的带头结点的单链表中可能进行查找的位置为i=0, 1, 2, , n。在等概率情况下P1=P2=Pi=1/(n+1)。若查找成功时,则单链表中查找任意位置上数据元素的平均比较次数为:,