文章目录
一、不带头结点的单链表基本操作
(1)单链表结构体
🌽代码
【写法1】这里使用这个写法
cpp
typedef int ElemType;
typedef struct LNode{
ElemType data; //数据域
LNode* next; //指针域,保存后继结点的地址
}LNode,*LinkedLIst;
注意:
1、LNode* p(强调节点) 与 LinkedList p(强调链表)无区别。
2、头指针:每次指向链表的第一个节点。
头结点:申请的头结点是虚拟的节点,里面不存放数据信息,真正有意义的在头结点后面,头结点只是为了方便操作。指向第一个元素的依然叫做"头指针"。

【写法2】
cpp
typedef struct LNode{
ElemType data;
LNode* next;
}LNode;
typedef struct {
LNode* head;
int length; //用来表示表长
}LinkedList;
(2)单链表初始化
🌽代码
cpp
//初始化
void initLinkedList(LinkedList& L) {
L = NULL; //直接为空即可
}
(3)头插法创建单链表
🌽代码
【写法1】
cpp
//头插法创建单链表
void createListByHead(LinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode)); //申请节点空间
if (node == NULL) { //若是申请空间失败,则退出
return;
}
node->data = x;
node->next = NULL;
if (L == NULL) { //是第一个节点
L=node;
}
else { //不是第一个节点
node->next=L;
L = node;
}
scanf("%d", &x); //继续输入
}
}
【写法2】优化版,这里使用这一种写法
cpp
void createListByHead(LinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode)); //申请节点空间
node->data = x;
node->next = NULL;
if(L!=NULL){
node->next = L;
}
L = node;
scanf("%d", &x); //继续输入
}
}
头插法读入数据的顺序,与链表中的元素顺序相反。
🍺测试结果

(4)打印单链表
🌽代码
cpp
//打印单链表
void printLinkedList(LinkedList L) {
LNode* p = L; //p指向头指针处
while (p!= NULL) { //p不为空才打印
printf("%d->", p->data);
p = p->next; //p后移
}
printf("\n");
}
(5)尾插法创建单链表
🌽代码
cpp
//尾插法创建单链表
void createListByTail(LinkedList& L) {
ElemType x;
scanf("%d", &x);
LNode* p = NULL; //p用来指向链表的最后一个节点
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = x;
node->next = NULL;
if (L == NULL) { //创建的是第一个节点
L = node; //node为创建的第一个节点
p = node;
}
else { //不是第一个节点
p->next = node; //先动node再动p
p = node;
}
scanf("%d", &x);
}
}
🍺测试结果

有一个表尾指针p,始终指向最后一个节点。注意每次先将node放好,然后再动p。
尾插法:输出结果与输入结果顺序一致。
(6)按位查找
🌽代码
cpp
//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos) {
if (pos < 1) { //pos不合法
return NULL;
}
int i = 1; //i用于计数,从1开始
LNode* p = L; //p用于遍历单链表
while (i < pos && p!=NULL) { //注意p不能为空,为空的时候就是表尾了
p = p->next;
i++;
}
return p;
}
🍺测试结果

(7)按值查找
🌽代码
cpp
//按值查找节点
LNode* locateElem(LinkedList L, ElemType e) {
LNode* p = L;
while (p != NULL &&p->data!=e) { //p!=NULL可以简写为p
p = p->next;
}
return p;
}
(8)求表长
🌽代码
cpp
//求表长(从头到尾遍历即可)
int length(LinkedList L) {
LNode* p = L;
int i = 0; //注意i从0开始,p不是空的时候才加一
while (p != NULL) {
p = p->next;
i++;
}
return i;
}
🍺测试结果

(9)单链表的插入
🌽代码
【写法1】
cpp
//在第pos位置插入元素e
void insertElem(LinkedList& L, int pos, ElemType e) {
//pos合法范围为:[1,length+1],可以在最后位置后面插入
if (pos<1 || pos>length(L)+1) { //pos不合法,退出
return;
}
LNode* p = L; //p为遍历指针
int i = 1; //i计数
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = e;
node->next = NULL;
//在第一个位置上插入node
if (pos == 1) {
node->next = L;
L = node;
}
else { //在其余位置包括最后的位置插入node
while (i < pos - 1) { //找第pos位置的前一个节点
p = p->next;
i++;
}
node->next = p->next;
p->next = node;
}
}
【写法2】
cpp
//在第pos位置插入元素e
void insertElem(LinkedList& L, int pos, ElemType e) {
//pos合法范围为:[1,length+1],可以在最后位置后面插入
if (pos<1 || pos>length(L)+1) { //pos不合法,退出
return;
}
int i = 1; //i计数
LNode* node = (LNode*)malloc(sizeof(LNode)); //开辟新节点
node->data = e;
node->next = NULL;
if (pos == 1) { //在第一个位置上插入node
node->next = L;
L = node;
}
else { //在其余位置包括最后的位置插入node
//直接调用前面写的函数
LNode* pre = getNode(L, pos - 1);
node->next = pre->next;
pre->next = node;
}
}
🍺测试结果

