2.28k likes | 2.45k Views
数 据 结 构 东北大学 信息学院软件所 李景银 版权所有 侵权必究 二 00 二年二月. 书山有路勤为径 学海无涯苦作舟. 第一章 绪论 本章概要 本章的主要内容是介绍和数据结构相关的一些初始概念、术语以及算法的描述和对其性能的评价。通过学习应当掌握: * 数据、数据元素、数据对象、数据类型、数据结构与存储结构; * 四种基本的数据结构与两种存储结构; * 用 C 语言描述算法的基本格式; * 对算法性能的评价标准;
E N D
数 据 结 构 东北大学 信息学院软件所 李景银 版权所有 侵权必究 二00二年二月
书山有路勤为径 学海无涯苦作舟
第一章绪论 本章概要 本章的主要内容是介绍和数据结构相关的一些初始概念、术语以及算法的描述和对其性能的评价。通过学习应当掌握: * 数据、数据元素、数据对象、数据类型、数据结构与存储结构; * 四种基本的数据结构与两种存储结构; * 用C语言描述算法的基本格式; * 对算法性能的评价标准; * 算法的时间复杂度与空间复杂度的估算; 3
一.有关数据的概念 1、数据(data)的形式定义 数据是对客观实体的一种描述(表现)形式。 实体可以分为可 见的与抽象的两类。在计算机领域多为抽象实体。 *常用的几种数据形式: 1)数值数据:是用0到9十个数字的组合描述一个实体。 2)符号数据是用公认的一些符号的组合描述一个实体。这种数据具有广泛性、模糊性。 3)图像(图形)数据是用图像、图形描述一个实体。 这种数据能 直观的表现实体各部分之间的关系,便于我们了解分析实体的本质 。正因为如此,虽然处理复杂,但是,在许多领域我们仍然要使用 这种数据形式。 4)语音数据是用自然语言描述一个实体。 总之,在计算机科学领域,凡是计算机能识别与处理的数字、符号、图像、图形、语音以及它们的汇集通称数据。
2 、 信息(information) 及信息与数据的关系 信息是客观世界向人们或向计算机提供的新知识。所谓新知识 是指人们不知道、 不了解的客观事物。我们任何一个人都是从无 知到有知识的,从不认识客观世界到认识客观世界的。那么,人们 是通过什么方式认识客观世界的呢?无非是通过上述四种数据形式。 所以说:数据是载荷信息的一种物理符号。 数据的形式随着设备、 环境的不同而异。但是 , 信息不受数据形式变化的影响。 在某种 意义上讲信息更能反映客观世界的本质。由上可知信息与数据是不 可分割的。 所以,在许多场合信息与数据可以互相代替另外,信息 的度量单位是熵。其含义是不肯定性的大小,不知道的程度。 抽象、描述 去旧取新、抽取 学习、积累 客观实体 数据 信息 知识
3、数据元素(data element) 是系统中数据的基本单位(即在内存中具有可访问地址号的最小数据单位)。在实际应用中一个数据元素往往是有几部分组成,其中每一部分称为一个数据项(数据项是数据处理时不可再分割的最小数据单元)。每一个数据项都有一个值,习惯上称这个值为关键字。应用时,关键字又分主关键字与次关键字。 主关键字是指它能唯一的标识一个数据元素 如表1中学号2026为主关键字。 下表1为一张学生的登记卡,在学籍管理系统中为一个数据元素。 用C语言中的结构类型描述如下 :Structxs{Int:xh;Char:xm[10];Char:xb[];Int:nl;Char:jg[10];};其中保留字Struct定义一个结构,标识符xs是该结构的名称,五个数据项xh、xm、xb、nl、jg分别表示学号、姓名、性别、年龄、藉贯 。 5 表1 学生登记卡(数据元素) 数据项 关键字
4、数据对象(data object)是性质相同的数据元素的集合,是数据的一个子集。例如,整数数据对象是集合:N={0,1,2,。。。} 5、数据类型 (data type) 是把主要特性相同的一些独立的数据个体用一个标识符来代表它们,那么就构成一个数据的群体,习惯上称这个群体为数据类型。在高级程序语言中的整型数就是整数类型,实型数就是实数类型。(1)取值范围。(2)操作运算。 6、数据结构(data structure) 数据结构的形式定义: 数据结构是数据本身固有的(本身固有是指他人或先人已经设计好的且经过实践证明合理的数据形式)以及数据与数据之间的关系。本书中主要讨论数据之间的关系。一般表示为一个二元组: D ata-Structure = (D,S) D是数据元素的有限集合,S是D上关系的有限集。
数据结构的实用定义 : 在数据处理时,为了用户存取、查找、修改、更新、 插入、删除等操作方便,对系统中提供的原始数据进行必 要的得加工与组织,而经过人们加工得到的数据型式称为 数据结构。它是一种抽象的逻辑结构(logical structure)。 7、存储结构(storage structure) 存储结构是数据结构在存储介质上的具体表现形式。 这是一种实际的物理结构(physical structure)。 5数据结构与存储结构在应用中是不可分割的。
为说明它们的关系下面我们举个例子:假设有十块木牌,每块木牌上写有从0到中的一个数字,无规则的堆放在桌子上。现在,要求按照木牌上数字从大到小的顺序挂到十个门上。为说明它们的关系下面我们举个例子:假设有十块木牌,每块木牌上写有从0到中的一个数字,无规则的堆放在桌子上。现在,要求按照木牌上数字从大到小的顺序挂到十个门上。 原始数据:9、7、4、2、1、5、0、6、8、3;(无规则) 数据结构:0、1、2、3、4、5、6、7、8、9;(递增有序) 存储结构如下图示:
顺序存储 0 1 2 3 4 5 6 7 8 9 1 2 0 5 3 7 6 4 9 8 链式存储 从表中可以看出:*顺序存储时,逻辑上相邻的数据其在内存的物 理位置一定相邻。*链式存储时,逻辑上相邻的数据其在内存的物 理位置不一定相邻。现在,我们可以归纳出下面两点:1)一种数 据结构可以对应多种存储结构。但是,在一个系统中一种数据结构 只能使用一种存储结构。2)存储结构在表现形式上可以与数据结 构相同,也可以不同。但是,它们都必须能准确无误地保证原数据 结构的逻辑关系。幻灯片 7 8 图1-1 两种存储结构图示
8、 抽象数据类型(Abstract Data Type, ADT) 是指一个数学模型以及定义在该模型上的一组操作。起定义仅取决于它的一组逻辑特性,而与其在计算机内部如何表示和实现无关,即不论起内部结构如何变化,只要它的数学特性不变,都不影响其外部的使用。 抽象数据类型可用三元组表示: (D,S,P) D是数据对象,S是D上的数据关系集,P 是对D的基本操作集。
二、数据结构的主要内容 (一)理论知识方面幻灯片 11 1、计算机科学领域常用的四种数据结构 (1)集合结构 数据元素之间没有固定关系。 ( 2)线性结构 数据元素之间有一对一应关系。 (3)树型结构 数据元素之间有一对多的关系。 (4)图形(象)结构 数据元素之间有多对多的关系 (1)集合结构 ( 2)线性结构 (3)树型结构 (4)图形结构
2 、两种基本的存储结构 (1)顺序存储 按照线性表中元素的顺序把数据依次存储到地址连续的存储区间。 (2)链式存储 按照线性表中元素的顺序把数据存储到任意的地址区间. (3)索引存储方法 存储数据的同时,建立一个索引表。 (4)散列存储方法 根据元素的关键字计算出其存储地址。 3、基本知识点 * 线性结构:线性表、链表、栈与队列、串、数组与广义表。 * 非线性结构:树与二叉树 (二叉树的定义、性质、存储、遍历、 线索二叉树 哈夫曼树); * 非线性结构:图(图的概念、存储、遍历、图的应用最小代价生成树、拓扑 排序、关键路径); * 集合结构:查找(顺序查找、折半查找, 顺序索引查找二叉排序查找、平衡二叉排序查找、B-树、B+树、哈希查找); 排序(直接插入、希尔排序、快速排序二路归并、堆排序、基数排序)。 10
(二)技能培养 1根据系统提供的原始数据与用户的要求能设计或选择一种合理的数据结构。幻灯片 9 2在一种数据结构上能方便的执行存取、修改、更新、插入删除等操作。 3能编写完成上述各种功能的各种算法 4依据算法用熟悉的程序语言编程上机运行,验证算法的性能优劣。 三、算法(algorithm)以及对算法性能的评价 1、算法的定义算法是对某类特定问题求解步骤的描述。它应满足下列特性: (1)有零个或多个输入量; (2)每一步都有唯一确定的含义; (3)至少有一个输出量; (4)执行有限步后自动停止; (5)能用现有的程序语言实现编程上机运行; • 11
2、算法的描述方式 (1)自然语言描述. (2)流程框图描述. (3)形式语言描述.本书采用C、C++语言来描述算法; 3、对算法性能的分析 虽然, 算法不是程序不能上机运行,但是对算法性能的分析,完全可以用对程序性能的分析方法进行.
(1) 算法的时间复杂性(time complexity) 算法的时间相当于程序时间中的运行时间部分。同样,关键操作的次数对时间复杂性的影响最大。假设, 算法中关键操作执行的次数是问题特征(规模)n 的某个函数f(n)。那么,算法的时间量度(复杂性)记作: T (n) = O(f(n)) 它表示随问题特征n的增大,算法中关键操作执行时间的增长率和f(n) 的增长率相同,所以称f(n)为算法的时间复杂性。在多数情况下, 算法中关键操作执行的次数和包含它的语句的频度相同。 语句的频度(frequency count)指的是该语句重复执行的次数。所以,在实际应用时,用算法中语句的最大频度作为算法的时间复杂性。例如:在下面的三个程序段中 (a){++x;s=0} ;(b)for(i=1;i<=n;++i){++x;s+=x;} (c)for (j=1;j<=n;++j) for (k=1;k<=n;++k) {++x;s+=x;} 关键操作++ x的语句的频度分别为1, n , n²,则这三个程序段的时间复杂性分别为O(1), O(n),O(n² )分别称为常数阶,线性阶,平方阶。 16
2)算法的空间复杂性(space complexity) 算法的空间相当于程序所需空间中的可变空间部分SP。所以,算法所需空间的量度记作: S (n)=O(f(n)) 其中N 为问题特征。表示除输入和程序之外所需的额外空间。 四、数据结构在计算机科学领域的作用 数据结构是计算机科学与技术专业的专业基础课,是学习数据库、操作系统,计算机网络等专业课程必备的先导课。下面通过几个例子说明数据结构与几门专业课程的关系: 1、数据结构与数据库 数据库是把人们经常共用的一些数据(或函数),事先存储在内存的某个区域,用户需要时可以直接从这个区域中调出。习惯上称存放数据的这个区域为数据库. 17
灯 丝 锤 假设, 下图为一个仓库, 现有三种物品“锤子”、“灯泡”、“保险丝”要求存放到仓库中。当然,不同的用户有不同的放法,观察下面两图,它们都能完成对物品的存放。 锤 丝 灯 按物品消耗率存放 按物品重量存放 图1-2 数据库存放数据的抽象描述 2 数据结构与操作系统 操作系统的分时系统、动态存储管理等都利用数据结构中的知识 18
练习题 1、什么是数据,数据元素、数据项?何谓主关键字?幻灯片 5 2、什么是数据结构?什么是存储结构?它们各有多少种形式? 3、衡量一个算法性能好坏的标准是什么? 4、算法与程序的主要区别是什么? 5、如果两个算法的平均时间复杂度相同,能否说,这两个算法的实际执行时间一定相等? 6、假设两中算法的时空性能表示如下: Tav=0. 6n ² ; Tav = 2n ² ; Sax = 1.1n ² ; Sax = 1.5n ² ; 试比较两算法性能的优劣,问两算法的时间复杂度是否相同?它们实际的运算速度是否一样? 19
本章概要 本章主要介绍线性表的逻辑结构、各种存储结构的描述形式以及在两种存储结构上的基本运算。通过学习掌握: *线性结构的特点,即数据元素之间的线性关系。 *线性表的顺序存储的定义、特征及描述方法。 *线性表的链式存储的定义、特征及描述方法。 *线性表的顺序存储上的各种运算。 *线性表的链式存储上的各种运算。 *线性表的顺序存储、链式存储在时空性能上的差异。 *用C语言描述数据类型及顺序存储、链式存储各种运算算法。 第二章 线性表 20
一、线性表的定义 线性表(Linear-list)是含有n(n≧0)个数据元素的有限序列 一般表示为(a ₁,a₂,a₃,····,an) n为表的长度(表中数据元 素的个数)。当n=0时,称其为空表。对于一个非空的表,其中任一 元素ai(1≦i≦n),代表构成该表的一个实体。表中a1a2a3···an-1称 为元素an的前趋元素,a1称为a2的直接前驱,a1没有直接前驱元素. a2a3··· an 称为a1的后继元素, a2称为a1的直接后继元素,an没有 直接后继元素。即表中除了a1、an以外其余的每个元素都只有一个 直接前驱和一个直接后继元素。 线性表(a1,a2 ,a3,····an)的数据结构: LinearList =( D,R ) 其中,D ={ai∣ai∈ D0,i= 1,2,3…n,n ≧0 } R ={N},N ={<ai-1,ai>∣ai-1,ai∈ D0, i = 2,3,…,n}。 D0为某个数据对象。
二、线性表上的基本操作 1、INITATE(L)初始化操作。 设定一个空的线性表L。 2 LENGTH(L)求长度函数。计算线性表L中数据元素的个数。 3 GET(L,i)取元素函数。得到线性表L中第I个数据元素。 4 PRIOR(L,elm)求前驱函数.求已知元素elm的前驱元素。 5 NEXT(L,elm)求后继函数。求已知元素elm的后继元素。 6 LOCATE(L,x)定位函数。计算线性表L中与x相同的第一个元素在表中的位置序号。 7 INSERT(L,i,b) 前插操作 。 把新数据元素b插到表L中第i个元素之前。 8 DELET(L,i)删除操作。 线性表L中第i个数据元素删除。 9 EMPTY (L) 判空表函数 。若L为空表返回“true”.否则,为false. 10 CLEAR(L) 把表L置空操作。
三、顺序存储(Sequential storage、 Mapping) 1、顺序存储是指把线性表中的元素按照其顺序依次存储到内存中从某已知地址开始物理位置连续的区间。 例如线性表( a1 a2 a3 a4 )只需4个存储单元,但它只能存储在图(1)的区域。图(2)中虽然也有大于线性表所需的存储空间,但是,不能用来存储这个表。因为图2中没有位置连续的4个可用单元,若用图2必将数据A或B进行移动。 0 1 2 3 4 ······ m-1 图2- 1 a1 a2 a3 a4 ….. 图2- 2 A B 假设表中每个元素都占用l个存储单元,而第一个元素的存储地 址LOC(a1)为基址。那么,表中第i 个数据元素ai的存储地址为: LOC(ai) = LOC(a1) + (i – 1) * l l>=1 • i为表中第i 个数据元素ai的下标,不是存储空间的下标。
2 顺序存储的实现 在高级程序语言中一般都用数组来描述顺序存储。在C语言中可用动态分配的一维数组描述如下。 define LIST_INIT_SIZE 100 //初次分配空间量 define LISTINCREAENT 10 // 每次分配增量 Typeset struct { ElemType *elem; //数组指针表的基址 int length; //当前长度 int listsize; //任何时刻能存最多元素个数 }SqList; 空间量 = 最多元素个数 *每次分配增量(每个元素占用空间量)。
3、构成一个空线性表 Status InitList-Sq(SqList &L) L.elem = (ElemType*)malloc(LIST-INIT-SIZE*sizeof(ElemType)); if(! L.elem)exit(OVERFLOW);// 分配失败L.length = 0;// 数据元素个数为0 L.Listsize =LIST_INIT_SIZE;//最多元素个数 return OK;L.Listsize } 逻辑地址0 1 2 3 …. L.elem 物理地址99 100 101 102 。。。
4、线性表上的插入操作 已知内存中有Sq-List类型的顺序表L(a1…ai-1,ai,…an) 。要求把一个新的数据e插到表中第i个数据之前。 e插入前为图1;e插入后为图2; 0 1 … i-1 i …… n-1 a1 a2 … ai ai+1 …… an-1 an 图2-3 新数据e插入前 0 1 … i-1 i ……… n-1 n a1 a2 … e ai+1 ……… an an+1 图2-4 新数据e插入后
Void Insertlist(seqlist *L,datatype x,int i) • int j ; • if (i <1 ∥ i> L->length+1) Error(position error); • if(L->length+1 >=Llistsize) Error( overflow); • For ( j =L->length-1 ;j>= i –1;j- -) • L->data[j+1] = L->data[j] ; • L->data[ i-1]= x • L->length+ +;
现在,分析该算法的时间复杂性。其时间主要是移动数据操作的次数。假设pi是第i个数据之前插入一个数据的概率,则在长度为n 的表中插入一个数据时移动次数的期望值(平均次数)为 n+1 Eis = ∑Pi(n – i +1) • i=1 假设在线性表的任何位置上插入数据都是等概率的,即 Pi =— 1 那么Eis = ——∑(n – i +1)= — 1 n+1 n 1 2 n+1
a b c d e f g h i 5、线性表上的删 除操作 例如 要求把下表1中第5个数据删 除。注意第5个数据实际上是表中序号为4的元素即数据e. 0 1 2 3 4 (p) 5 6 7 8(q) 表1 删 除前 0 1 2 3 4 5 6 7 8 a b c d f g h i 表2 删 除后
Status ListDelete-Sq(SqList &L, int i,j){ if (( i<1 ) ∥(i > L->length)) return ERROR; // 数据的位置 for ( j = i; j<= L->length-1; j++) L->data[j-1] = L->data[j] ; // 移动数据 L->length- -; // 长度减1 return OK; 上表执行该算法时移动数据的语句执行次数最多为N-1。所以该的时间复杂度为O(N)。 • 删除算法的描述
删除算法的时间估算 假设qi是`删除第i个元素的概率,则在长度为Nn的线性表中删除一个元素所需移动元素次数的期望值(平均次数)为 E di= ∑qi ( n – i ) 如果删除任何位置的元素都是等概率即 qi = — Edi = —∑ (n – i) = — 由此可知该算法的时间复杂性为O(n)。 n i=1 1 n+1 n n 1 n+1 2 i=1
顺序存储小结 1)必须预知最大空间量。 2)每个数据元素都占用相同的存储单元。 3)逻辑上相邻的元素,它们在存储介质上的物理位置一定相邻。 4)在任意位置上的插入与删除元素浪费时间。 5)可以随机查找。 作业: 1、设一线性表的顺序存储,总存储容量为M , 其指针的变化范围为——。 2、线性表的顺序存储,要求每个数据元素都占用 ----- 的存储单元。 3、线性表的顺序存储可以用顺序查找,也可以用 ---查找。 4、线性表的顺序存储的缺点是在任意位置上 ---- 数据与 ---- 数据费时间。 5、线性表的顺序存储是用一组 ____连续的空间单元实现数据元素的存储。
6、编写在内存中生成线性表(a1 a2 a3…an )的算法。 7、已知线性表( a1 a2 a3…an )以顺序的方式存储在内存,编写在表查找值为X的元素,若存在把它与其直接前驱元素交换,否则把X插到表尾的算法。并计算该算法的时间复杂性. 8、编写把线性表( a1 a2 a3…an )的顺序完全倒置的算法。并计算该算法的时间复杂性及决定时间复杂性的语句频度。 9编写把从线性表( a1 a2 a3…an )中值为X 的元素开始到的所有元素顺序倒置的算法。
四、线性表的链式存储1、单链表(singly linked lists) ( 1)、结点(node) 线性表中一个数据元素所占用的存储空间。它由数据域与指针域两部分组成,数据域用来存储用户的有用数据; 指针域用来存储直接后继数据元素所在结点的地址。 数据域 指针域 如右图示: p 在C语言中可用结构指针来描述结点: typedef struct Lnode { ElemType data; struct Lnode * next; }Lnode, *LinkList; P为LinkList型的变量。P->data:存储用户的有用数据。 p ->next:存储下一个结点的地址号。 data next 结点的图型表示
0 1 2 3 4 5 6 7 (2)备(可)用链表 根据用户定义的结点结构计算机在内存中进行,查找空闲单元, 将它们按要求构造成备(可)用结点,并形成一个链表,习惯上称 之为备(可)用链表。备(可)用结点: 是指结点的数据域为空或有无用的数据。 可用来存储用户当前输入的数据。 用户从指针变量P中得到 可以使用的新结点地址。 P 6 1 1 4 3 4 3 P
a b c d e 3)、单链表的定义 如果,构成链表的每个结点都只含有一个指针域, 那么称这个表为单链表.在应用中又分为带头结点与不带头结点两种。分别如图1、图2所示: H H=NULL 不带头结点的空单链表. L 注意: 在头结点的数据域中存储的是不同于有用数据的标示符;尾结点的指针域中存储的是空,标示该结点是表中最后一个结点. ∧ 图2- 5不带头结点的单链表 a b c d e ∧ L 头结点 图 2 -6 带头结点的单链表尾结点 带头结点的空单链表 . L->next=NULL
。。。11 12 13 14 15 16 17 18 19 20 存储地址 数据域 指针域 右图为数据单链表的物理存储结构示意图。 下图为数据单链表的逻辑存储结构示意图。 头指针 15 eat hat cat bat mat fat head
(4)、单链表上的插入操作 从实际应用讲单链表上的插入可以分成三类: 首部插入: 把新结点插在表中第1 个结点之前;这种插入是最简单的插入方式。 无论在任何场合,只要用户没有要求都用首部插入。 尾部插入: 把新结点插在表中最后一个结点之后;这种插入需查找表的当前尾 部结点地址。 条件插入: *把新结点插在表中第I 个结点之前; *把新结点插在表中值为X的结点之前; *把新结点插在表中地址为Y的结点之前; 这三种插入的关键是在查找条件结点的同时,要保存住其直接前驱 结点的地址。因为,从前边图中可以看到,仅知道条件结点的地址 是不能将新结点插入的. 下边我们分别介绍这几种插入方法。
首部插入:把新结点插在表中第1 个结点之前; Link List CreatListF(void) { char ch; LinkList head; // 头指针 List Node * s ; //工作指针 Head = NULL; // 置空表 Ch= getchar(); // 读入第一个字符 While ( ch!= ‘\n’) { s = (ListNode *)malloc ( size(ListNode )); // 生成一个新结点 s->data = ch; // 将有用数据存储到结点数据域 s->next =head; // 将新结点插到链表中 head = s; ch =getchar(); { Return head; { head b c d e ∧ head a b c d e ∧ s
尾部插入(1):把新结点插在表中最后一个结点之后;尾部插入(1):把新结点插在表中最后一个结点之后; Link List CreatList R(void) { char ch; LinkList head; // 头指针 Head = NULL; // 置空表 P= Head; While(p->next!= NULL ) P= p->next ; s = (ListNode *)malloc ( size(ListNode )); // 得到一个新结点地址 Ch= getchar(); // 读入一个字符 s->data = ch; // 将数据存储到结点数据域生成一个新结点 s->next= NULL ; (s->next= p->next ) // 将新结点插到链表中 p ->next = s; Return head; { head b c d e ∧ head b c d e f ∧
尾部插入(2):把新结点插在表中最后一个结点之后;尾部插入(2):把新结点插在表中最后一个结点之后; Link List CreatList R(void) { char ch; LinkList head; // 头指针 List Node * s,*r ; // 工作指针 Head = NULL; r = NULL; // 置空表 Ch= getchar(); // 读入第一个字符 While((ch= getchar();!= ‘\n’) ){ s = (ListNode *)malloc ( size(ListNode )); // 生成一个新结点 s->data = ch; s->next= NULL ; // 将有用数据存储到结点数据域 if ( head =NULL) // 将新结点插到链表中 head = s; else r -> next = s; r = s; { Return head; {
条件插入之一: 已知有一个带头结点的单链表L,现在,要求把一个新的数据c插到表中第i个结点之前。 假设i=3,新的数据就是插在表L 中数据为d的结点之前。上、下图分别为插入前后的表。 L 图2-7 插入c前的表 • L i-1 i a ∕ b d e ∧ ∕ a b c d e ∧ 图2-8 插入c后的表
插入算法如下: (该算法关键是查找第I-1个结点的地址) status ListInsert_L(LinkList &L, int i, ElemType c) { P =L; j = 0; while ( P && j< i -1) { P = P->next; ++j; } if (!P ∥ j> i-1) return ERROR; S =(LinkList) malloc (sizeof( Lnode)); S-> data = c; S->next = P->next; P->next = S; return OK; • }
直接前驱 条件结点 条件插入之二: 已知,L为LinkList类型的变量。是表的头结点地址指针。要求把 一个新的数据元素c插到表中值为d的结点之前。 L ∕ a b d e ∧ 图2-10插入前的表 L ∕ a b c d e ∧ 图2-11插入后的表 新结点
实现算法如下: status ListInsert_L(LinkList &L ,intc ElemTyped){ p =L; q = p->next;(p为前驱结点地址指针变量) while (q ->data != d && q->next != NULL) { p = q ; q = q->next ;} if (! q->data) return ERROR; s = (LinkList) malloc ( sizeof (Lnode)); s->data = c; s->next = p->next; p->next = s; Return OK; } 该算法的时间复杂性仍然是O(N)级。实际上该算法是属于表中的条件插入的一种。同学们可以自己完成其他条件插入。
( 5)、单链表上的删除操作 从实际应用讲单链表上的删除可以分成三类: 首部删除: 这种删除是最简单的删除方式。无论在任何场合,只要用户没有要 求都用首部删除。 尾部删除: 这种删除需查找表的当前尾部结点地址。要保存住其直接前驱结点的 地址。 条件删除: *把表中第I 个结点删除; *把新表中值为X的结点删除; *把表中地址为Y的结点删除; 这三种删除的关键是在查找条件结点的同时,要保存住其直接前驱 结点的地址。因为,从前边图中可以看到,仅知道条件结点的地址是 不能将结点删除的。
status ListDlete_L(LINKlIST &l,int I,ElemYype &e) { P = L; (p为寻找的尾部结点的前驱) q =p->next;(得到第一个数据结点的地址) while (q->next!=NULL) { (查找表的尾部结点地址) p =q;q = q->next;} p->next = q->next;(从中删除尾部结点) e = q->data; (保存被删除结点的数据) free(q);(释放被删除的结点) return OK; } 尾部删除 a b c d e a c d b
条件删除(1) 已知L为带头结点的单链表,要求把表中第i个数据元素删除,并由e返回。实现的算法如下: status ListDlete_L(LINKlIST &l,int I,ElemYype &e) { P = L; j = 0; while ( p->next && j<i –1) (p为寻找的第I个结点的前驱) { p = p->next; ++ j; } if (! (p->next)‖j >i –1) return ERROR; q = p->next; p->next = q->next; e = q->data; free(q);(释放被删除的结点) return OK; } i = 3 a b c d e a d e b • 图2-9 删除第三结点前后
条件删除(2)已知L为带头结点的单链表,要求把表中数据元素值为X的结点删除,并由e返回。实现的算法如下:条件删除(2)已知L为带头结点的单链表,要求把表中数据元素值为X的结点删除,并由e返回。实现的算法如下: status Listdelet_L(LinkList &L ,intc ElemTyped){ p =L;( )q = p->next; (p为前驱结点地址指针变量) while (q ->data != X&& q->next != NULL) { p = q ; q = q->next ;} if (! q->data) return ERROR; else c = q->data; p->next = q->next; Free(q); Return OK; • } X = c a b c d e L a d e b
说明:malloc 与 free是两个标准函数。p 和q是LINKLIST型的变量。 p= (Linklist)malloc(sizeof(Lnode)的作用是系统动态生成一个LNode类型的一个接点,并把该结点的起始位置赋给变量P。free(q)的作用就是把用户释放的一个结点返回系统准备再次生成结点时用。它们的功能完全类似PASCAL语言链表中的函数new(s)与dispose(q)。 关于空间利用率:是对用户申请的空间量与其实际应用的讨论。 用户的数据实际占用的空间量 空间利用率 = 用户申请的总空间量 单链表小结: 1)可用任意空闲地址单元实现对线性表的存储; 2)线性表中元素的逻辑关系,是用指针来保证的; 3)在任意位置上插入与删除数据元素方便; 4)在单链表中查找数据只能用顺序查找方式; 5)空间利用率 比顺序存储低;