链表种类
链表的种类非常多,一共可以分成8种:
- 单向或者双向
- 带头或者不带头
- 循环或者不循环
单向与双向链表

带头与不带头链表

循环不循环链表

在实际使用,最多使用的还是无头单向不循环链表和带头双向循环链表

对比二者的区别:
- 无头单向非循环链表,结构简单,一般不会单独存储数据,实际中更多会被作为其他数据结构的子结构,例如哈希桶等等
- 带头双向循环链表,结构最为复杂,一般单独存储数据。实际中使用链表的数据结构,都是带头双向循环链表。
代码实现带头双向循环链表
定义链表结构体
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}LTNode;
带头双向循环链表,有两个指针,一个指向下一个结点,一个指向前一个结点
初始化链表
带头双向循环链表需要进行初始化,因为这个链表没有元素的时候需要有一个哨兵位的头结点,并且这个结点的next指向自己,prev指向自己。
- 这里可以体现出循环的优势:最后一个结点的next会指向哨兵位的头,而哨兵位的头的prev会指向最后一个结点
第一种方式:传递二级指针
// 初始化链表
// 第一种方式,传递二级指针
void LTInit(LTNode** pphead)
{
LTNode* cur = *pphead;
cur = BuyNewNode(-1);
if (cur == NULL)
return;
cur->next = cur;
cur->prev = cur;
}
- 在对结构体进行初始化的时候需要传递二级指针,我们需要修改plist指针里面的内容,不能只传递plist这个一级指针,如果只传递一级指针,那么在函数内修改一级指针所指向的内容,函数结束时,并不会影响一级指针本身所指向的内容,这种情况属于一级指针传值;
- 而如果传递二级指针,二级指针解引用后使用一级指针的地址,在这个地址中保存新数据
- 画个图理解
第二种方式:返回指针
// 第二种方式,返回指针
LTNode* LTInit(LTNode* phead)
{
phead = BuyNewNode(-1);
if (phead == NULL)
return NULL;
phead->next = phead;
phead->prev = phead;
return phead;
}
返回指针,可以让一个指针去接收这个新结点
添加新结点
// 添加新结点
LTNode* BuyNewNode(LTDataType x)
{
LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
if (newNode == NULL)
{
perror("newNode malloc fail\n");
return NULL;
}
else
{
newNode->next = NULL;
newNode->prev = NULL;
newNode->data = x;
return newNode;
}
}
添加新结点的方式就是使用返回一个指针,外部会有一个指针去接受申请出来的结点的位置。
销毁链表
// 销毁链表
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
传递的是一级指针,销毁后实参可能会变成野指针,所以需要使用该函数后,即使置空。
链表打印数据
// 双向链表的打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<->", cur->data);
cur = cur->next;
}
printf("head<->\n");
}
【注意】不能让cur指向phead,遍历整个链表
判断链表是否为空
// 判断链表是否为空
bool LTEmpty(LTNode* phead)
{
return phead->next == phead;
}
如果链表中只有头结点返回真,否则返回假S
尾部添加结点
// 尾增
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);// 这个断言是防止phead没有初始化就进行添加结点
LTNode* newNode = BuyNewNode(x);
if (newNode == NULL)
return NULL;
LTNode* tail = phead->prev;
tail->next = newNode;
newNode->prev = tail;
phead->prev = newNode;
newNode->next = newNode;
}
这里使用一级指针的原因是,phead指向的是头结点,这个链表中一定存在一个结点来保存指向下一个结点的地址
尾部删除结点
// 尾增
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);// 这个断言是防止phead没有初始化就进行添加结点
LTNode* newNode = BuyNewNode(x);
if (newNode == NULL)
{
return;
}
LTNode* tail = phead->prev;
tail->next = newNode;
newNode->prev = tail;
phead->prev = newNode;
newNode->next = newNode;
}
链表头插
// 头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
// 保存下一个结点
LTNode* Pnext = phead->next;
LTNode* newNode = BuyNewNode(x);
phead->next = newNode;
newNode->prev = phead;
newNode->next = Pnext;
Pnext->prev = newNode;
}
头插需要提前保存下一个结点,以便链接
链表头删
// 头删
void LTPopFront(LTNode* phead)
{
assert(phead);
// 保存第一个结点
LTNode* cur = phead->next;
// 保存第二个结点
LTNode* curNext = cur->next;
phead->next = curNext;
curNext->prev = phead;
free(cur);
cur->data = 0;
cur->next = cur->prev = NULL;
}
pos位置之前插入一个结点
// pos位置之前插入一个结点
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newNode = BuyNewNode(x);
posPrev->next = newNode;
newNode->prev = posPrev;
newNode->next = pos;
pos->prev = newNode;
}
完成insert函数之后,双向链表的所有插入的函数都可以复用insert函数。
pos位置删除结点
// pos位置删除结点
void LTErase(LTNode* pos)
{
assert(pos);
assert(pos->next);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
pos->next = pos->prev =NULL;
}
链表查找元素
// 链表查找元素
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
注意双向链表一定是不为空的
双向链表的优点和缺点
优点
- 双向遍历,可以从链表头部或者尾部开始访问,快速访问第一个结点和最后一个结点
- 插入或者删除结点非常迅速
缺点
- 存储空间较大,除了自身数据,还需要保存两个指针
- 遍历链表,连续访问链表较慢
顺序表和链表的区别对比