又如:

又如:

(10)单链表的删除
🌽代码
【写法1】
cpp
void DeleteElem(LinkedList& L, int pos, ElemType& e) {
//pos合法范围为:[1,length]
if (pos<1 || pos>length(L)) {
return;
}
LNode* p = getNode(L, pos); //找到pos位置的节点,要删除的就是它
if (pos == 1) { //删除第一个位置的节点
L = p->next;
}
else { //删除其他位置的节点
LNode* pr=getNode(L, pos-1); //找到pos-1位置的节点
pr->next = p->next;
}
e = p->data;
free(p); //释放空间
}
【写法2】不推荐,可以选择法一直接使用get函数
cpp
//删除第pos位置的节点,并且返回被删除节点的元素值e
void DeleteElem(LinkedList& L, int pos, ElemType& e) {
//pos合法范围为:[1,length]
if (pos<1 || pos>length(L)) {
return;
}
int i = 1;
int j = 1;
LNode* p = L; //p为遍历指针
LNode* pr = L; //pr为遍历指针
if (pos == 1) {
e = L->data;
L = L->next;
}
else {
while (i < pos) { //找到第pos位置的节点
i++;
p = p->next;
}
while (j < pos - 1) { //找到第pos-1位置的节点
j++;
pr = pr->next;
}
e = p->data;
pr->next=p->next;
}
}
🍺测试结果

又如:

