ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

线性表详细说明【线性表】(3)

2022-03-20 12:31:28  阅读:239  来源: 互联网

标签:链表 结点 线性表 元素 next 说明 插入 详细


线性表的概念

文字定义

一个线性表是 n 个数据元素的有限序列

这里需要强调的是:
有限:数据元素的个数取值一定是有限的,n 的取值一定是 0(0表示空的线性表)、1、2、3、5、10 等有限的个数。

序列:在满足数据元素的个数有限,数据元素之间的关系要满足有序的关系,所谓有序就是数据元素之间按照先后有顺序。

形式定义

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

省略号只是表示省略,并不是表示中间元素下标是无穷限制的,中间的元素下标一定是有限的个数。

上面的形式定义,数据元素ai (1<=i<=n) 只是一个抽象的符号,其代表的具体含义在不同的情况下可以不同。

强调:也就是说数据元素的类型是没有限制的,根据具体的情况而定,但是要求所有的数据元素都是同一种类型(或者说性质相同)。

举例说明:
①:26个英文字母组成的字母表: (A , B , C ,… Z )
上面的数据元素都是字符类型,也就是性质相同。

②:学生成绩表:90,97,78,75…,84
上面的数据元素都是整型,也就是性质相同。

我们所见到的线性表只要满足 n 个数据元素形成的有限序列即可,数据元素的类型是没有限制的,或者说是各式各样的。

那么我们的线性表元素也可以复杂一点?而不是常规的基本类型。

一个数据元素可以由若干个数据项组成,这时,也可以称数据元素为记录,含有大量记录的线性表又称为 " 文件 "。

例如:
复杂的线性表
上面的形式能不能叫一个线性表呢?
我们可以看到,每一横行数据元素的类型是一样的,如果按照上面的姓名有先后顺序,那么就满足有序,上面的形式就可以是一个线性表。

那么很多读者有数据库的知识,上面的形式和数据库里面的表格非常像。

我们对于上面的表格形式形成的线性表进行总结:

数据元素(结点、记录) 由5和数据项(字段、域)组成。

把上面形式表格形成的线性表我们可以称为文件。

线性表中的数据元素可以是各种各样的,但同一线性表中的元素必定具有相同特性(属于同一数据对象或者说是同一种类型)。

线性表中的数据元素之间存在着序偶关系 <ai–1, ai> ,也就是相邻元素之间有先后顺序。

线性表是最常见最简单的一种数据结构。

线性表

上面图中的圈表示结点或者数据元素,相邻数据元素之间的连线表示它们之间的关系。

线性表的特点:4个唯一

对于数据元素的非空有限序列来说:

存在唯一的一个被称作“第一个”的数据元素;

存在唯一的一个被称作“最后一个”的数据元素;

除第一个之外的数据元素均只有一个前驱;

除最后一个之外的数据元素均只有一个后继。

线性表的特点中 唯一的第一个数据元素唯一的最后一个数据元素 说明数据元素的个数一定是确定的,这也体现了线性表元素个数有限的特点。

线性表的特点中 数据元素的前驱和后继 的说明体现了线性表元数据元素之间有序的特点。

举例说明线性表的特点:
举例说明线性表的特点

线性表的ADT定义

定义阶段和具体的实现无关,只是进行规格说明。

ADT  List   //List  是新定义的类型名称
{
数据对象:D={ ai | ai ∈ElemSet, i=1,2,...,n,  n≥0 }
数据关系:R1={ <ai-1 ,ai >|ai-1 ,ai∈D,  i=2,...,n }
基本操作:
                      结构初始化操作
                      结构销毁操作
                      引用型操作
                      加工型操作 
} ADT  List

基本操作包括:

结构初始化操作:初始化可以理解为定义一个变量。

结构销毁操作:对于销毁结构来说,这个类型是我们自定义出来的,所以管理权限由我们自己来做,销毁操作就是释放存储空间。

引用型操作:不改变元素的值,也不改变元素之间的关系。例如查找数据元素功能。

