<>
单链表
定义 :单链表是由节点组成的,每个节点包含两部分,一部分是数据域,用于存储数据元素;另一部分是指针域,用于存储指向下一个节点的指针。通过指针将各个节点依次连接起来,形成一个链式结构,每个结点结点都有一个指针指向下一个结点,最后一个节点的指针通常指向空,表示链表的结束。
1.单链表结构体
c
//单链表的结构体
struct LNode{
ElemType data;//定义单链表结点类型
struct LNode *next;//定义指向下一个节点
};
typedef struct LNode LNode;
typedef struct LNode LinkList;
||
typedef struct LNode{
ElemType data;//定义单链表结点类型
struct LNode *next;//定义指向下一个节点
}LNode,*LinkList;
typedef
关键字--数据类型重命名
typedef<数据类型><别名>
为了增加代码的维护性对部分代码进行包装
c
/**
* tool function
* 根据索引遍历链表,找到第i个节点
* @param temp_p 链表头指针
* @param i 要查找的位置
* @param temp_j 输出参数,记录实际遍历到的位置
* @return 找到的节点指针,如果位置不合法或链表为空则返回NULL
*/
LNode* traverseByIndex(LNode* temp_p, int i, int &temp_j){
LNode* p = temp_p;
int j = 0;
while (p!= NULL && j < i) {
p = p->next;
j++;
}
temp_j = j;
return p;
}
2.单链表的插入

c
/**
* 在单链表的第i个位置插入元素e
* @param L 链表头指针的引用
* @param i 插入位置(从1开始计数)
* @param e 要插入的元素
* @return 插入成功返回true,否则返回false
*/
bool LinkListInsert(LinkList &L, int i, ElemType e) {
int j = 0;
LNode* p = traverseByIndex(L, i - 1, j); // 遍历到第i-1个节点,为插入做准备
if (p == NULL) { // 非法区域,位置i超出链表范围或链表为空
return false;
}
LNode* s = (LNode*)malloc(sizeof(LNode)); // 申请要插入的结点
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
时间复杂度分析 :插入操作主要的时间开销在于找到第i - 1
个节点,调用了traverseByIndex
函数,最坏情况下需要遍历i - 1
个节点,因此时间复杂度为O (i ),在最坏情况下(即i
等于链表长度n
)时间复杂度为O (n )。而插入节点本身的操作(修改指针指向)时间复杂度为O(1)。
注意\] :`p->next = s` 与 `s->next = p->next`位置不可以调换 不然会出现后面的结点丢失的情况
#### 3.单链表的删除

```c
/**
* 删除单链表的第i个位置的元素,并将删除的元素值存储到e中
* @param L 链表头指针的引用
* @param i 删除位置(从1开始计数)
* @param e 用于存储删除的元素值
* @return 删除成功返回true,否则返回false
*/
bool ListDelete(LinkList &L, int i, ElemType &e) {
int j = 0;
LNode* p = traverseByIndex(L, i - 1, j);
if (p == NULL || j > i - 1) { // 指向空区域或i不合法,位置i超出链表范围或链表为空
return false;
}
LNode* q = p->next;
e = q->data;
p->next = q->next;
free(q);
return true;
}
```
**时间复杂度分析** :删除操作主要的时间开销在于找到第`i - 1`个节点,调用了`traverseByIndex`函数,最坏情况下需要遍历`i - 1`个节点,因此时间复杂度为(O(i)),在最坏情况下(即`i`等于链表长度`n`)时间复杂度为(O(n))。而删除节点本身的操作(修改指针指向和释放内存)时间复杂度为(O(1))。
#### 4.获取链表元素(位置\|\|元素)
```c
/**
* 根据位置获取单链表中的元素
* @param L 链表头指针的引用
* @param i 要获取的元素位置(从1开始计数)
* @return 找到的节点指针,如果位置不合法或链表为空则返回NULL
*/
LNode* GetElemByIndex(LinkList &L, int i) {
int j = 0;
LNode* p = traverseByIndex(L, i, j);
return p; // p为NULL或者想查找的结点
}
/**
* 根据值获取单链表中的元素
* @param L 链表头指针的引用
* @param e 要查找的值
* @return 找到的节点指针,如果未找到则返回NULL
*/
LNode* GetElemByDate(LinkList &L, ElemType e) {
LNode* p = L;
while (p!= NULL && p->data != e) {
p = p->next;
}
return p; // p为NULL或者想查找的结点
}
```
* `GetElemByIndex`函数:主要依赖于`traverseByIndex`函数,最坏情况下需要遍历`i`个节点,因此时间复杂度为(O(i)),在最坏情况下(即`i`等于链表长度`n`)时间复杂度为(O(n))。
* `GetElemByDate`函数:需要从链表头开始依次遍历,直到找到值为`e`的节点或者遍历到链表末尾,最坏情况下需要遍历整个链表,因此时间复杂度为(O(n))。
#### 5.头插法
顾名思义就是不断在第一个位置去插入元素
```c
/**
* 头插法创建单链表
* @param L 链表头指针的引用
* @return 创建好的链表头指针
*/
LinkList List_HeadInsert(LinkList &L){
LNode *s = NULL;
ElemType x = 0;
printf("请输入插入的元素,输入-1停止插入\n");
scanf("%d", &x);
while (x!=-1) {
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = L->next;
L->next = s;
scanf("%d", &x);
}
return L;
}
```
#### 6.尾插法
顾名思义就是不断在表尾位置去插入元素
```c
/**
* 尾插法创建单链表
* @param L 链表头指针的引用
* @return 创建好的链表头指针
*/
LinkList List_RearInsert(LinkList &L){
LNode *s = NULL;
LNode *q = L;
ElemType x = 0;
printf("请输入插入的元素,输入-1停止插入\n");
scanf("%d", &x);
while (x!=-1) {
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
q->next = s;
s->next = NULL;
q = q->next;
scanf("%d", &x);
}
return L;
}
```
#### 7.代码实现
```c
#include