(11)整体代码
①LinkedList.h
cpp
#pragma once
typedef int ElemType;
typedef struct LNode{
ElemType data; //数据域
LNode* next; //指针域,保存后继结点的地址
}LNode,*LinkedList;
//注意:LNode* p(强调节点) 与 LinkedList p(强调链表)无区别
//typedef struct LNode{
// ElemType data;
// LNode* next;
//}LNode;
//
//typedef struct {
// LNode* head;
// int length; //用来表示表长
//}LinkedList;
//初始化
void initLinkedList(LinkedList& L);
//头插法创建单链表
void createListByHead(LinkedList& L);
//打印单链表
void printLinkedList(LinkedList L);
//尾插法创建单链表
void createListByTail(LinkedList& L);
//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos);
//按值查找节点
LNode* locateElem(LinkedList L, ElemType e);
//求表长
int length(LinkedList L);
//在第pos位置插入元素e
void insertElem(LinkedList& L, int pos, ElemType e);
//删除第pos位置的节点,并且返回被删除节点的元素值e
void DeleteElem(LinkedList& L, int pos, ElemType& e);
②LinkedList.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include "LinkedList.h"
//初始化
void initLinkedList(LinkedList& L) {
L = NULL; //直接为空即可
}
//头插法创建单链表
//void createListByHead(LinkedList& L) {
// ElemType x;
// scanf("%d", &x);
// while (x != 999) {
// LNode* node = (LNode*)malloc(sizeof(LNode)); //申请节点空间
// if (node == NULL) { //若是申请空间失败,则退出
// return;
// }
// node->data = x;
// node->next = NULL;
//
// if (L == NULL) { //是第一个节点
// L=node;
// }
// else { //不是第一个节点
// node->next=L;
// L = node;
// }
// scanf("%d", &x); //继续输入
// }
//}
void createListByHead(LinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode)); //申请节点空间
node->data = x;
node->next = NULL;
if(L!=NULL){
node->next = L;
}
L = node;
scanf("%d", &x); //继续输入
}
}
//打印单链表
void printLinkedList(LinkedList L) {
LNode* p = L; //p指向头指针处
while (p!= NULL) {
printf("%d->", p->data);
p = p->next; //p后移
}
printf("\n");
}
//尾插法创建单链表
void createListByTail(LinkedList& L) {
ElemType x;
scanf("%d", &x);
LNode* p = NULL; //p用来指向链表的最后一个节点
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = x;
node->next = NULL;
if (L == NULL) { //创建的是第一个节点
L = node; //node为创建的第一个节点
p = node;
}
else { //不是第一个节点
p->next = node; //先动node再动p
p = node;
}
scanf("%d", &x);
}
}
//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos) {
if (pos < 1) { //pos不合法
return NULL;
}
int i = 1; //i用于计数
LNode* p = L; //p用于遍历单链表
while (i < pos && p!=NULL) { //注意p不能为空,为空的时候就是表尾了
p = p->next;
i++;
}
return p;
}
//按值查找节点
LNode* locateElem(LinkedList L, ElemType e) {
LNode* p = L;
while (p != NULL &&p->data!=e) { //p!=NULL可以简写为p
p = p->next;
}
return p;
}
//求表长(从头到尾遍历即可)
int length(LinkedList L) {
LNode* p = L;
int i = 0; //注意i从0开始,p不是空的时候才加一
while (p != NULL) {
p = p->next;
i++;
}
return i;
}
//在第pos位置插入元素e
void insertElem(LinkedList& L, int pos, ElemType e) {
//pos合法范围为:[1,length+1],可以在最后位置后面插入
if (pos<1 || pos>length(L)+1) { //pos不合法,退出
return;
}
//LNode* p = L; //p为遍历指针
int i = 1; //i计数
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = e;
node->next = NULL;
//在第一个位置上插入node
if (pos == 1) {
node->next = L;
L = node;
}
else { //在其余位置包括最后的位置插入node
//while (i < pos - 1) { //找第pos位置的前一个节点
// p = p->next;
// i++;
//}
//直接调用前面写的函数
LNode* pre = getNode(L, pos - 1);
node->next = pre->next;
pre->next = node;
}
}
//删除第pos位置的节点,并且返回被删除节点的元素值e
//void DeleteElem(LinkedList& L, int pos, ElemType& e) {
// //pos合法范围为:[1,length]
// if (pos<1 || pos>length(L)) {
// return;
// }
//
// int i = 1;
// int j = 1;
// LNode* p = L; //p为遍历指针
// LNode* pr = L; //pr为遍历指针
// if (pos == 1) {
// e = L->data;
// L = L->next;
// }
// else {
// while (i < pos) { //找到第pos位置的节点
// i++;
// p = p->next;
// }
// while (j < pos - 1) { //找到第pos-1位置的节点
// j++;
// pr = pr->next;
// }
// e = p->data;
// pr->next=p->next;
// }
//}
void DeleteElem(LinkedList& L, int pos, ElemType& e) {
//pos合法范围为:[1,length]
if (pos<1 || pos>length(L)) {
return;
}
LNode* p = getNode(L, pos); //找到pos位置的节点,要删除的就是它
if (pos == 1) { //删除第一个位置的节点
L = p->next;
}
else { //删除其他位置的节点
LNode* pr=getNode(L, pos-1); //找到pos-1位置的节点
pr->next = p->next;
}
e = p->data;
free(p); //释放空间
}
③Client.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "LinkedList.h"
#include<stdio.h>
#include<stdlib.h>
int main() {
LinkedList L; //创建单链表
initLinkedList(L); //初始化单链表
//createListByHead(L); //头插法创建单链表(输出与输入顺序相反)
createListByTail(L); //尾插法创建单链表(输出与输入顺序一致)
printLinkedList(L); //打印单链表
int res = -1;
DeleteElem(L, 4, res);
printf("删除的节点元素值为:%d\n",res);
printLinkedList(L); //打印单链表
//insertElem(L, 5, 100);
//printf("插入之后的单链表为:\n");
//printLinkedList(L); //打印单链表
/*LNode* cur=getNode(L, 2);
printf("第二个节点的值为%d\n", cur->data);*/
//printf("表长为:%d\n", length(L));
return 0;
}
二、带头结点的单链表基本操作
(1)初始化
🌽代码
cpp
//初始化(需要申请一个头结点,让L指向它)
void initDummyLinkedList(DummyLinkedList& L) {
L = (LNode*)malloc(sizeof(LNode));
L->data = 0; //假设头结点数据域用于存放表长信息
L->next = NULL;
}
头指针始终指向第一个节点,不论是否有头结点。
(2)头插法创建单链表
🌽代码
cpp
//头插法创建带头结点的单链表
void createDummyListByHead(DummyLinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = x;
node->next = L->next;
L->next = node;
L->data++; //创建一个就记录一个,表长加一
scanf("%d", &x);
}
}
🍺测试结果

(3)尾插法创建单链表
🌽代码
cpp
//尾插法创建带头结点的单链表
void createDummyListByTail(DummyLinkedList& L) {
ElemType x;
scanf("%d", &x);
LNode* tail = L; //尾指针,最开始指向头结点
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = x;
node->next = NULL;
tail->next = node;
tail = node; //tail总是指向最后一个节点
L->data++; //表长加一
scanf("%d", &x);
}
}
带头结点的单链表 ,头插法和尾插法都不需要额外判断是否创建的是第一个节点。
🍺测试结果

