单链表的插入删除(数据结构与算法)

单链表的插入删除(数据结构与算法)

一、单链表特性

单链表是一种常见的线性数据结构,由一个个节点组成,每个节点包含两个部分:数据部分和指针部分。

单链表的特点是每个节点只能指向下一个节点,而最后一个节点指向一个空指针。
这个空指针常用来表示链表的结尾,一般命名为 nullptr。

下面是一个简单的单链表的示意图:

头指针 -> 节点1 -> 节点2 -> 节点3 -> … -> 最后一个节点 -> nullptr

与数组不同,单链表的节点是通过指针来连接的,
因此在插入、删除节点时不需要移动其他节点,只需要修改指针的指向即可,这是单链表的一个优势。

由于单链表每个节点只存储了指向下一个节点的指针,
所以访问节点时需要从头指针开始依次遍历访问,直到找到需要的节点,或者到达链表的结尾。

单链表适用于需要频繁插入、删除节点的场景,但不适用于随机访问节点的场景,
因为随机访问需要从头指针开始遍历整个链表。

在 C++ 中,可以使用结构体或类来定义单链表的节点,
并通过指针来连接节点。这样就可以很方便地操作单链表的插入、删除和遍历等操作。

具体来说,单链表中的每个节点包含两个重要的成员变量:

  1. 数据部分:用于存储节点的数据。
  2. 指针部分:用于指示下一个节点的位置。

二、单链表的插入操作

在这里插入图片描述在这里插入图片描述

按位序插入(带头结点)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码实现如下:

//在第i个位置插入元素e(带头结点)
typedef struct LNode{
	ElemType data;
	struct LNode *next;
}LNode, *LinkList;

bool ListInsert(LinkList &L, int i, ElemType e){
	if(i < 1)
		return false;
	LNode *p;	//指针p指向当前扫描到的结点
	int j = 0;	//当前p指向的是第几个结点
	p = L;		//L指向头结点,头结点是第0个结点(不存数据)
	while (p != NULL && j < i-1){
	//循环找到第i-1个结点
	p = p->next;   //p结点向后移动一位
	j++;           
	}
	if(p==NULL)		//i值不合法
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = e;    //将数据元素e存入s数据域中
	s->next = p->next;  //令s指向p结点的后继结点。即使s结点连上p的后一个结点。
	p->next = s;      //将结点s连到p之后
	return true;      //插入成功
}

在这里插入图片描述
在这里插入图片描述

按位序插入(不带头结点)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

三、单链表的删除操作

按位序删除(带头结点)
//删除第i个元素(带头结点)
typedef struct LNode
{
	ElemType data;
	struct LNode *next;
}LNode, *LinkList;

bool ListDelete(LinkList &L, int i, ElemType &e)
{
	if (i < 1)
		return false;
	LNode *p;       //指针p指向当前扫描到的结点
	int j = 0;      //当前p指向的是第几个结点
	p = L;          //L指向头结点,头结点是第0个结点(不存数据)
	while(p != NULL && j < i-1)    //循环找到第  i - 1 个结点 
	{
		p = p->next;
		j++;
	}
	if (p == NULL)
		return false;
	if (p->next == NULL)     //第i-1个结点之后已经无其他结点
		return false;
	LNode *q = p->next;      //令q指向被删除结点
	e = q->data;			//用e返回元素的值
	p->next = q->next;		//将*q结点从链中“断开”
	free(q);				//释放结点的存储空间
	return true;			//删除成功
}

在这里插入图片描述

分析:删掉第 i 个结点, 即先循环找到第 i-1个结点。

如果 i = 4; 要删掉第4个结点a4,就要先通过while循环找到它的前驱结点,即第三个结点a3。

最终,p会指向第3个结点a3,接下来定义一个指针q, 指向p结点的next,也就是指向了第4个结点a4。

把q结点的数据元素复制到变量e中,注意变量e需要把此次删除的节点的值,通过变量e带回到ListDelete函数的调用者那,所以e是引用类型的。

p的next指向q的next,也就是指向NULL, 最后调用free()函数将q结点释放掉。具体实现如下图所示:

在这里插入图片描述

指定结点的删除

在这里插入图片描述

如果不带头结点,如何删除第一个元素,或者在前驱结点未知情况下,删除p结点?(偷天换日法,如下图所示)

  1. 创建一个q指针,指向p结点的后继结点。.在这里插入图片描述
  2. p->data = p->next->data; 将q结点的数据元素赋值到p结点的数据域里面。
    在这里插入图片描述
  3. 然后再让p结点的next指针指向q结点之后的位置。
    在这里插入图片描述
  4. 再将q结点存储空间释放掉。从而实现删除操作,这种实现方式时间复杂度也是O(1)。
    在这里插入图片描述
  5. 详细代码实现如下:
//删除指定结点p
bool DeleteNode(LNode * p)
{
	if (p == NULL)     //若删除的节点为空结点,操作无效
		return false;
	LNode *q = p->next;		//定义一个q指针,令q指向*p的后继结点
	p->data = p->next->data; //和后继结点交换数据域,相当于将p节点的后一个结点的数据赋值到p结点中
	p->next = q->next;    //将*q结点从链中“断开” 
	free(q);            //释放后继结点的存储空间
	return true;
}

由于需删除结点的前驱结点未知,或者要删除的是第一个结点,且不带头结点。那么换个思路,创建一个q指针指向p结点的后继结点,将p结点的后继结点q中的数据覆盖到p结点数据域中,然后令p结点指向q结点的后继结点: p->next = q->next;
再删掉“悬空”的q结点完成操作。等同于:1->2->3->4,若要删掉1,可以先令前两个数据交换,2->1->3->4,
再让1的指针链断开,令2指向3: 2->(1)-3->4, 把1断开,于是就是2->3->4。

如果要删除的p结点是最后一个结点,以上偷天换日法无法使用,我们只能从表头开始依次往后寻找p的前驱,时间复杂度为O(n)。
在这里插入图片描述

单链表只能单向地检索各个结点,无法逆向检索,有时不太方便。
如果有双向检索呢?是不是能解决这个不足,那就需要引出之后的双链表了。

四、知识点脉络回顾

在这里插入图片描述
在这里插入图片描述

  • 13
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
一.单选题(共10题,5) 1线性表采用链式存储结构时,其地址( )。A、必须是连续的B、部分地址必须是连续的C、一定是不连续的D、连续与否均可以正确答案: D 2带头结点的单链表head为空的判断条件是( )。A、head=NULLB、head->next=NULLC、head->next=headD、head!=NULL正确答案: B 3将两个各有n个元素的有序表归并成一个有序表,其最少的比较次数是( )。A、nB、2n-1C、 2nD、n-1正确答案: A 4在一个单链表中,已知q所指结点是p所指结点的前趋结点,若在q和p之间插入s结点,则执行( )。A、 s->next=p->next;p->next=sB、p->next=s->next;s->next=pC、q->next=s;s->next=pD、p->next=s;s->next=q正确答案: C 5向一个有 127 个元素的顺序表中插入一个新元素并保持原来顺序不变,平均要移动( )个元素。A、64B、63C、63.5D、7正确答案: C 插入 n/2 删除 n-1/2 查询 n+1/2 6若某线性表中最常用的操作是在最后一个元素之后插入一个元素和删除第一个元素,则采用( )存储方式最节省运算时间。A、单链表B、仅有头指针的单循环链表C、双链表D、仅有尾指针的单循环链表正确答案: D 7若长度为n的线性表采用顺序存储结构,在其第个i位置插入一个新元素算法的时间复杂度为( )。A、 O(log2n)B、O(1)C、O(n)D、O(n2) 正确答案: C 8线性表的静态链表存储结构与顺序存储结构相比优点是( )。A、所有的操作算法简单B、便于插入删除C、便于利用零散的存储空间D、便于随机存取正确答案: B 9在一个单链表中, 若删除 p 所指结点的后续结点, 则执行( )A、s->next=p;p->next=s;B、s->next=p->next;p=s;C、s->next=p->next;p->next=s;D、p->next=s;s->next=p;正确答案: B 10一个线性表第一个元素的存储地址是 100, 每个元素的长度为 2,则第 5 个元素的地址是( )A、110B、108C、100D、120
对于单链表插入操作,有两种常见的情况:在链表的头部插入一个新节点和在链表的指定位置插入一个新节点。 1. 在链表头部插入新节点: 首先,创建一个新的节点,并将新节点的值设为待插入的值。 然后,将链表的头节点作为新节点的下一个节点。 最后,将新节点设为链表的新头节点。 2. 在链表指定位置插入新节点: 首先,遍历链表找到待插入位置的前一个节点。 然后,创建一个新的节点,并将新节点的值设为待插入的值。 接下来,将原来前一个节点的下一个节点设为新节点的下一个节点。 最后,将新节点设为原来前一个节点的下一个节点。 具体实现代码如下(使用Python语言): ```python class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def insertAtHead(head, value): new_node = ListNode(value) new_node.next = head return new_node def insertAtPosition(head, position, value): if position == 0: return insertAtHead(head, value) cur = head for _ in range(position-1): if cur.next is None: raise ValueError("Invalid position") cur = cur.next new_node = ListNode(value) new_node.next = cur.next cur.next = new_node return head ``` 使用示例: ```python # 创建一个链表 1 -> 2 -> 4 head = ListNode(1) head.next = ListNode(2) head.next.next = ListNode(4) # 在头部插入新节点 0 head = insertAtHead(head, 0) # 在位置 2 插入新节点 3 head = insertAtPosition(head, 2, 3) ``` 以上是单链表插入操作的基本思路和实现代码。在实际使用中,可以根据具体需求进行相应的修改和优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值