-
基本操作的实现
-
插入和删除
❕插入、删除的代码都要会写,都重要
-
插入
-
按位序插入
-
带头结点
ListInsert(&L,i,e): 插入操作。在表L中的第i个位置上插入指定元素e。
-
-
-
-
头结点可以看作“第0个”结点
-
代码实现
typedef struct LNode{ ElemType data; struct LNode *next; }LNode, *LinkList; //第i个位置上插入指定元素e(带头结点) bool ListInsert(LinkList &L,int i,ElemType e){ if(i < 1) return false; //优化 //LNode *p = GetElem(L,i -1); //找到第i个结点 LNode *p; //指针p指向当前扫描到的结点 int j = 0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) //重要循环 while(p != NULL && j < 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; p -> next = s; //将结点s连到p之后 return true; //插入成功 }
!分析:
1.如果i = 1(插在表头)
最好时间复杂度:O(1)
2.如果i = 3(插在表中) 自己走一遍代码即可看懂
3.如果i = 5(插在表尾)
最坏时间复杂度:O(n)
4.如果i = 6 (i > length)
-
不带头结点
ListInsert(&L,i,e): 插入操作。在表L中的第i个位置上插入指定元素e。
- 不存在“第0个”结点,因此i = 1时需要特殊处理
-
代码实现
typedef struct LNode{ ElemType data; struct LNode *next; }LNode, *LinkList; bool ListInsert(LinkList &L,int i,ElemType e){ if(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指向的是第几个结点 p = L; //p指向第1个结点(注意:不是头结点) //重要循环 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; p -> next = s; //将结点s连到p之后 return true; //插入成功 }
!分析:
1.如果i = 1(插在表头)
2.如果i > 1… 后续逻辑和带头结点的一样
💡 考试中带头、不带头都有可能考察,注意审题
-
指定结点的后插操作
typedef struct LNode{ ElemType data; struct LNode *next; }LNode, *LinkList; //后插操作:在p结点之后插入元素e bool InsertNextNode(LNode *p,ElemType e){ if(p == NULL) return false; //考虑到非法情况:健壮性 LNode *s = (LNode *)malloc(sizeof(LNode)); // 某些情况下有可能分配失败(如内存不足) if(s == NULL) //内存分配失败 return false; s -> data =e; //用结点s保存数据元素e s ->next = p -> next; p -> next = s; //将结点s连到p之后 return true; }
时间复杂度:O(1)
-
带头结点代码优化
typedef struct LNode{ ElemType data; struct LNode *next; }LNode, *LinkList; //第i个位置上插入指定元素e(带头结点) 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){ p = p -> next; j++; } //优化 return InsertNextNode(p,e); }
⭐ 封装(基本操作)的好处
避免重复代码,简介,易维护
-
-
指定结点的前插操作
-
方法1
//前插操作:在p结点之前插入元素e //传入头指针 bool InsertPriorNode(LinkList L, LNode *p, ElemType e)
时间复杂度:O(n)
-
-
方法2
//前插操作:在p结点之前插入元素e bool InsertPriorNode(LNode *p, ElemType e){ if(p == NULL) return false; LNode *s = (LNode *)malloc(sizeof(LNode)); if(s == NULL) //内存分配失败 return false; s -> next = p -> next; p -> next = s; //新结点s连到p之后 s -> data = p -> data; //将p中元素复制到s中 p -> data = e; //p中元素覆盖为e return true; }
时间复杂度:O(1)
-
删除
-
按位序删除(带头结点)
ListDelet(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值
-
(找到第i -1 个结点,将其指针指向第i + 1个结点,并释放第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 = GetElem(L,i -1); //找到第i个结点 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) //i值不合法 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; //插入成功 }
! 分析:
1.如果i = 4…
最坏、平均时间复杂度:O(n)
最好时间复杂度: O(1)
❔如果不带头结点,删除第1个元素,是否需要特殊处理?
❕需要特殊处理,除第一个元素的其他元素要删除需要找到该元素的前一个元素,保证链不断 而第一个元素只要修改L的指向
-
指定结点的删除
//删除指定结点p bool DeleteNode(LNode *p)
-
删除结点p,需要修改其前驱结点的next指针
-
-
方法1:传入头指针,循环寻找p的前驱结点
-
方法2:偷天换日(类似于结点前插的实现)
//删除指定结点p bool DeleteNode(LNode *p){ if(p == NULL) return false; LNode *q = p -> next; //令q指向*p的后继结点 p -> data = p -> next -> data; //和后继节点交换数据域 p -> next = q -> next; //将*q结点从链中“断开” 可能是指向一个结点,也可能是指向NULL free(q); //释放后继结点的内存空间 return true; }
时间复杂度:O(1)
❔ 如果p是最后一个结点
❕ 只能从表头开始依次寻找p的前驱,时间复杂度O(n)
查找
-
按位查找
✅只探讨“带头结点”的情况
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
-
代码实现
-
//按位查找,返回第i个元素(带头结点)
LNode * GetElem(LinkList L,int i){
if(i < 0)
return NULL;
LNode *p; //指针p指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点(不存数据)
while(p != NULL && j < i){ //循环找到第i个结点
p = p -> next;
j++;
}
return p;
}
边界情况:1.如果i = 0 2.如果i = 8
普通情况:如果i = 3
平均时间复杂度:O(n)
-
按值查找
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
-
代码实现
//按值查找,找到数据域==e 的特点 //注释:假设本例中ElemType是int LNode * LocateElem(LinkList L,ElemType e){ LNode *p = L -> next; //从第1个结点开始查找数据域为e的结点 while (p != NULL && p -> data != e) p = p -> next; return p; //找到后返回该结点指针,否则返回NULL }
(头指针 → 5 → 8 → 10 → 2 → NULL)
-
能找到的情况:如果e = 8
-
不能找到的情况:如果e = 6
平均时间复杂度:O(n)
-
-
求表的长度
//求表的长度 int Length(LinkList L){ int len = 0; //统计表长 LNode *p = L; while(p -> next != NULL){ p = p -> next; len++; } return len; }
时间复杂度:O(n)
总结:
-
建立
❔ 如果给你很多个数据元素(ElemType),要把他们存放到一个单链表里,怎么做?
❕step1:初始化一个单链表。 step2:每次取一个数据元素,插入到表尾/表头
注:探讨带头结点的情况👇
-
尾插法
typedef struct LNode{ //定义单链表结点类型 ElemType data; //每个结点存放一个数据元素 struct LNode *next; //指针指向下一个结点 }LNode, *LinkList; //初始化一个单链表(带头结点) bool InitList(LinkList &L){ L = (LNode *) malloc(sizeif(LNode)); //分配一个头节点 if (L == NULL) //内存不足,分配失败 return false; L -> next = NULL; //头节点之后暂时还没有结点 return true; } void test(){ LinkList L; //声明一个指向单链表的指针 //初始化一个空表 InitList(L); //...后续代码... } //第i个位置上插入指定元素e(带头结点) bool ListInsert(LinkList &L,int i,ElemType e){ if(i < 1) return false; LNode *p; //指针p指向当前扫描到的结点 int j = 0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点(不存数据) //每次都从头开始之后遍历,时间复杂度为O(n²) **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; p -> next = s; //将结点s连到p之后 return true; //插入成功 }
-
初始化单链表
-
设置变量length记录链表长度
-
While循环{
每次取一个数据元素e;
ListInsert(L,length + 1,e)插到尾部;
length++; }
因为while循环时间复杂度O(n²)太高,故可以建立一个尾指针👇
LinkList List_TailInsert(LinkList &L){ //正向建立单链表 int x; //设ElemType为整型 L = (LinkList)malloc(sizeof(LNode)); //建立头结点 LNode *s,*r = L; //r为表尾指针 scanf("%d",&x); //输入结点的值 while(x != 9999){ //输入9999表示结束 **s = (LNode *)malloc(sizeof(LNode)); s -> data = x; r -> next = s;** r = s; //r指向新的表尾结点 永远保持r指向最后一个结点 scanf("%d",&x); } r -> next = NULL; //尾结点指针置空 return L; }
时间复杂度:O(n)
-
💡 注意设置一个指向表尾结点的指针
-
头插法
💡 核心:对头结点的后插操作
-
初始化单链表
-
while 循环{
每次取一个数据元素e;
InsertNextNode(L,e);
}
LinkList List_HeadInsert(LinkList &L){ //逆向建立单链表 LNode *s; int x; L = (LinkList)malloc(sizeof(LNode)); //建立头结点 //只要是初始化但列联表,都先把头指针指向NULL (防止指向脏数据) **L -> next = NULL; //初始为空链表** scanf("%d",&x); //输入结点的值 while(x != 9999){ //输入9999表示结束 s = (LNode *)malloc(sizeof(LNode)); //创建新结点 s -> data = x; r -> next = L -> next; L -> next = s; //将新结点插入表中,L为头指针 scanf("%d",&x); } return L; }
❗ 重要应用——链表的逆置
-
-
💡 核心就是初始化操作、指定结点的后插操作