(4)打印
🌽代码
cpp
//打印带头结点的单链表(不打印第一个节点也就是头结点的数据)
void printDummyLinkedList(DummyLinkedList L) {
LNode* p = L->next; //从第一个有真实意义的节点开始打印
while (p != NULL) {
printf("%d->", p->data);
p = p->next;
}
printf("\n");
}
(5)按位查找
🌽代码
cpp
//返回第pos位置上的节点
LNode* getNode(DummyLinkedList L, int pos) {
//pos合法范围是[0,表长],注意可以取0,我们输出头结点即可
//第一个位置是从真实意义的结点开始计算的
if (pos<0 || pos>L->data) {
return NULL;
}
int i = 0; //注意这里i从0开始
LNode* p = L;
while (p != NULL&& i<pos) {
p = p->next;
i++;
}
return p;
}
(6)第pos位置上插入元素e
🌽代码
cpp
//第pos位置上插入元素e
void insertElem(DummyLinkedList& L, int pos, ElemType e) {
//pos的合法范围是:[1,表长+1]
if (pos<1 || pos>L->data + 1) {
return;
}
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = e;
node->next = NULL;
LNode* pr = getNode(L, pos - 1); //找到要插入位置的前一个位置
node->next = pr->next;
pr->next = node;
L->data++; //维护表长
}
🍺测试结果

(7)删除第pos位置的元素节点
🌽代码
cpp
//删除第pos位置的元素节点,并用e返回被删除节点的元素值
void deleteElem(DummyLinkedList& L, int pos, ElemType& e) {
//pos合法范围是[1,表长]
if (pos<1 || pos>L->data) {
return;
}
LNode* pr = getNode(L, pos - 1); //pos-1位置上面的节点
LNode* p = getNode(L, pos); //pos位置上面的节点,要删除的元素
pr->next = p->next;
e=p->data;
L->data--; //维护表长
free(p); //释放空间
}
🍺测试结果

(8)求表长
🌽代码
cpp
//求表长
int length(DummyLinkedList& L) {
return L->data;
}
(9)整体代码
①DummyLinkedList.h
cpp
#pragma once
typedef int ElemType;
typedef struct LNode{
ElemType data; //数据域
LNode* next; //指针域
}LNode,*DummyLinkedList;
//初始化(需要申请一个头结点,让L指向它)
void initDummyLinkedList(DummyLinkedList& L);
//头插法创建带头结点的单链表
void createDummyListByHead(DummyLinkedList& L);
//打印带头结点的单链表
void printDummyLinkedList(DummyLinkedList L);
//尾插法创建带头结点的单链表
void createDummyListByTail(DummyLinkedList& L);
//返回第pos位置上的节点
LNode* getNode(DummyLinkedList L, int pos);
//第pos位置上插入元素e
void insertElem(DummyLinkedList& L, int pos, ElemType e);
//删除第pos位置的元素节点,并用e返回被删除节点的元素值
void deleteElem(DummyLinkedList& L, int pos, ElemType& e);
//求表长
int length(DummyLinkedList& L);
②DummyLinkedList.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "DummyLinkedList.h"
#include<stdio.h>
#include<stdlib.h>
//初始化(需要申请一个头结点,让L指向它)
void initDummyLinkedList(DummyLinkedList& L) {
L = (LNode*)malloc(sizeof(LNode));
L->data = 0; //假设头结点数据域用于存放表长信息
L->next = NULL;
}
//头插法创建带头结点的单链表
void createDummyListByHead(DummyLinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = x;
node->next = L->next;
L->next = node;
L->data++; //创建一个就记录一个,表长加一
scanf("%d", &x);
}
}
//打印带头结点的单链表(不打印第一个节点也就是头结点的数据)
void printDummyLinkedList(DummyLinkedList L) {
LNode* p = L->next; //从第一个有真实意义的节点开始打印
while (p != NULL) {
printf("%d->", p->data);
p = p->next;
}
printf("\n");
}
//尾插法创建带头结点的单链表
void createDummyListByTail(DummyLinkedList& L) {
ElemType x;
scanf("%d", &x);
LNode* tail = L; //尾指针,最开始指向头结点
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = x;
node->next = NULL;
tail->next = node;
tail = node; //tail总是指向最后一个节点
L->data++; //表长加一
scanf("%d", &x);
}
}
//返回第pos位置上的节点
LNode* getNode(DummyLinkedList L, int pos) {
//pos合法范围是[0,表长],注意可以取0,我们输出头结点即可
//第一个位置是从真实意义的结点开始计算的
if (pos<0 || pos>L->data) {
return NULL;
}
int i = 0; //注意这里i从0开始
LNode* p = L;
while (p != NULL&& i<pos) {
p = p->next;
i++;
}
return p;
}
//第pos位置上插入元素e
void insertElem(DummyLinkedList& L, int pos, ElemType e) {
//pos的合法范围是:[1,表长+1]
if (pos<1 || pos>L->data + 1) {
return;
}
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = e;
node->next = NULL;
LNode* pr = getNode(L, pos - 1); //找到要插入位置的前一个位置
node->next = pr->next;
pr->next = node;
L->data++; //维护表长
}
//删除第pos位置的元素节点,并用e返回被删除节点的元素值
void deleteElem(DummyLinkedList& L, int pos, ElemType& e) {
//pos合法范围是[1,表长]
if (pos<1 || pos>L->data) {
return;
}
LNode* pr = getNode(L, pos - 1); //pos-1位置上面的节点
LNode* p = getNode(L, pos); //pos位置上面的节点,要删除的元素
pr->next = p->next;
e=p->data;
L->data--; //维护表长
free(p); //释放空间
}
//求表长
int length(DummyLinkedList& L) {
return L->data;
}
③Client.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"DummyLinkedList.h"
#include<stdio.h>
#include<stdlib.h>
int main() {
DummyLinkedList L; //创建带头结点的单链表
initDummyLinkedList(L); //初始化
//createDummyListByHead(L); //头插法
createDummyListByTail(L); //尾插法创建
printDummyLinkedList(L); //打印
//insertElem(L, 5, 100); //插入
ElemType x;
deleteElem(L, 8, x);
printDummyLinkedList(L); //打印链表
printf("被删除的元素值为:%d\n", x);
return 0;
}
三、循环单链表(带头结点)
-
带头结点的循环单链表 :尾指针指向头结点(没有真实意义的节点),头结点后面是首节点

