线性结构——单向链表基础知识(链表的概念、意义、建立、遍历、删除、插入)

一、链表概念

链表是一种数据结构。链表与数组具有类似的概念——都可以批量处理数据。

但事实上,链表弥补了数组的一些天生局限:

二、链表的意义(优点)

1.数组占有固定的内存空间,常常需要顾忌浪费内存空间或者越界的麻烦;而链表具有灵活动态的内存空间,可以随时增加与停止。

2.数组的每个数据在内存上都是连续的,这导致想要在中间增减某个数据就需要牵动到整个数组,带来不便;而链表的每个数据在内存上不是严格连续的,每个数据之间的链接关系可以由我们控制并随时改变。

三、链表的建立操作

1、链节(linker)

链表中的每个链节都是结构体。对结构体的唯一要求是必须有一个成员为指针,除此之外的其他数据都可以按需要定义在结构体中。以一个存放学生分数的链表为例:

//定义链节结构体 
struct linker{
	char name;//姓名 
	int sco;//分数 
	linker *next;//下一个节点的地址 
};

char型与int型的数据只是为了满足设计的个性化需求,在其他需求背景下还可以定义其他类型数据。而 linker *next;的定义是链表能连接起来的关键。关于该操作有两个原理问题:

1)为什么能在结构体尚未定义完之前,用该结构体类型定义指针。

也就是说为什么能在结构体的定义中,像递归函数一样,使用该结构体类型定义指针:首先,除了定义该结构体的指针以外,其他涉及到该结构体类型的定义都不成立(例如不可以在里面定义一个“linker example”)。理由是这个结构体定义还未完成,占用内存大小不确定,系统无法为变量分配内存。这个回答也是为什么指针可以的原因,因为任何类型的指针(包括int、char以及结构体)占用的空间都是确定且相等的八个字节,系统在分配内存时不会有任何疑惑。

2)为什么链节能靠其中的指针实现链接关系

每个链节作为独立的数据都有属于自己的地址,而只要上一个链节中的指针储存了下一个链节的地址,就可以实现数据的连续性。需要注意的是:因为指针要储存下一个链节的地址,所以定义的指针类型是该结构体类型(例如上面的linker *next;)。至于指针名推荐使用“next”,也是因为表示指针指向下一个链节的地址。链接关系可参考下图理解:

2、头节点 (head)

//创立头节点
	static linker *head=new linker; //利用动态内存分配命令new创建节点 
	head->name=1;// 对头节点的数据name赋值 
	head->sco=60;//对头节点的数据sco赋值 
	head->next=NULL;//对头节点的next指针赋值:由于下一个节点还未创立,所以先指向NULL 

如上图。用static修饰了头节点,是为了防止头节点被篡改导致丢失。由于整个链表都从头节点开始,调用时也从头节点出发,所以一旦头节点丢失,那么整个链表中的所有数据都无法使用,也就是说整个链表都找不到了。这一点对于理解下面的链表遍历操作很重要。

特殊步骤解释:

*需要利用动态内存分配命令new创建节点:因为每个结构里的成员所需空间大小未知。

*创建每个节点时,节点内的指针都先赋NULL,因为下一个节点还未创立,尚不知道该指向哪里。

3、中间节点与尾节点(tail)

	//创立中间节点和尾节点
    linker *tail=head; 	//使得tail能从head开始创建
    for(int i=0;i<=n;i++)//创建了n个节点 
	{
		tail->next=new linker;//先创建出 tail->next
		
		tail->next->name=i;//为tail->next赋值i 
		tail->next->sco=(rand()%(100-0))+0;//随机数为tail->next赋值sco
		tail->next->next=NULL;为tail->next赋值next
		
		tail=tail->next;//指向跳到tail->next 
	}

这里的tail节点即指尾节点。它是一个动态的节点,建立完一个节点后就会跳到下一个节点,永远指向链表的最后。除了头节点需要单独定义以外,链表的所有数据都是在tail的动态移动中创建出来的。(注意:在tail未跳到之前,中间节点是不存在的,每个节点都是tail跳过来才创建的)

具体步骤解释:

1)为什么要创建一个等于head的tail节点,而不直接用已有的head节点去动态赋值