加工型操作 :在操作的过程当中可能会改变元素的值或者改变元素之间的关系,例如插入数据元素和删除数据元素的操作。

接下来我们使用类C语言来描述ATD的定义。

结构初始化操作

InitList ( &L )
操作结果:构造一个空的线性表 L 。

销毁操作

DestroyList( &L )    
初始条件:线性表 L 已存在。
操作结果:销毁线性表 L。

引用型操作

操作的结果不改变线性表中的数据元素,也不改变数据元素之间的关系。

ListEmpty( L )
初始条件:线性表 L 已存在。
操作结果:若 L 为空表,则返回 TRUE,否则返回 FALSE。

ListLength( L )
初始条件:线性表 L 已存在。
操作结果:返回 L 中元素个数。

PriorElem( L, cur_e, &pre_e )
初始条件:线性表 L 已存在。
操作结果:若 cur_e 是 L 中的数据元素,则用 pre_e 返回它的前驱,否则操作失败,pre_e 无定义。

NextElem( L, cur_e, &next_e )
初始条件:线性表 L 已存在。
操作结果:若 cur_e 是 L 中的数据元素,则用 next_e 返回它的后继,否则操作失败,next_e 无定义。

GetElem( L, i, &e )
初始条件:线性表 L 已存在,1≤i≤LengthList(L)。
操作结果:用 e 返回 L 中第 i 个元素的值。

LocateElem( L, e, compare( ) )
初始条件:线性表 L 已存在,compare( ) 是判定函数。
操作结果:返回 L 中第 1 个与 e 满足关系 compare( ) 的元素的位序。若这种元素不存在,则返回 0。

compare 函数一般情况下使用最多的是判等,在线性表中判断有没有和给定的数据元素 e 相等的数据元素。这里不写 equal 而写的是 compare 也是数据结构比较灵活和高度抽象的体现,当我们不取相等而取其他关系的时候就会更加方便,所以 compare 函数在抽象数据类型中进行抽象的时候是为了使用方便,可以适用多种变化,这也是数据结构需要我们去领悟的精妙所在。

ListTraverse(L, visit( ))
初始条件:线性表 L 已存在,visit( ) 为访问函数。
操作结果:依次对 L 的每个元素调用函数 visit( )。一旦 visit( ) 失败,则操作失败。

加工型操作

操作的结果或修改表中的数据元素,或修改元素之间的关系。

ClearList( &L )
初始条件:线性表 L 已存在。
操作结果:将 L 重置为空表。

PutElem( &L, i, e )
初始条件:线性表 L 已存在,1≤i≤LengthList(L)。
操作结果:L 中第 i 个元素赋值为 e 的值。

ListInsert( &L, i, e )
初始条件:线性表 L 已存在,1≤i≤LengthList(L)+1。
操作结果:在 L 的第 i 个元素之前插入新的元素 e, L 的长度增 1。

ListDelete( &L, i, &e )
初始条件:线性表 L 已存在且非空,1≤i≤LengthList(L)。
操作结果:删除 L 的第 i 个元素,并用 e 返回其值,L 的长度减 1。

注意:所有加工型的操作,参数都要传递地址。

理解:
ADT是自定义的,它会根据我们的研究需要我们自己抽象出来有什么样的操作,所以只要是我们需要用到的,我们都可以对于操作进行抽象定义然后完成表现和实现部分就可以使用。这也是抽象数据结构灵活性的体现。所以对于ADT定义的操作来说,没有对错,只有更适合。

基本操作的应用举例

例如,主程序中有如下代码:

  List  myList;	//定义 List 类型 myList
     //其他代码(省略)
  ListEmpty(myList);   //引用型操作
  ListInsert(myList,3,12);  //加工型操作 (在第三个位置之前插入元素12)

线性表ADT基本操作的简单应用

已知集合 A 和 B,求这两个集合的并集

使 A=A∪B,且 B 不再单独存在。

要在计算机中求解,首先要确定“如何表示集合”。
我们这里使用线性表表示集合。

