单链表
- [1. 链表的概念以及结构](#1. 链表的概念以及结构)
- [2. 单链表的模拟实现](#2. 单链表的模拟实现)
-
- 接口总览
- [2.1 单链表的尾插和尾删](#2.1 单链表的尾插和尾删)
-
- [2.1.1 尾插](#2.1.1 尾插)
- [2.1.2 尾删](#2.1.2 尾删)
- [2.2 头插和头删](#2.2 头插和头删)
-
- [2.2.1 头插](#2.2.1 头插)
- [2.2.2 头插的应用(链表的逆置)](#2.2.2 头插的应用(链表的逆置))
- [2.2.3 头删](#2.2.3 头删)
- [2.3 查找链表中的元素](#2.3 查找链表中的元素)
- [2.4 在任意位置插入元素](#2.4 在任意位置插入元素)
- [2.5 删除任意位置的元素](#2.5 删除任意位置的元素)
- [2.6 遍历链表 和 销毁链表](#2.6 遍历链表 和 销毁链表)
- [3. 链表 VS 顺序表](#3. 链表 VS 顺序表)
-
- [3.1 比较存储结构](#3.1 比较存储结构)
- [3.2 从基本操作比较](#3.2 从基本操作比较)
1. 链表的概念以及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
单链表结构:

注意:
- 从图上可以看出,单链表在逻辑结构上是连续的,但是物理结构上不一定连续。
- 在实际过程中我们单链表的节点都是从堆上申请出来的。
- 从堆上的空间是编译器分配出来的,因此申请的空间地址有可能连续,也有可能不连续。
2. 单链表的模拟实现
接口总览
c
typedef int SLDateType;
typedef struct SListNode
{
SLDateType date;
struct SListNode* next;
}SLNode;
//打印链表
void SLPrint(SLNode* plist);
//尾插
void SLPushBack(SLNode** plist, SLDateType num);
//头插
void SLPushFront(SLNode** plist, SLDateType num);
//尾删
void SLPopBack(SLNode** plist);
//头删
void SLPopFront(SLNode** plist);
//查找链表中的元素
SLNode* SLFind(SLNode* plist, SLDateType num);
//在任意位置插入元素
void SLInsert(SLNode** plist, SLNode* pos, SLDateType num);
//删除任意位置的元素
void SLErase(SLNode** plist, SLNode* pos);
//摧毁链表
void SLDestory(SLNode** plist);
2.1 单链表的尾插和尾删
2.1.1 尾插
c
void SLTPushBack(SLNode** pphead, SLNDataType x)
{
assert(pphead);
SLNode* newnode = CreateNode(x);
//判断空链表
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
// 找尾
SLNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
2.1.2 尾删
c
void SLTPopBack(SLNode** pphead)
{
assert(pphead);
//assert(*pphead);
// 温柔的检查
//if (*pphead == NULL)
// return;
// 空
assert(*pphead);
// 1、一个节点
// 2、一个以上节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLNode* tail = *pphead;
while (tail->next->next != NULL) // 找到尾结点的前驱结点
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
2.2 头插和头删
2.2.1 头插
c
void SLTPushFront(SLNode** pphead, SLNDataType x)
{
assert(pphead);
SLNode* newnode = CreateNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
2.2.2 头插的应用(链表的逆置)
根据头插法将源链表的头结点,头插入新建的newNode 链表中。




c
struct ListNode* ReverseList(struct ListNode* head ) {
struct ListNode* cur = head ;
struct ListNode * newNode = NULL;
while(cur != NULL)
{
struct ListNode * next = cur ->next ;
cur->next = newNode;
newNode = cur;
cur = next ;
}
return newNode ;
}
2.2.3 头删
c
void SLTPopFront(SLNode** pphead)
{
assert(pphead);
// 空
assert(*pphead);
SLNode* tmp = *pphead;
*pphead = (*pphead)->next;
free(tmp);
}
2.3 查找链表中的元素
按值查找 时间复杂度为: O(N)
c
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{
SLNode* cur = phead;
while (cur)
{
if (cur->val == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
2.4 在任意位置插入元素
c
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{
// 严格限定pos一定是链表里面的一个有效节点
assert(pphead);
assert(pos);
assert(*pphead);
if (*pphead == pos)
{
// 头插
SLTPushFront(pphead, x);
}
else
{
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLNode* newnode = CreateNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
2.5 删除任意位置的元素
c
void SLTErase(SLNode** pphead, SLNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
if (*pphead == pos)
{
// 头删
SLTPopFront(pphead);
}
else
{
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
2.6 遍历链表 和 销毁链表
时间复杂度为 O(N)
c
void SLTPrint(SLNode* phead)
{
SLNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->val);
cur = cur->next;
}
printf("NULL\n");
}
c
void SLTDestroy(SLNode** pphead)
{
assert(pphead);
SLNode* cur = *pphead;
while (cur)
{
SLNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
3. 链表 VS 顺序表
3.1 比较存储结构
顺序表
- 优点:支持随机存储 ,存储密度高
- 缺点:大片连续空间分配不方便 ,更改容量不方便
链表
- 优点:离散的小空间分配方便,方便更改容量
- 缺点:不可以随机存取,存储密度底
3.2 从基本操作比较
一、创建
顺序表
- 需要分配大片连续的空间,若空间过小则之后不方便扩容 ,若空间过大则回造成空间的浪费
- 静态分配:数组容量不可改变
- 动态分配:容量可以改变单是存入数据需要移动大量的元素,时间代价高
链表
- 只需要分配结点即可,之后方便拓展
- 相较于顺序表灵活性高
二、销毁
顺序表
- 静态: 系统回收
- 动态: 需要手动"fee" 回收堆中的空间
链表
- 一次删除各个结点 "fee" 注意要将链表置空
三、 增加和删除
顺序表 :增删都需要移动元素,前移或是后移 时间复杂度为 O(N) 开销主要来自移动元素 (代价高)
链表:只需要插入和删除修改指针既可 时间复杂度为 O(N) ,开销主要来自查找目标元素 (代价底)
四、 查找
顺序表 : 按位查找时间复杂度为 O(1)、按值查找时间复杂度为 O(N)有序情况下为O(logn)
链表: 按位查找时间复杂度为 O(N)、按值查找时间复杂度为 O(N)都只能从头结点开始查找
注 : 顺序表的查找效率比链表高
链表------表长难以估计、经常要增加和删除元素 。
顺序表------表长可以估计、查询操作较多。