-
不带头结点的循环单链表 :尾指针指向首节点(有真实意义的节点)

【相关操作】

带头结点的循环单链表 ,若是要**删除最后一个节点**,时间复杂度就不是O(1)了,因为**必须要找到最后节点的前面一个**,让它指向L.head,然后删除最后一个节点。
但是,在**最后一个位置插入节点**,只需要让尾指针
tail指向它即可,它再指向头指针。
(1)结构体
🌽代码
cpp
//结构体
typedef struct LNode{ //仅代表一个节点
ElemType data; //数据域
LNode* next; //指针域
LNode* tail; //每一个都加尾节点,会导致空间的浪费
}LNode;
typedef struct { //代表一个链表
LNode* head; //头结点
LNode* tail; //尾节点
int length; //长度
}LinkedList;
⭐注意
若每一个都加尾节点,会导致空间的浪费。它只有一个节点是尾节点。

(2)初始化循环单链表
🌱分析

🌽代码
cpp
//初始化循环单链表(带头结点)
void initLinkedList(LinkedList& L) {
L.head = (LNode*)malloc(sizeof(LNode)); //创建一个头结点
L.tail = L.head; //尾指针tail初始指向head
L.tail->next = L.head; //最后一个节点的后继指向头(循环链表)
L.length = 0; //表长初始为0
}
(3)头插法创建循环单链表
🌱分析
<1> 创建的是第一个节点

<2> 创建的不是第一个节点
注意,只有创建第一个节点的时候才需要动tail指针,因为头插法会导致第一个创建的节点一直是尾节点(一直在往头部插入节点),所以后面创建的节点不需要再动tail指针。

【写法1】
🌽代码
cpp
//头插法创建循环单链表(带头结点)
void createLinkedListWithHead(LinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = x;
if (L.head==L.tail) { //创建的是第一个节点
node->next = L.head->next; //最开始L.head->next就是head
L.head->next = node;
//注意tail需要移动到最后的节点处(创建第一个节点的时候才动它)
L.tail = node; //创建的第一个节点将是最后一个节点
}
else { //创建的不是第一个节点
//头插法,第一个创建的节点会是最后一个节点,往后创建的节点无需再动tail
node->next = L.head->next;
L.head->next = node;
}
L.length++; //表长加一
scanf("%d", &x);
}
}
【写法2】采用这种写法
🌽代码
cpp
void createLinkedListWithHead(LinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = x;
node->next = L.head->next;
L.head->next = node;
if (L.head == L.tail) { //只有创建的是第一个节点,才需要动tail
L.tail = node; //之后创建的节点都在前面
}
L.length++; //表长加一
scanf("%d", &x);
}
}
🍺测试结果

(4)尾插法创建循环单链表
🌱分析
每次新创建的作为尾节点,指向头节点。

🌽代码
cpp
//尾插法创建循环单链表
void createLinkedListWithTail(LinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = x;
//无需判断创建的是不是第一个节点,每次让新来的作为尾节点,指向头结点
node->next = L.head;
L.tail->next = node;
L.tail = node;
L.length++;
scanf("%d", &x);
}
}
🍺测试结果

(5)打印
🌽代码
cpp
//打印链表
void printLinkedList(LinkedList L) {
LNode* p = L.head->next; //遍历指针p指向第一个有真实意义的节点
while (p != L.head) { //当p重新回到头结点的时候,就说明已经遍历好了一轮
printf("%d->", p->data);
p = p->next; //p指针后移
}
printf("\n"); //换行
}
(6)返回第pos位置上的节点
🌱分析
【案例1】

【案例2】