以线性表 LA 和 LB 分别表示集合 A 和 B,两个线性表的数据元素分别为集合 A 和 B 中的成员。

由此,上述集合求并的问题便可演绎为:
扩大线性表 LA, 将存在于线性表 LB 中而不存在于线性表 LA 中的数据元素插入到线性表 LA 中去。

步骤抽象:
1.从 Lb 中取出一个数据元素;
GetElem ( Lb, i, &e ) ListDelete (&Lb, i, &e );

2.依次在 La 中进行查询; LocateElem ( La, e, equal());

3.若不存在,则插入之。 ListInsert ( &La, n + 1, e );

重复上述三步直至 Lb 中的数据元素取完为止。

算法实现:

void union(List &La, List Lb)
{ 
	La_len = ListLength(La);        
	Lb_len =ListLength(Lb); 
	
    for (i = 1; i <= Lb_len; i++) 
    { 
    	GetElem (Lb, i, &e); // 取 Lb 中第 i 个数据元素赋给 e     
		if(!LocateElem (La, e, equal()))             
 		ListInsert (La, ++La_len, e);   
 		// La 中不存在和 e  相同的数据元素,则插入之 
    } 
 DestroyList (Lb); // 销毁线性表 Lb
 } //union 

时间复杂度:O(ListLength (La) × ListLength (Lb))

合并两个有序表

LA = (3,5,8,11)
LB = (2,6,8,9,11,15,20)
LC = (2,3,5,6,8,8,9,11,11,15,20)

思路:
1.分别从 La 和 Lb 中取得当前元素 ai 和 bj ;
2.若 ai <= bj,则将 ai 插入到 Lc 中然后继续向后走,否则将 bj 插入到 Lc 中然后继续向后走。

不断进行上面过程,直到其中一个有序表走完之后把另一个有序表剩余的数据元素插入到当前LC的末尾。

算法实现:

void MergeList(List La, List Lb, List &Lc)
{
   InitList(Lc);
   i = j = 1; k = 0;
   La_len = ListLength(La);     
   Lb_len = ListLength(Lb);
   
   while ((i <= La_len) && ( j <= Lb_len)) 
   {   // La 和 Lb 均未取完 
        GetElem(La, i, ai ); GetElem(Lb, j, bj );
        if (ai < bj ) 
        {
        	 ListInsert(Lc, ++k, ai ); ++i; 
        }
        else 
        { 
       		 ListInsert(Lc, ++k, bj ); ++j; 
        }
   }
   
   while (i<=La_len) 
   {
   		GetElem(La, i++, ai);  
   		ListInsert(Lc, ++k, ai);
   } 
   while (j<=Lb_len) 
   {
   GetElem(Lb, j++, bj);  
   ListInsert(Lc, ++k, bj);
   } 
}  

时间复杂度:O(ListLength(La) + ListLength(Lb))

在实际的程序设计中要使用线性表的基本操作, 必须先实现线性表类型。

实现包括两部分:
线性表

线性表的顺序表示和实现

线性表的顺序存储结构图示说明

在计算机中用 一组地址连续的存储单元 依次存储线性表的各个数据元素,称作线性表的顺序存储结构或顺序映象。用这种方法存储的线性表称作顺序表。

线性表的顺序存储结构

假设线性表的每个元素需占 k 个存储单元,则第 i + 1 个 元素的存储位置和第 i 个元素的存储位置之间满足关系:
LOC(ai +1) = LOC(ai ) + k

由此,所有数据元素的存储位置均可通过基地址得到:
LOC(ai ) = LOC(a1) + (i - 1) × k

特点:
以物理位置相邻表示逻辑关系;
任一元素均可随机存取,查找和赋值元素非常方便。

随机存取:顺序表中通过位置的计算,可以轻松的进行存取操作而与表长无关。

结论:已知位置、获取该位置上的元素非常方便,与该线性表的长度无关 。

设计顺序表的存储结构:

#define LIST_INIT_SIZE 100  //线性表存储空间的初始分配量