链表和顺序表是两个互补的顺序结构,顺序表支持快速随机访问,链表支持快速随机插入删除元素。
向链表代码实现
List.h头文件
#ifndef LIST_H_
#define LIST_H_
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}LTNode;
//// 初始化链表
//void LTInit(LTNode** pphead);
// 第二种方式,返回指针
LTNode* LTInit(LTNode* phead);
// 双向链表的打印
void LTPrint(LTNode* phead);
// 判断链表是否为空
bool LTEmpty(LTNode* phead);
// 销毁链表
void LTDestory(LTNode* phead);
// 添加新结点
LTNode* BuyNewNode(LTDataType x);
// 尾增
void LTPushBack(LTNode* phead, LTDataType x);
// 尾删
void LTPopBack(LTNode* phead);
// 头插
void LTPushFront(LTNode* phead, LTDataType x);
// 头删
void LTPopFront(LTNode* phead);
// pos位置之前插入一个结点
void LTInsert(LTNode* pos, LTDataType x);
// pos位置删除结点
void LTErase(LTNode* pos);
// 链表查找元素
LTNode* LTFind(LTNode* phead, LTDataType x);
#endif
List.c源文件
#include"List.h"
//// 初始化链表
//// 第一种方式,传递二级指针
//void LTInit(LTNode** pphead)
//{
// LTNode* cur = *pphead;
// cur = BuyNewNode(-1);
// if (cur == NULL)
// return;
// cur->next = cur;
// cur->prev = cur;
//}
// 第二种方式,返回指针
LTNode* LTInit(LTNode* phead)
{
phead = BuyNewNode(-1);
if (phead == NULL)
return NULL;
phead->next = phead;
phead->prev = phead;
return phead;
}
// 双向链表的打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<->", cur->data);
cur = cur->next;
}
printf("head<->\n");
}
// 判断链表是否为空
bool LTEmpty(LTNode* phead)
{
if (phead->next == phead)
{
return true;
}
else
{
return false;
}
}
// 添加新结点
LTNode* BuyNewNode(LTDataType x)
{
LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
if (newNode == NULL)
{
perror("newNode malloc fail\n");
return NULL;
}
else
{
newNode->next = NULL;
newNode->prev = NULL;
newNode->data = x;
return newNode;
}
}
// 尾增
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);// 这个断言是防止phead没有初始化就进行添加结点
LTNode* newNode = BuyNewNode(x);
if (newNode == NULL)
{
return;
}
LTNode* tail = phead->prev;
tail->next = newNode;
newNode->prev = tail;
phead->prev = newNode;
newNode->next = newNode;
}
// 尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
// 注意检查没有结点,只有一个头结点的存在
if (LTEmpty(phead))
return;
LTNode* tail = phead->prev;
LTNode* tailprev = tail->prev;
tailprev->next = phead;
phead->prev = tailprev;
free(tail);
}
// 头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
// 保存下一个结点
LTNode* Pnext = phead->next;
LTNode* newNode = BuyNewNode(x);
phead->next = newNode;
newNode->prev = phead;
newNode->next = Pnext;
Pnext->prev = newNode;
}
// 头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
// 保存第一个结点
LTNode* cur = phead->next;
// 保存第二个结点
LTNode* curNext = cur->next;
phead->next = curNext;
curNext->prev = phead;
free(cur);
cur->data = 0;
cur->next = cur->prev = NULL;
}
// pos位置之前插入一个结点
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newNode = BuyNewNode(x);
posPrev->next = newNode;
newNode->prev = posPrev;
newNode->next = pos;
pos->prev = newNode;
}
// pos位置删除结点
void LTErase(LTNode* pos)
{
assert(pos);
assert(pos->next);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
pos->next = pos->prev =NULL;
}
// 链表查找元素
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 销毁链表
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}



被折叠的 条评论
为什么被折叠?