🌽代码
cpp
//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos) {
//pos合法范围:[0,表长]
if (pos<0 || pos>L.length) {
return NULL;
}
if (pos == 0) { //0号位置是头结点,没有存真实意义的数据
return L.head; //返回头结点
}
int i = 1;
LNode* p = L.head->next; //p从首节点(第一个有真实意义的节点)开始往后遍历
while (i < pos && p != L.head) {
p = p->next;
i++;
}
//p若是等于头结点说明没找到第pos位置,返回空即可;否则返回p
return p == L.head ? NULL : p;
}
(7)向表尾插入元素e
🌽代码
cpp
//向表尾插入元素e
void insertElemToTail(LinkedList& L, ElemType e) {
LNode* node = (LNode*)malloc(sizeof(LNode)); //申请空间
node->data = e;
L.tail->next = node;
node->next = L.head;
L.tail=node;
L.length++;
}
🍺测试结果

(8)从cur节点处遍历循环单链表
🌽代码
cpp
//从cur节点处遍历循环单链表
void printLinkedList2(LNode* cur, LinkedList L) {
LNode* p = cur; //记录cur结点的位置
for (int i = 0; i < L.length+1; i++) { //因为有头节点,所以需要遍历到表长
if (p != L.head) { //头节点没有元素,无需打印
printf("%d->", p->data);
}
p = p->next;
}
printf("\n");
}
🍺测试结果

又如:

(9)整体代码
①LinkedList.h
cpp
#pragma once
typedef int ElemType;
//结构体
typedef struct LNode{ //代表一个节点
ElemType data; //数据域
LNode* next; //指针域
}LNode;
typedef struct { //代表链表
LNode* head; //头结点
LNode* tail; //尾节点
int length; //长度
}LinkedList;
//初始化单链表
void initLinkedList(LinkedList& L);
//头插法创建循环单链表
void createLinkedListWithHead(LinkedList& L);
//尾插法创建循环单链表
void createLinkedListWithTail(LinkedList& L);
//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos);
//向表尾插入元素e
void insertElemToTail(LinkedList& L, ElemType e);
//打印链表
void printLinkedList(LinkedList L);
//从cur节点处遍历循环单链表
void printLinkedList2(LNode* cur, LinkedList L);
②LinkedList.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "LinkedList.h"
#include<stdio.h>
#include<stdlib.h>
//初始化单链表(带头结点)
void initLinkedList(LinkedList& L) {
L.head = (LNode*)malloc(sizeof(LNode)); //创建一个头结点
L.tail = L.head; //尾指针tail初始指向head
L.tail->next = L.head; //最后一个节点的后继指向头(循环链表)
L.length = 0; //表长初始为0
}
//头插法创建循环单链表(带头结点)
//void createLinkedListWithHead(LinkedList& L) {
// ElemType x;
// scanf("%d", &x);
// while (x != 999) {
// LNode* node = (LNode*)malloc(sizeof(LNode));
// node->data = x;
// if (L.head==L.tail) { //创建的是第一个节点
// node->next = L.head->next; //最开始L.head->next就是head
// L.head->next = node;
// //注意tail需要移动到最后的节点处(创建第一个节点的时候才动它)
// L.tail = node; //创建的第一个节点将是最后一个节点
// }
// else { //创建的不是第一个节点
// //头插法,第一个创建的节点会是最后一个节点,往后创建的节点无需再动tail
// node->next = L.head->next;
// L.head->next = node;
// }
// L.length++; //表长加一
// scanf("%d", &x);
// }
//}
void createLinkedListWithHead(LinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = x;
node->next = L.head->next;
L.head->next = node;
if (L.head == L.tail) { //只有创建的是第一个节点,才需要动tail
L.tail = node; //之后创建的节点都在前面
}
L.length++; //表长加一
scanf("%d", &x);
}
}
//尾插法创建循环单链表
void createLinkedListWithTail(LinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = x;
//无需判断创建的是不是第一个节点,每次让新来的作为尾节点,指向头结点
L.tail->next = node;
node->next = L.head;
L.tail = node;
L.length++;
scanf("%d", &x);
}
}
//返回第pos位置上的节点
LNode* getNode(LinkedList L, int pos) {
//pos合法范围:[0,表长]
if (pos<0 || pos>L.length) {
return NULL;
}
if (pos == 0) {
return L.head;
}
int i = 1;
LNode* p = L.head->next;
while (i < pos && p != L.head) {
p = p->next;
i++;
}
//p若是等于头结点说明没找到第pos位置,返回空即可;否则返回p
return p == L.head ? NULL : p;
}
//向表尾插入元素e
void insertElemToTail(LinkedList& L, ElemType e) {
LNode* node = (LNode*)malloc(sizeof(LNode));
node->data = e;
L.tail->next = node;
node->next = L.head;
L.tail=node;
L.length++;
}
//打印链表
void printLinkedList(LinkedList L) {
LNode* p = L.head->next; //遍历指针p指向第一个有真实意义的节点
while (p != L.head) {
printf("%d->", p->data);
p = p->next;
}
printf("\n");
}
//从cur节点处遍历循环单链表
void printLinkedList2(LNode* cur, LinkedList L) {
LNode* p = cur;
for (int i = 0; i < L.length+1; i++) { //因为有头节点,所以需要遍历到表长
if (p != L.head) { //头节点没有元素,无需打印
printf("%d->", p->data);
}
p = p->next;
}
printf("\n");
}
③Client.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "LinkedList.h"
#include<stdio.h>
#include<stdlib.h>
int main() {
LinkedList L;
initLinkedList(L);
//createLinkedListWithHead(L);//头插法创建
createLinkedListWithTail(L); //尾插法创建
printLinkedList(L); //打印
//insertElemToTail(L, 100); //表尾插入100
//printLinkedList(L); //打印
for (int i = 1; i < 10; i++) {
LNode* p = getNode(L, i); //返回第i个位置的节点
printLinkedList2(p, L); //从链表第i个节点处打印链表
}
return 0;
}
四、双链表
(1)介绍
【单链表】