typedef struct 
{
	ElemType  elem[LIST_INIT_SIZE]; 
	int length;   //当前长度
} SqList;  

考虑到线性表因插入元素而使存储空间不足的问题,应允许数组容量进行动态扩充。 (静态顺序存储->动态顺序存储)

#define LIST_INIT_SIZE 100  //线性表存储空间的初始分配量  
#define LISTINCREMENT 10 //线性表存储空间的分配增量 

typedef struct 
{
	ElemType  *elem;   //数组指针,指示线性表的基地址 
	int length;   //当前长度
	int listsize;  //当前分配的存储容量(以sizeof(ElemType)为单位)  
} SqList;  

注意:C 语言中的数组下标从 “0” 开始,因此若 L 是 Sqlist 类型的顺序表,则表中第 i 个元素是 L.elem[i -1]。

线性表的初始化操作

构造空的线性表

构造一个空的线性表(顺序表)

构造一个空线性表

初始化—类c语法描述

bool InitList(SqList   &l)
{
         l.elem=(ElemType *)
            malloc(LIST_INIT_SIZE*sizeof(ElemType));
	if(!l.elem)
		exit  (OVERFLOW);
	l.length=0;
	l.listsize=LIST_INIT_SIZE;
	return  OK;
}

初始化-C语言实现

/*------------------------------------------------------------
操作目的:	初始化顺序表
初始条件:	无
操作结果:	构造一个空的线性表
函数参数:
		SqList *L	待初始化的线性表
返回值:
		bool		操作是否成功
------------------------------------------------------------*/
bool InitList(SqList *L)
{
	L->elem = (ElemType*)malloc(LIST_INIT_SIZE * sizeof(ElemType));
	if (!L->elem)
		return false;
	else
	{
		L->length = 0;
		L->listsize = LIST_INIT_SIZE;
		return true;
	}
}

线性表的插入操作

在第i个位置之前插入新元素:
线性表的插入运算是指在表的第 i (1 <= i <= n +1) 个位置上, 插入一个新结点 b,使长度为 n 的线性表 (a1, …, ai –1, ai, …, an)
变成长度为 n + 1 的线性表 (a1, …, ai –1, b, ai, …, an)

算法思想

1)检查 i 值是否超出所允许的范围 (1 <= i <= n + 1) ,若超出, 则进行“超出范围”错误处理;
2)将线性表的第 i 个元素和它后面所有元素均后移一个位置;
3)将新元素写入到空出的第 i 个位置上;
4)使线性表的长度增 1。

插入的位置包括线性表尾部,线性表头部和线性表的中间位置。
线性表的插入

算法实现

Status ListInsert_Sq(Sqlist &L,int i,ElemType e)
{
	if(i<1||i>L.length+1) 
	return ERROR;
	
	if(L.length>=L.listsize)
	{   //当前存储已满,增加分配
		newbase=(ElemType*)realloc(L.elem,
       (L.listsize+ LISTINCREMENT)*sizeof(ElemType));
		if(!newbase) 
			exit(OVERFLOW);
		L.elem=newbase;
		L.listsize+= LISTINCREMENT;
	} 
	q=&(L.elem[i-1]);
	for(p =&(L.elem[L.length-1]);p>=q;--p) 
		*(p+1)=*p;
	//插入位置之后元素后移
  	*q=e;
	++L.length; 
 	return  OK;
}

C语言实现插入数据元素操作

