1 双向链表的结构
带头双向循环链表
注意:这里的“带头”跟前面我们说的“头节点”是两个概念,实际前面在单链表阶段的称呼并不严谨,为了我们更好的理解就直接成为单链表的头节点。
带头链表的头节点实际为“哨兵位”,哨兵位节点不存储任何有效的元素,只是站在这里“放哨”的。
“哨兵位”存在的意义:遍历循环链表避免死循环。
当链表中只有哨兵位节点的时候,我们称该链表为空链表,即哨兵位是不能删除的。
2 双向链表的实现
List.h 定义双向链表的结构,双向链表要实现的接口/方法
List.c 具体实现双向链表里定义的接口/方法
test.c 测试双向链表
2.1 准备工作
List.h
#include<stdio.h>
#include<stdlib.h>
#inlcude<assert.h>
//定义双向链表中节点的结构
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* prev; //指向前驱节点
struct ListNode* next; //指向后继节点
}LTNode;
LTNOde* LTBuyNode(LTDataType x) //开辟新节点
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if(newnode == NULL)
{
perror("malloc fail!!");
exit(1);
}
newnode->data = x;
newnode->next = nwenode->prev = newnode;
return newnode;
}
List.c
#include"List.h"
2.2 打印双向链表
List.h
void LTPrint(LTNode* phead);
List.c
void LTPrint(LTNode* phead)
{
assert(phead); //phead不能为空
LTNode* pcur = phead->next;
while(pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
2.3 初始化双向链表
初始化有两种方式:
- 一种是传参的(不用参数接收返回值)
- 一种是不传参的(需要参数接收返回值)
两种的效果是一样的
注意:双向链表是带有哨兵位的,插入数据之前链表中必须要先初始化一个哨兵位
List.h
//传参的初始化
void LInit01(LTNode** pphead);
//不传参的初始化
LTNode* LTInit02();
List.c
//传参的初始化
void LInit01(LTNode** pphead) //需要修改哨兵位,所以我们要传二级指针
{
*pphead = LTNOde* LTBuyNode(-1)
}
//不传参的初始化
LTNode* LTInit02()
{
LTNode* phead = LTNOde* LTBuyNode(-1);
return phead;
}
test.c
#inlcude"List.h"
//传参
void ListTest01()
{
LTNode* plist = NULL;
LTInit01(&plist);
}
//不传参
void ListTest02()
{
LTNode* plist = LTInit();
}
int main()
{
//ListTest01();
ListTest02();
return 0;
}
2.4 双向链表的尾插
我们先来思考一个问题:双向链表尾插需不需要找尾操作?
答案是:不需要,因为可通过头节点的前驱节点找到,即ptail = head->prev
尾插:在哨兵位之前插入节点/最后一个有效节点之后插入数据。
List.h
void LTPushBack(LTNode* phead, LTDataType x);
List.c
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead); //哨兵位不能为空,哨兵位为空就说明还没初始化链表
LTNode* newnode = LTBuyNode(x);
newnode->next = phead;
newnode->prev = phead-prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
test.c
void ListTest()
{
LTNode* plist = LTInit();
LTPushBcak(plist,1);
LTPushBcak(plist,2);
LTPushBcak(plist,3);
LTPushBcak(plist,4);
LTPrint(plist);
}
int main()
{
ListTest();
return 0;
}
输出结果为
1->2->3->4->
2.5 双向链表的头插
头插:在第一个有效节点之前插入。
List.h
void LTPushFront(LTNode* phead, LTDataType x);
List.c
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
test.c
void ListTest()
{
LTNode* plist = LTInit();
LTPushFront(plist,1);
LTPushFront(plist,2);
LTPushFront(plist,3);
LTPushFront(plist,4);
LTPrint(plist);
}
int main()
{
ListTest();
return 0;
}
输出结果为
4->3->2->1->
2.6 双向链表的尾删
List.h
void LTPopBack(LTNode* phead);
List.c
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead); //链表为空:只有一个哨兵位节点
LTNode* del = phead->prev; //要删除的节点(尾节点)
LTNode* prev = del->prev; //要删除节点的前驱节点
prev->next = phead;
phead=>prev = prev;
free(del);
del = NULL;
}
test.c
void ListTest()
{
LTNode* plist = LTInit();
LTPushFront(plist,1);
LTPushFront(plist,2);
LTPushFront(plist,3);
LTPushFront(plist,4);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);
LTPopBack(plist);
LTPrint(plist);
}
int main()
{
ListTest();
return 0;
}
输出结果为
4->3->2->1->
4->3->2->
4->3->
4->
2.7 双向链表的头删
List.h
void LTPopFront(LTNode* phead);
List.c
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* del = phead->next; //要删除的节点(第一个节点)
LTNode* next = del->next; //要删除节点的后继节点
next->prev = phead;
phead->next = next;
free(del);
del = NULL;
}
test.c
void ListTest()
{
LTNode* plist = LTInit();
LTPushFront(plist,1);
LTPushFront(plist,2);
LTPushFront(plist,3);
LTPushFront(plist,4);
LTPrint(plist);
LTPopFront(plist);
LTPrint(plist);
LTPopFront(plist);
LTPrint(plist);
LTPopFront(plist);
LTPrint(plist);
}
int main()
{
ListTest();
return 0;
}
输出结果为
4->3->2->1->
3->2->1->
2->1->
1->
2.8 查找双向链表
List.h
LTNode* LTFind(LTNode* phead, LTDataType x);
List.c
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;
}
test.c
void ListTest()
{
LTNode* plist = LTInit();
LTPushFront(plist,1);
LTPushFront(plist,2);
LTPushFront(plist,3);
LTPushFront(plist,4);
LTPrint(plist);
LTNode* findRet01 = LTFind(plist, 4);
if(findRet01 == NULL)
{
printf("未找到!!\n");
}
else
{
printf("找到啦!!\n");
}
LTNode* findRet02 = LTFind(plist, 520);
if(findRet02 == NULL)
{
printf("未找到!!\n");
}
else
{
printf("找到啦!!\n");
}
}
int main()
{
ListTest();
return 0;
}
输出结果为
4->3->2->1->
找到啦!!
未找到!!
2.9 指定位置之后插入数据
List.h
void LTInsert(LTNode* pos, LTDataType x);
List.c
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LtNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode; //注意顺序
pos->next = newnode;
}
test.c
void ListTest()
{
LTNode* plist = LTInit();
LTPushFront(plist,1);
LTPushFront(plist,2);
LTPushFront(plist,3);
LTPushFront(plist,4);
LTPrint(plist);
LTNode* findRet = LTFind(plist,1);
LTInsert(findRet, 66);
LTPrint(plist);
}
int main()
{
ListTest();
return 0;
}
输出结果为
4->3->2->1->
4->3->2->1->66->
2.10 删除指定位置数据
List.h
void LTErase(LTNode* pos);
List.c
void LTErase(LTNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
test.c
void ListTest()
{
LTNode* plist = LTInit();
LTPushFront(plist,1);
LTPushFront(plist,2);
LTPushFront(plist,3);
LTPushFront(plist,4);
LTPrint(plist);
LTNode* findRet = LTFind(plist,3);
LTErase(findRet);
LTPrint(plist);
}
int main()
{
ListTest();
return 0;
}
输出结果为
4->3->2->1->
4->2->1->
2.11 销毁双向链表
可以有两种销毁方法
- 传二级指针,调用完之后自动对哨兵位置NULL
- 传一级指针,调用完要手动置NULL(推荐这种,因为可以保持接口一致性)
List.h
//传二级指针
void LTDesTory01(LTNode** pphead);
//传一级指针
void LTDesTory01(LTNode* phead);
List.c
//传二级指针
void LTDesTory(LTNode** pphead)
{
assert(pphead);
//哨兵位不能为空
assert(*pphead);
LTNode* pcur = (*pphead)->next;
while(pcur != *pphead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//链表中只有一个哨兵位
free(*pphead);
*pphead = NULL;
}
//传一级指针
void LTDesTory(LTNode* phead)
{
//哨兵位不能为空
assert(phead);
LTNode* pcur = phead->next;
while(pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//链表中只有一个哨兵位
free(phead);
phead = NULL;
}
3 整理双链表代码
List.h
#include<stdio.h>
#include<stdlib.h>
#inlcude<assert.h>
//定义双向链表中节点的结构
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* prev; //指向前驱节点
struct ListNode* next; //指向后继节点
}LTNode;
//注意,双向链表是带有哨兵位的,插入数据之前链表中必须要先初始化一个哨兵位
LTNOde* LTBuyNode(LTDataType x) //开辟新节点
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if(newnode == NULL)
{
perror("malloc fail!!");
exit(1);
}
newnode->data = x;
newnode->next = nwenode->prev = newnode;
return newnode;
}
//打印
void LTPrint(LTNode* phead);
//初始化
//传参的初始化
void LInit01(LTNode** pphead);
//不传参的初始化
LTNode* LTInit02();
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDataType x);
//指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);
//删除指定位置数据
void LTErase(LTNode* pos);
//销毁
//传二级指针
void LTDesTory01(LTNode** pphead);
//传一级指针
void LTDesTory01(LTNode* phead);
List.c
#include"List.h"
//打印
void LTPrint(LTNode* phead)
{
assert(phead); //phead不能为空
LTNode* pcur = phead->next;
while(pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
//初始化
//传参的初始化
void LInit01(LTNode** pphead) //需要修改哨兵位,所以我们要传二级指针
{
*pphead = LTNOde* LTBuyNode(-1)
}
//不传参的初始化
LTNode* LTInit02()
{
LTNode* phead = LTNOde* LTBuyNode(-1);
return phead;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead); //哨兵位不能为空,哨兵位为空就说明还没初始化链表
LTNode* newnode = LTBuyNode(x);
newnode->next = phead;
newnode->prev = phead-prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuuyNode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead); //链表为空:只有一个哨兵位节点
LTNode* del = phead->prev; //要删除的节点(尾节点)
LTNode* prev = del->prev; //要删除节点的前驱节点
prev->next = phead;
phead=>prev = prev;
free(del);
del = NULL;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* del = phead->next; //要删除的节点(第一个节点)
LTNode* next = del->next; //要删除节点的后继节点
next->prev = phead;
phead->next = next;
free(del);
del = NULL;
}
//查找
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;
}
//指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LtNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode; //注意顺序
pos->next = newnode;
}
//删除指定位置数据
void LTErase(LTNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
pos = NULL;
}
//销毁
//传二级指针
void LTDesTory(LTNode** pphead)
{
assert(pphead);
//哨兵位不能为空
assert(*pphead);
LTNode* pcur = (*pphead)->next;
while(pcur != *pphead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//链表中只有一个哨兵位
free(*pphead);
*pphead = NULL;
}
//传一级指针
void LTDesTory(LTNode* phead)
{
//哨兵位不能为空
assert(phead);
LTNode* pcur = phead->next;
while(pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//链表中只有一个哨兵位
free(phead);
phead = NULL;
}
4 顺序表和双向链表的优缺点