【双链表】

若是删除4号结点 ,双链表 可以直接找到前驱结点,让其指向空即可,时间复杂度为O(1)。
而单链表 需要遍历找到前驱的3号结点,然后让它指向空,时间复杂度为O(n)。
如下图:

(2)结构体
🌽代码
cpp
//结构体
typedef struct DNode {
ElemType data; //数据域
DNode* next; //用来存放后继结点的指针
DNode* prior; //用来存放前驱结点的指针
}DNode,*DLinkedList;
(3)初始化带头结点的双链表
假设带头结点。

🌽代码
cpp
//双链表的初始化
void initDLinkedList(DLinkedList& L) {
L = (DNode*)malloc(sizeof(DNode)); //创建一个头结点
L->next = NULL;
L->prior = NULL;
L->data = 0; //假设用头结点的数据域来存放表长
}
(4)打印双链表
🌽代码
cpp
//打印双链表
void printDList(DLinkedList L) {
DNode* p = L->next; //从第一个有真实意义的节点开始遍历输出
while (p) { //p不空的时候一直遍历
printf("%d->", p->data);
p = p->next;
}
printf("\n"); //换行
}
(5)头插法创建双链表
🌱分析


🌽代码
cpp
//头插法创建双链表
void createDListWithHead(DLinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
DNode* node = (DNode*)malloc(sizeof(DNode));
node->data = x;
node->next = NULL;
node->prior = NULL;
node->next = L->next;
if (L->next != NULL) { //创建的node不是第一个节点
L->next->prior = node;
}
node->prior = L;
L->next = node;
L->data++;
scanf("%d", &x);
}
}
🍺测试结果

(6)尾插法创建双链表
🌱分析
头插法需要看插入的是否是第一个结点,而尾插法不需要。

若创建的不是第一个结点,代码和上面一样。

🌽代码
cpp
//尾插法创建双链表
void createDListWithTail(DLinkedList& L) {
ElemType x;
scanf("%d", &x);
DNode* tail = L; //尾指针初始指向头节点
while (x != 999) {
DNode* node = (DNode*)malloc(sizeof(DNode));
node->data = x;
tail->next = node;
node->prior = tail;
tail = node;
tail->next = NULL;
L->data++; //表长加一
scanf("%d", &x); //继续输入下一个
}
}
🍺测试结果

(7)返回第pos位置上的结点
🌽代码
cpp
//返回第pos位置上的结点
DNode* getNode(DLinkedList L, int pos) {
if (pos<0 || pos>L->data) { //非法位置直接返回NULL
return NULL;
}
//遍历链表
int i = 0;
DNode* p = L; //遍历指针P初始指向表头L
while (p!=NULL && i < pos) {
i++;
p = p->next;
}
return p;
}
(8)在第pos个位置插入元素e
🌱分析


🌽代码
cpp
//在第pos个位置插入元素e
void insertElem(DLinkedList& L, int pos, ElemType e) {
DNode* pre = getNode(L, pos - 1); //找到第pos位置的前驱结点
DNode* node = (DNode*)malloc(sizeof(DNode)); //创建要插入的结点
node->data = e;
//若不是在最后一个结点位置上面插入
if (pos != L->data+1) { //最后一个位置是在表长加一的位置
pre->next->prior = node;
}
node->next = pre->next;
node->prior = pre;
pre->next = node;
L->data++;
}
🍺测试结果

(9)删除第pos位置上的元素
🌱分析

🌽代码
cpp
//删除第pos位置上的元素,用e接收被删除的元素值
void deleteElem(DLinkedList& L, int pos, ElemType& e) {
//pos合法性这里省略
DNode* pre=getNode(L, pos-1); //找到第pos位置的前一个结点
DNode* removed = getNode(L, pos); //找到第pos位置的结点
if (pos==L->data) { //当pos等于表长的时候,删除的是最后一个结点
pre->next = NULL;
}
else { //删除的不是最后一个结点
pre->next = removed->next;
removed->next->prior = pre;
}
L->data--; //表长减一
e = removed->data;
free(removed); //释放空间
}
🍺测试结果