/*------------------------------------------------------------
操作目的:	在顺序表的指定位置插入结点,插入位置i表示在第i个
			元素之前插入
初始条件:	线性表L已存在,1<=i<=ListLength(L) + 1
操作结果:	在L中第i个位置之前插入新的数据元素e,L的长度加1
函数参数:
		SqList *L	线性表L
		int i		插入位置
		ElemType e	待插入的数据元素
返回值:
		bool		操作是否成功
------------------------------------------------------------*/
bool ListInsert(SqList *L, int i, ElemType e)
{
	ElemType* newbase = NULL;
	if (i < 1 || i>L->length + 1)
		return false;
	if (L->length == L->listsize)
	{
		newbase = (ElemType*)realloc(L->elem, (LIST_INIT_SIZE + LISTINCREMENT) * sizeof(ElemType));
		if (!newbase)
			return false;
		else
		{
			L->elem = newbase;
			L->listsize += LISTINCREMENT;
		}
	}
		for (int j = L->length; j >= i; j--)
			L->elem[j] = L->elem[j - 1];
		L->elem[i - 1] = e;
		++L->length;
		return true;
}

插入算法的时间复杂度分析

问题规模是表的长度,设它的值为 n。

算法的时间主要花费在向后移动元素的 for 循环语句上。该 语句的循环次数为 (n– i +1)。由此可看出,所需移动结点的次数不仅依赖于表的长度 n,而且还与插入位置 i 有关。

当插入位置在表尾 (i=n +1) 时,不需要移动任何元素;这是 最好情况,其时间复杂度 O(1)。

当插入位置在表头 (i = 1) 时,所有元素都要向后移动,循环语句执行 n 次,这是最坏情况,其时间复杂度 O(n)。

算法的平均时间复杂度:设 pi 为在第 i 个元素之前插入一个元素的概率,则在长度为 n 的线性表中插入一个元素时所需移动元素次数的期望值为:
元素移动次数的期望值

假设在表中任何位置 (1  i  n+1) 上插入结点的机会是 均等的,则

计算机结果

由此可见,在顺序表上做插入运算,平均要移动表上一半元素。当表长 n 较大时,算法的效率相当低。算法的平均时间复杂度为 O(n)。

线性表的删除操作

线性表的删除运算是指将线性表的第 i (1 <= i <= n) 个结点删除,
使长度为 n 的线性表 (a1, …, ai –1, ai, ai +1, …, an)
变成长度为 n -1 的线性表 (a1, …, ai –1, ai +1, …, an)

算法思想

  1. 检查 i 值是否超出所允许的范围 (1 <= i <= n),若超出,则进 行“超出范围”错误处理;
  2. 将线性表的第 i 个元素后面的所有元素均前移一个位置;
  3. 使线性表的长度减 1。

删除尾部元素,删除中间元素,删除头部元素
线性表的删除操作

算法实现

Status ListDelete_Sq(SqList &L, int i, ElemType &e) 
{
	if ((i <1) || (i > L.length)) 
		return ERROR;   // 删除位置不合法      
	p = &(L.elem[i -1]);   // p为被删除元素的位置
	e = *p;   // 被删除元素的值赋给 e
	q = L.elem+L.length-1;   // 表尾元素的位置
	
	for (++p; p <= q; ++p) 
		*(p-1) = *p;   
     	// 被删除元素之后的元素左移
     	
    --L.length;   //线性表长度减 1
    return OK;
}//ListInsert_sq 

C语言实现删除数据元素操作

/*------------------------------------------------------------
操作目的:	删除顺序表的第i个结点
初始条件:	线性表L已存在且非空,1<=i<=ListLength(L)
操作结果:	删除L的第i个数据元素,并用e返回其值,L的长度减1
函数参数:
		SqList *L	线性表L
		int i		删除位置
		ElemType *e	被删除的数据元素值
返回值:
		bool		操作是否成功
------------------------------------------------------------*/
bool ListDelete(SqList* L, int i, ElemType* e)
{
	if (i < 1 || i>L->length)
		return false;
	*e = L->elem[i -1];
	for (int j = i - 1; j < L->length - 1; j++)
	{
		L->elem[j] = L->elem[j + 1];
	}
	--L->length;
	return true;
}

void ListDeleteHead(SqList* L, ElemType* e)							//线性表的头删法
{
	ListDelete(L,1, e);
}
void ListDeleteTail(SqList* L, ElemType* e)							//线性表的尾删法
{
	ListDelete(L, L->length, e);
}

删除算法的时间复杂度分析

