b站懒猫数据结构课程笔记:https://www.bilibili.com/read/cv8013121?spm_id_from=333.999.0.0
一、链表的概念
- 单链表:线性表的链接存储结构
-
单链表存储特点:
-
逻辑次序和物理次序不一定相同
-
元素之间的逻辑关系用指针表示
-
举例:(a1, a2, a3, a4)的存储示意图
- 单链表是由若干个结点构成的;
- 单链表的结点只有一个指针域
-
-
单链表的结点结构:
typedef struct node { DataType data; // 数据域(可以是任意类型) struct node *next; // 指针域 } Node, *Link; // Node为node类型的别名,Link为node类型的指针别名 Node st; // 声明一个Node类型的结构体st,等价于struct node st; Link p; // 等价于struct node *p;
- 引用数据元素:
(*p).data
,但建议用p->data
- 引用指针域:
p->next
- 引用数据元素:
-
什么是存储结构?
- 头指针:指向第一个结点的地址
- 尾标志:终端结点的指针域为空
空表和非空表不统一,不便于编程,如何将空表和非空表统一???
- 头节点不存储数据
二、单链表的实现
2.1 单链表的遍历操作
- 操作接口:
void displayNode(Link head);
void displayNode(Link head) {
p = head->next; // head的next域就是指向第一个有效结点
while(p != NULL) { // 链表p为空的时候就直接退出了
printf("%d", p->data); // 打印p对应的数据域data
p = p->next; // p的next域就是下一个结点的位置,往后移一位
}
}
step 1:
step 2:
…
最后一次:p的next域指向了尾指针,p = NULL,从而退出循环
-
注意:不能将
p = p->next
改成p++
来实现指针后移-
因为链表中结点的存储不是连续的,是零散的分配在内存中
-
2.2 求单链表的元素个数
- 操作接口:
int length(Link head);
- 初始化让p指向第一个有效结点,并初始化累加器count
int length(Link head) {
p = head->next; // head的next域就是指向第一个有效结点
count = 0;
while(p != NULL) { // 链表p为空的时候就直接退出了
p = p->next; // p的next域就是下一个结点的位置,往后移一位
count++;
}
return count;
}
2.4 单链表的查找操作
bool queryNode(Link head, DataType x) { // DataType x是准备查找的数据;head是头指针
p = head->next;
while ( p!=NULL ) {
if ( p->data == x ) {
print(data); // 找到则调用输出函数,并提前返回true
return true;
}
p = p->next;
}
// 如果循环结束了,说明没有找到
return false;
}
2.5 单链表的插入操作
- 操作接口:
void insertNode(Link head, int i, DataType x);
- 定义要插入的数据的结点node
- 开辟一块存储空间,让node指向该空间(
node = (Link)malloc(sizeof(Node))
) - 将所希望插入的数据元素x赋给node的数据域(
node->data = x
) - 将
a
i
−
1
a_{i-1}
ai−1对应的p的next域赋给插入结点node(
node->next= p->next
)
- 开辟一块存储空间,让node指向该空间(
- 将 a i − 1 a_{i-1} ai−1对应的p的next域指向结点node(`node->next = p)
- 定义要插入的数据的结点node
需要注意分析边界请况:表头,表尾
bool insertNode(Link head, int i, DataType x) {
p = head; // 工作指针p指向头结点
count = 0;
while ( p != NULL && count < i-1 ) { // 查找第i-1个结点
p = p->next;
count++;
}
if ( p == NULL ) {
return false; // 没有找到第i-1个结点
} else {
node = (Link)malloc(sizeof(Node)); // 申请一个结点node
node->data = x;
node->next = p->next; // 结点node插入结点p之后
p->next = node;
return node;
}
}
2.6 创建一个单链表
2.6.1 头插法
- 操作接口:
Link newList(DataType a[], int n)
- 头插法:将待插入结点插在头结点的后面
注意:创建一个结点时,最好立马将结点的next域设置为空
- 顺序和数组相反
template<class DataType>
Link newList(DataType a[], int n) {
// 创建头结点
head = (Link)malloc(sizeof(Node));
head->next = NULL;
// 创建后续结点
for ( i = 0; i < n; i++ ) {
node = (Link)malloc(sizeof(Node));
node->data = a[i];
node->next = head->next;
head->next = node;
}
return head;
}
2.6.2 尾插法
- 操作接口:
Link newList(DataType a[], int n)
- 尾插法:将待插入结点插在终端结点的后面
- head表示头结点,rear表示尾结点
Link newList(DataType a[], int n) {
head = (Link)malloc(sizeof(Node)); // 生成头指针
head->next = NULL;
rear = head; // 尾指针初始化
for ( i = 0; i < n; i++ ) {
node = (Link)malloc(sizeof(Node));
node->data = a[i];
// node->next = NULL; 加上这句,则后面的rear->next = NULL;就不需要了
rear->next = node;
rear = node;
}
rear->next = NULL;
return head;
}
2.7 单链表的删除
- 操作接口:
bool deleteNode(Link head, DataType x);
bool deleteNode(Link head, DataType x) {
if ( head == NULL || head->next == NULL ) { // 若链表没有数据
return false;
}
p = head->next; // 初始化。p,q两个指针一前一后
q = head;
while ( p != NULL ) {
if ( p->data == x ) { // 找到x结点,删除这个结点,并提前返回
q -> next = p->next;
free(p);
return true;
} else {
q = p;
p = p->next;
}
}
// 如果循环结束了,说明没有找到和x相等的结点
return false;
}
2.8 单链表的释放
三、循环链表的实现
- 循环链表插入(相关操作和单链表相似)