文章目录
前言
本文给出了单链表_插入删除查找三种基本操作以及求表长_代码及相应图示助于理解,代码实现使用的是C语言在线工具。无概念解释,初学者建议配合书本食用。
【如果图片看不清楚,网页版是很清晰的。😦】
一、插入
1. 按位序插入(带头结点的单链表)
在单链表第 i 个位置插入元素 e 。只需要找到第 i -1 个结点,将新结点插入其后。
图示:
i 的 3 种位置:第一个、中间、最后一个。
由上图可以看出,
当 i<1,i 不合法【就是代码的第一个 if 条件判断】。
当 i=1,最好情况下的时间复杂度是O(1)。
当 i=length+1,最坏情况下时间复杂度是O(n),因为需要循环找到第 i-1个结点。
当 1< i <length+1,平均时间复杂度也是O(n),因为也要循环找到第 i-1个结点。
当 i>length+1,i 不合法【就是代码的第二个 if 条件判断】。
代码如下:
typedef struct LNode{
//定义单链表结点类型
int data; //每个结点存放一个数据元素
struct LNode *next; //指针指向下一个结点
}LNode, *LinkList;
//在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L, int i, int e){
if(i<1) //判断i是否合法,位序从1开始,如果i<1,不合法
return false;
LNode *p; //指针p指向当前扫描到的结点
int j = 0; //p指向当前第j个结点,j=0是头结点,所谓的第0个结点(不存数据)
p = L; //L指向头结点,p从头结点开始
while(p!=NULL && j<i-1){
//循环找到第i-1个结点
p = p->next;
j++;
}
if(p==NULL) //判断i是否合法,比如总共4个结点存储数据,那么最多插入到第5个位置,如果i=6,经过上述while循环后此时p==NULL,不合法
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true; //返回true代表插入成功
}
2. 按位序插入(不带头结点的单链表)
在单链表第 i 个位置插入元素 e 。只需要找到第 i -1 个结点,将新结点插入其后。
i 的 3 种位置:第一个、中间、最后一个。
但是如果 i 是第一个位置,因为不带头结点,也就是不存在所谓的 “第0个” 结点,因此插入第1个元素时,需要更改头指针L,需要单独加一段代码,如图:
(其余两种位置和带头结点的一样)
typedef struct LNode{
//定义单链表结点类型
int data; //每个结点存放一个数据元素
struct LNode *next; //指针指向下一个结点
}LNode, *LinkList;
//在第i个位置插入元素e(不带头结点)
bool ListInsert(LinkList &L, int i, int e){
if(i < 1) //判断i是否合法,位序从1开始,如果i<1,不合法
return false;
if(i == 1){
//插入第1个结点的操作与其他结点不同
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s; //头指针指向新结点
return true;
}
LNode *p; //指针p指向当前扫描到的结点
int j = 1; //p指向当前第j个结点
p = L; //p从第一个结点开始(不是头结点)
while(p!=NULL && j<i-1){
//循环找到第i-1个结点
p = p->next;
j++;
}
if(p == NULL) //判断i是否合法
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next; //将结点s与p的下一个结点相连
p->next = s; //将结点s连到p之后
return true; //返回true代表插入成功
}
3. 在指定结点之后,插入一个结点
指定 p 结点,在这个结点之后插入一个结点元素 e。
代码如下,由于没有循环,所以时间复杂度是O(1)。
typedef struct LNode{
//定义单链表结点类型
int data; //每个结点存放一个数据元素
struct LNode *next; //指针指向下一个结点
}LNode, *LinkList;
//在p结点之后插入新结点元素e
bool InsertNextNode(LNode *p, int e){
if(p == NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s == NULL) //内存不足,分配失败,这个if也可以不加
return false;
s->data = e;
s->next = p->next;
p->next = s; //将结点s连到p后
return true;
}
这段代码很眼熟吧,其实就是ListInsert插入函数的这一部分:
于是可以封装为如下图:
4. 在指定结点之前,插入一个结点
指定 p 结点,在这个结点之前插入一个结点元素 e。相当于在 p 的前驱结点后面插入一个结点。
由于单链表只能向后找,不能向前找,那如何找到 p 结点的前驱节点?
- 办法一,传入头指针 L,从头开始遍历找到 p 的前驱结点 m,然后在 m 结点之后插入 e。这样也可以,但是需要循环,意味着时间复杂度为O(n)。
bool InsertPriorNode(LinkList L, LNode *p, ElemType e) //传入头指针L
/* 然后循环,查找p的前驱 */
- 办法二,与其找 p 的上一个结点,不如创建一个新结点 s,把 s 插到 p 之后,然后对调 p 和 s(偷天换日了啊兄弟们),这不就相当于在 p 之前插入一个 s 吗,这样不用循环,所以时间复杂度是O(1)。代码如下:
typedef struct LNode{
//定义单链表结点类型
int data; //每个结点存放一个数据元素
struct LNode *next; //指针指向下一个结点
}LNode, *LinkList;
//在p结点之前插入新结点元素e
bool InsertPriorNode(LNode *p, ElemType e){
if(p == NULL)
return false;
LNode *s = (LNode