问题规模是表的长度,设它的值为 n。

算法的时间主要花费在向前移动元素的 for 循环语句上。
该语句的循环次数为 (n – i)。由此可看出,所需移动结点的次数不仅依赖于表的长度 n,而且还与删除位置 i 有关。

当删除位置在表尾 (i = n) 时,不需要移动任何元素;这是最好情况,其时间复杂度 O(1)。

当删除位置在表头 (i = 1) 时,有 n -1 个元素要向前移动,循环语句执行 n -1 次,这是最坏情况其时间复杂度 O(n)。

算法的平均时间复杂度:设 qi 为删除第 i 个元素的概率,则在长度为 n 的线性表中删除一个元素时所需移动元素次数的期望值为:

数学期望值

假设在表中任何位置(1  i  n)删除结点的机会是均等的, 则:

计算结果
由此可见,在顺序表上做删除运算,平均约要移动表上一半元素。当表长 n 较大时,算法的效率相当低。算法的平均时间复杂度为 O(n)。

线性表的链式表示和实现

线性表的链式说明

用一组物理位置任意的存储单元来存放线性表的数据元素。 这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。因此,链表中元素的逻辑次序和 物理次序不一定相同。

线性表的顺序存储和链式存储的对比
上面逻辑关系的链式表示
上面单链表中 ^ 表示指向为空,也就是指向NULL。

单链表是由头指针唯一确定,因此单链表可以用头指针的名字来命名。

单链表的表示

单链表在 C 语言中可用“结构指针”来描述:

typedef  struct  Lnode
{  
        //声明结点的类型和指向结点的指针类型  
        ElemType         data;    //数据元素的类型 
        struct   Lnode  *next;   //指示结点地址的指针  
}Lnode, *LinkList;               

图解说明
Lnode 是结构体类型。
LinkList 是指向 Lnode 结构体类型的指针。

举例说明:

struct Student
{ 
	char num[8];   //数据域
  	char name[8];  //数据域
 	int score;     //数据域
 	struct Student *next;  // next 既是 struct Student 
                           // 类型中的一个成员,又指 
                           // 向 struct Student 类型的数据。 
}Stu_1, Stu_2, Stu_3, *LL;  

图解说明

头结点:在单链表的第一个数据元素结点之前人为设置的一个结点。

头节点图解说明:
头节点图解说明

头指针存放头结点的地址:

头指针存放头结点的地址

上面的 a1 是线性表的第一个数据元素所在的结点,a1 也就是首个元素所在的结点,我们这里简称首元结点。

如果线性表是一个空表,那么就让头节点的next域指向为空。

单链表的基本操作

单链表的查找操作

按序号查找(GetElem(L, i, &e)在链表中的实现)

在单链表中,即使知道被访问结点的序号 i ,也不能像顺序表中那样直接按序号 i 访问结点,而只能从头指针出发,顺链域 next 逐个结点往下搜索,直到搜索到第 i 个结点为止。因此,单链表是非随机存取的存储结构。

设单链表的长度为 n,要查找表中第 i 个结点,仅当 1 <= i <= n 时,i 的值是合法的。其算法如下:

算法实现
Status GetElem_L(LinkList L, int i, ElemType &e) 
{  
 	p = L -> next;
 	j = 1; // 初始化,p 指向第一个结点,j 为计数器 
     while ( p && j < i )
     { 
        p = p -> next; 
        ++j;
     } 
     if ( !p || j > i ) 
     	return ERROR;    // 第 i 个元素不存在     
     e = p -> data; // 取第 i 个元素
     return OK; 
} // GetElem_L 

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

C语言实现单链表按序号查找
/*------------------------------------------------------------
操作目的:	得到链表的第i个元素
初始条件:	线性表L已存在,1<=i<=ListLength(L)
操作结果:	用e返回L中第i个数据元素的值
函数参数:
		LinkList L	线性表L
		int i		数据元素的位置
		ElemType *e	第i个数据元素的值
返回值:
		bool		操作是否成功
------------------------------------------------------------*/
bool GetElem(LinkList L, int i, ElemType *e)
{
	LinkList p = L->next;
	int j = 1;
	while (p && j < i)
	{
		p = p->next;
		i--;
	}
	if (!p ||j > i)
		return false;
	*e = L->data;
	return true;
}