(10)整体代码
①DLinkedList.h
cpp
#pragma once
typedef int ElemType;
//结构体
typedef struct DNode {
ElemType data; //数据域
DNode* next; //用来存放后继结点的指针
DNode* prior; //用来存放前驱结点的指针(单链表没有)
}DNode,*DLinkedList;
//DNode强调的是一个结点,DLinkedList强调的是一个链表,语义不同罢了
//DNode* p与DLinkedList p一样
//双链表的初始化
void initDLinkedList(DLinkedList& L);
//头插法创建双链表
void createDListWithHead(DLinkedList& L);
//尾插法创建双链表
void createDListWithTail(DLinkedList& L);
//返回第pos位置上的结点
DNode* getNode(DLinkedList L, int pos);
//在第pos个位置插入元素e
void insertElem(DLinkedList& L, int pos, ElemType e);
//删除第pos位置上的元素,用e接收被删除的元素值
void deleteElem(DLinkedList& L, int pos, ElemType& e);
//打印双链表
void printDList(DLinkedList L);
②DLinkedList.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "DLinkedList.h"
#include<stdio.h>
#include<stdlib.h>
//双链表的初始化
void initDLinkedList(DLinkedList& L) {
L = (DNode*)malloc(sizeof(DNode)); //创建一个头结点
L->next = NULL;
L->prior = NULL;
L->data = 0; //假设用头结点的数据域来存放表长
}
//头插法创建双链表
void createDListWithHead(DLinkedList& L) {
ElemType x;
scanf("%d", &x);
while (x != 999) {
DNode* node = (DNode*)malloc(sizeof(DNode));
node->data = x;
node->next = NULL;
node->prior = NULL;
node->next = L->next;
if (L->next != NULL) { //创建的node不是第一个节点
L->next->prior = node;
}
node->prior = L;
L->next = node;
L->data++;
scanf("%d", &x);
}
}
//尾插法创建双链表
void createDListWithTail(DLinkedList& L) {
ElemType x;
scanf("%d", &x);
DNode* tail = L; //尾指针初始指向头节点
while (x != 999) {
DNode* node = (DNode*)malloc(sizeof(DNode));
node->data = x;
tail->next = node;
node->prior = tail;
tail = node;
tail->next = NULL;
L->data++; //表长加一
scanf("%d", &x); //继续输入下一个
}
}
//返回第pos位置上的结点
DNode* getNode(DLinkedList L, int pos) {
if (pos<0 || pos>L->data) { //非法位置直接返回NULL
return NULL;
}
//遍历链表
int i = 0;
DNode* p = L; //遍历指针P初始指向表头L
while (p!=NULL && i < pos) {
i++;
p = p->next;
}
return p;
}
//在第pos个位置插入元素e
void insertElem(DLinkedList& L, int pos, ElemType e) {
DNode* pre = getNode(L, pos - 1); //找到第pos位置的前驱结点
DNode* node = (DNode*)malloc(sizeof(DNode)); //创建要插入的结点
node->data = e;
//若不是在最后一个结点位置上面插入
if (pos != L->data+1) { //最后一个位置是在表长加一的位置
pre->next->prior = node;
}
node->next = pre->next;
node->prior = pre;
pre->next = node;
L->data++;
}
//删除第pos位置上的元素,用e接收被删除的元素值
void deleteElem(DLinkedList& L, int pos, ElemType& e) {
//pos合法性这里省略
DNode* pre=getNode(L, pos-1); //找到第pos位置的前一个结点
DNode* removed = getNode(L, pos); //找到第pos位置的结点
if (pos==L->data) { //当pos等于表长的时候,删除的是最后一个结点
pre->next = NULL;
}
else { //删除的不是最后一个结点
pre->next = removed->next;
removed->next->prior = pre;
}
L->data--; //表长减一
e = removed->data;
free(removed); //释放空间
}
//打印双链表
void printDList(DLinkedList L) {
DNode* p = L->next; //从第一个有真实意义的节点开始遍历输出
while (p) { //p不空的时候一直遍历
printf("%d->", p->data);
p = p->next;
}
printf("\n"); //换行
}
③Client.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "DLinkedList.h"
#include<stdio.h>
#include<stdlib.h>
int main() {
DLinkedList L; //定义双链表
initDLinkedList(L); //初始化双链表
//createDListWithHead(L); //头插法创建双链表
createDListWithTail(L); //尾插法创建双链表
printDList(L); //打印
//insertElem(L,5,1000);
//printDList(L); //打印
ElemType x;
deleteElem(L, 5, x);
printDList(L); //打印
printf("被删除的元素值为:%d\n", x);
return 0;
}