(数据结构)线性表(下):链表分类及双向链表的实现
链表分类及双向链表的实现
- 链表的分类
- 双向链表
- 结点结构
- 链表结构
- 初始化
- 遍历/打印
- 尾插
- 头插
- 尾删
- 头删
- 查找
- 在pos位置之后插入
- 删除pos位置的结点
- 销毁双向链表
- 顺序表与链表的分析(线性表)
链表的分类
一共有222=8种链表
next:指向下一个结点(后继结点)
prev:指向前一个结点(前驱结点)
头结点不存储数据,作为“哨兵位”。
(单链表的第一个结点不能叫头结点!因为其存储了数据!)
不循环链表的尾结点next为空,循环链表的尾结点next不为空
- 虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:单链表(单向不带头不循环链表)和双向链表(双向带头循环链表)
- 单链表:结构简单,⼀般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
- 双向链表:结构最复杂,⼀般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。
双向链表
结点结构
struct ListNode
{int data;struct ListNode* next;struct ListNode* prev;
}
链表结构
双向带头循环链表:
特点:
- 头结点的prev指向尾结点,尾结点的next指向头结点。表达为:
ptail=phead->prev
phead=ptail->next
- 循环链表的特性:如果遍历条件是
pcur!=NULL
,链表将无线循环遍历下去。 - phead(即双向链表头结点的地址)永远不会发生改变。(但是头结点中的成员是可以改变的!)
初始化
双向链表为空的情况下只有一个哨兵位,且next和prev指针都指向自己。
(如果连哨兵位都没有的话,这就是单链表而不是双向链表)
//形成一个新的结点,频繁使用,独立出来
LTNode* buyNode(LTDataType x) {LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL) {perror("malloc fail!");exit(1);}node->data = x;node->next = node->prev = node;return node;
}
/双向链表的初始化法1
void LTInit01(LTNode** pphead) {//只有传地址才能真正改变phead!*pphead = buyNode(-1);//随便给一个data就行了,因为头结点的数据是不会用到了
}
//双向链表的初始化法2
LTNode* LTInit02() {LTNode* phead = buyNode(-1);return phead;
}
遍历/打印
双向链表是循环链表,所以循环条件不能再是pcur!=NULL
//打印双向链表
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead) {printf("%d -> ", pcur->data);pcur = pcur->next;}
}
尾插
添加元素时,需要先分情况为链表为空和链表非空,因为有可能操作是不同的。(但是尾插的情况下操作相同就可以合并)
- 修改顺序?——修改指针指向时,先修改对原链表无影响的指针
- newnode的prev、next可以先定
- 再修改尾结点的next和头结点的prev
//尾插
//传入的phead结点不会发生改变,参数就只传一级指针足够
//如果传入的phead指针需要发生改变(LTInit01),参数就需要传二级
void LTPushBack(LTNode* phead, LTDataType x) {//如果传入的phead为空,则传入的根本不是双向链表,直接断言报错assert(phead);LTNode* newnode = buyNode(x);newnode->next = phead;LTNode* ptail = phead->prev;newnode->prev = ptail;ptail->next = newnode;phead->prev = newnode;
}
头插
头插是指插入一个结点作为链表的第一个有效节点
当然不能插在phead前面!因为我们知道phead->prev其实是尾结点。
- 与尾插相同,修改指针指向时,先修改对原链表无影响的指针
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{LTNode* newnode = buyNode(x);newnode->prev = phead;newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;
}
尾删
进行删除前一定要先探空——防止导致头结点被删除导致链表销毁等异常问题
//尾删
void LTPopBack(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->prev;//把尾结点(即要删除的结点)单独写出来避免代码难读del->prev->next = phead;phead->prev = del->prev;free(del);
}
头删
void LTPushFront(LTNode* phead, LTDataType x)
{LTNode* newnode = buyNode(x);newnode->prev = phead;newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;
}
查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next; }return NULL;
}
在pos位置之后插入
//在pos位置之后插入
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = buyNode(x);newnode->prev = pos;newnode->next = pos->next;pos->next->prev = newnode;pos->next = newnode;
}
删除pos位置的结点
//删除pos位置的结点
void LTErase(LTNode* pos, LTDataType x)
{assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}
销毁双向链表
//销毁01 因为需要释放头结点,所以需要传二级指针
void LTDesTroy(LTNode** pphead)
{LTNode* pcur = (*pphead)->next;while (pcur != *pphead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(*pphead);*pphead = NULL;
}
//销毁02 为保持接口一致性,建议统一参数指针都为一级
//但是需要手动把phead实参置空,否则本函数里只能改变形参
void LTDesTroy(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);
}