按值查找(GetElemElem( L, e) 在链表中的实现)

按值查找是在单链表中查找结点值等于给定值 key 的结点,若有的话,则返回首次找到的其值为 key 的结点的存储位置;否则返回 NULL。

算法实现
Status GetElem_L1(LinkList L1, ElemType key) 
{ 
   p = L1 -> next; 
   while ( p && p -> data!=key)
   		p = p -> next; 
   return p; 
} // GetElem_L1 
C语言实现单链表按值查找
LinkList GetElem_L1(LinkList L1, ElemType key) 
{ 
   p = L1 -> next; 
   while ( p && p -> data!=key)
   		p = p -> next; 
   return p; 
} // GetElem_L1 

该算法的执行时间与 key 有关,时间复杂度为:O(n)

在单链表中插入数据元素(ListInsert(&L, i, e)在链表中的实现)

步骤:
1、首先找到 ai -1 的存储位置 p。

2、生成一个数据域为 e 的新结点。

3、插入新结点:
①、新结点的指针域指向结点 ai 。
②、结点 ai –1 的指针域指向新结点。

链表插入元素的图解说明

s ->next = p -> next; 
p -> next = s; 
算法实现
Status ListInsert_L(LinkList &L, int i, ElemType e)
 { 
	p = L; 
    j = 0; 
    while ( p && j < i - 1) 
    { 
    	p = p -> next; 
    	++j; 
    }    // 寻找第 i – 1 个结点 
    
	if (!p || j > i -1) 
          return ERROR;     // i 小于 1 或者大于表长+1
	s = (LinkList) malloc(sizeof(LNode));    // 生成新结点 
	s -> data = e;  
	s ->next = p -> next;    // 插入 L 中  
	p ->next = s;  
	return OK; 
 } // LinstInsert_L 

时间复杂度:O(n)。

C语言实现在单链表中插入数据元素
/*------------------------------------------------------------
操作目的:	在链表的指定位置插入结点,插入位置i表示在第i个
			元素之前插入
初始条件:	线性表L已存在,1<=i<=ListLength(L) + 1
操作结果:	在L中第i个位置之前插入新的数据元素e,L的长度加1
函数参数:
		LinkList L	线性表L
		int i		插入位置
		ElemType e	待插入的数据元素
返回值:
		bool		操作是否成功
------------------------------------------------------------*/
bool ListInsert(LinkList L, int i, ElemType e)
{
	LinkList p = L;
	int j = 0;
	while(p && i - 1)
	{
		p = p->next;
		++j;
	}
	if (!p || j > i)
		return false;
	LinkList tmp = (LinkList)malloc(sizeof(LNode));
	tmp->data = e;
	tmp->next = p->next;
	p->next = tmp;
}

在单链表中删除数据元素(ListDelete(&L, i, &e)在链表中的实现)

步骤:
1、首先找到 ai –1 的存储位置 p。
2、令 p -> next 指向 ai+1 。
3、释放结点 ai 的空间。

图解说明:
在这里插入图片描述