在上面对头节点的介绍中已经给出了答案:如果这里用head跳到后面动态赋值,我们就失去了整个链表的开始位置,最终只能得到最后一个链节(虽然还是叫head,但已经不是头链节了)。由于本文所讲的是单向链表,每个链节只有下一个链节的指针,也就是说只能向后遍历,所以前面的数据都找不到了。

为了防止这种情况发生,我们定义了一个动态的tail链节。在tail定义时为tail赋值了head,使得tail与head有了相同的首地址(也就是说指向同一个链节),tail向后延伸,其实是在弥补head之后的空白。

2)tail所指向的节点始终是赋完值了的节点(保证作为最后一个节点存在着)。每次赋值是在为tail->next赋值。在tail没跳到之前,tail->next是不存在的,所以需要用动态内存分配命令new创建出来,再为 tail->next中的每个成员赋值。

3)tail->next创建完毕后,关键的一步就是令tail跳到tail->next,此时刚刚创立完成的tail->next成了新的tail。那么进入下一个循环后,继续创立的tail->next则是相对于新的tail而言的tail->next。

4、完整链表建立函数 

//链表建立函数
linker *creat(int n)//表示创建有n个节点的链表
{ 
//如果创建0个节点的链表(即什么都没有) 
	if(n==0) return NULL;
	
//如果创建1个节点的链表(即只有头链节) 
	//创立头节点
	static linker *head=new linker; //利用动态内存分配命令new创建节点 
	head->name=1;// 对头节点的数据name赋值 
	head->sco=60;//对头节点的数据sco赋值 
	head->next=NULL;//对头节点的next指针赋值:由于下一个节点还未创立,所以先指向NULL 
	if(n==1) return head;
	
//如果创建多于一个节点的链表	
	linker *tail=head; 
	for(int i=2;i<=n;i++)//创建了n-1个节点 
	{
		tail->next=new linker;//先创建出 tail->next
		
		tail->next->name=i;//为tail->next赋值i 
		tail->next->sco=(rand()%(100-0))+0;//随机数为tail->next赋值sco
		tail->next->next=NULL;为tail->next赋值next
		
		tail=tail->next;//指向跳到tail->next 
	}
	return head; //返回head即可使用整个链表 
} 

四、链表的遍历 (move)

//链表打印函数
void print(linker *head)
{
	linker *move=head;
	while(move)// 即:move链节存在就打印出来 
	{cout<<move->name<<" "<<move->sco<<endl;
	move=move->next;}
 } 

如上面链表打印函数的示例

创立move与tail的意义相同,都是为了防止head改变导致整个链表丢失。

由于除了头链节以外的其他链节的地址都不知道,所以想要访问链表中的任何一个链节,都需要从头遍历过去,在找到后停下。这一点算是链表的一个弊端。 

五、链表的删除(deletelinker)

原理示意图:

代码实现:

//链表定点删除函数
 void deletelinker(linker *head,int n)//表示删除第n个链节
 {	
 	linker *move=head;
 	int i=1;
 	while(i!=n-1) move=move->next; //寻找所需删除的链节的前一个链节 
	move->next=move->next->next;//直接跳过想要删除的链节,重新链接 
  } 

*操作注意:

1、由于每个链节都是用new开辟出来的,所以,在中途删除的链节空间需要记得及时单独释放(delete)

2、示例中通过链节的排序位置进行寻找,实际问题中,可以灵活根据情境寻找相应的链节。比如说,还是这个存放学生分数的链表,我们也可以寻找存放学生姓名为LiMing的链节进行删除操作,那么寻找的条件就可以换成“while(move->next->name!="LiMing") move=move->next;”。

六、链表的插入(insertlinker)

原理示意图:

代码实现:

//链表定点插入函数
void insertlinker(linker *head,linker *newnode,int n) //表示在第几个链节后插入新的链节 (newnode)
{
    int i=1;
 	while(i!=n-1) move=move->next; 
  	newnode->next=move->next;
  	move->next=newcode;
}

*操作注意:

需要先让插入链节连接上下一个链节(第n+1个),再使上一个链节(第n个)改变为连接插入链节。如果颠倒操作顺序,那么第n+1个链节及之后的链节都会丢失。

 

 

 

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值