p -> next = p -> next -> next;
算法实现
Status ListDelete_L(LinkList &L, int i, ElemType &e) 
{  
	p = L;  
	j = 0;  
	while ( p ->next && j < i –1) 
	{
		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; 
} // ListDelete_L 

时间复杂度为:O(n)

在链表上实现插入和删除运算,无须移动结点,仅需修改指针。

C语言实现在单链表中删除数据元素

建立单链表(头插法建表 逆序建表)

从一个空表开始,逐个将新结点插入到当前链表的表头上(头插法)。

算法实现
void CreateList_L(LinkList &L, int n)
 { 
 // 逆位序输入 n 个元素的值,建立带表头结点的单链表 L。
	L = (LinkList) malloc (sizeof (LNode));
    L -> next = NULL;    // 先建立一个带头结点的单链表
	for (i = n; i > 0; --i)
	{
		p = (LinkList) malloc (sizeof (LNode));   // 生成新结点
		scanf(&p -> data);    // 输入元素值
		p -> next = L -> next; 
		L -> next = p;   // 插入到表头 
     }
 } // CreateList_L

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

因为每个新生成的结点的插入位置在表尾,则算法中必须维持一个始终指向已建立的链表表尾的指针。

C语言实现在单链表中删除数据元素

静态链表说明

静态链表的空间固定。

#define MAXSIZE 1000      / /链表的最大长度
typedef  struct{ 
     ElemType data;  //数据域
     int cur; 		 //指示下一个元素的下标
}component,SLinkList[MAXSIZE];

静态链表
借助于数组下标达到和指针一样的指示效果。

循环链表

循环链表:是一种头尾相接的链表(即:表中最后一个结点的指针域指向头结点,整个链表形成一个环)。

循环链表

优点:从表中任一结点出发均可找到表中其他结点。

由于循环链表中没有 NULL 指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断 p 或 p ->next 是否为空,而是判断它们是否等于头指针。

头指针单循环链表

判断循环链表是否为空: head -> next = head;

注意:表的操作常常是在表的首尾位置上进行。
尾指针单循环链表
时间复杂度是 O(1)。

典例:将两个线性表合并成一个线性表。

仅需将一个表的表尾和另一个表的表头相接。

将两个线性表合并成一个线性表

实现将两个线性表合并成一个线性表

当线性表以上图的循环链表作存储结构时,此操作仅需改变两个指针即可。
时间复杂度是 O(1)。

双向链表

双向链表

双向链表

双向链表的结构可定义如下:

typedef struct DuLNode{
   Elemtype                data;
   struct DuLNode    *prior, *next;
} DuLNode, *DuLinkList;

双向链表的结构

双向链表结构的对称性(设指针 p 指向某一结点):
p -> prior -> next = p = p -> next -> prior

 双向链表结构的对称性
双向链表的删除结点过程

双向链表的删除结点过程

双向链表的插入结点过程
双向链表的插入结点过程

双向循环链表

和单链的循环表类似,双向链表也可以有循环表,让头结点的前驱指针指向链表的最后一个结点,让最后一个结点的后继指针指向头结点。

图解双向循环链表

四种存储方式的比较

{顺序、链式},{静态、动态}
1、顺序存储的固有特点:
逻辑顺序与物理顺序一致,本质上是用数组存储线性表的各个元素(即随机存取);存储密度大,存储空间利用率高。

2、链式存储的固有特点:
元素之间的关系采用这些元素所在的节点的”指针”信息表示(插、删不需要移动节点)。

3、静态存储的固有特点:
在程序运行的过程中不用考虑追加内存的分配问题。

4、动态存储的固有特点:
可动态分配内存;有效的利用内存资源,使程序具有可扩展性。

动态顺序表和动态链式表各有哪些优缺点?
答:
动态顺序存储:
优点:存储密度大,存储空间利用率高,可随机存取。
节点空间可动态申请追加。

缺点:插入或删除元素时不方便。

动态链式存储:
优点:插入或删除元素时很方便,使用灵活。
结点空间可以动态申请和释放;

缺点:存储密度小,存储空间利用率低,非随机存取。

事实上,链表插入、删除运算的快捷是以空间代价来换取时间。

顺序表、链表各自的使用场合?
答:
顺序表适宜于做查找这样的静态操作;

链表宜于做插入、删除这样的动态操作。

若线性表的长度变化不大,且其主要操作是查找,则采用顺序表;

若线性表的长度变化较大,且其主要操作是插入、删除操作,则采用链表。

标签:链表,结点,线性表,元素,next,说明,插入,详细
来源: https://blog.csdn.net/qq_43648751/article/details/106937145

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有