链表
一、 链表
1、 概念
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。在逻辑结构上,也就是我们通过画图的方式画出链表,从大体上来看像一条链子。
2、 链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
- 单向与双向(不带头)
- 单向与双向(带头)
头指针不存储数据
- 循环与非循环
3、常用的链表结构
虽然有那么多链表的结构,但是我们在实际中最常用还是下面这两种结构:
- 无头单向非循环链表:结构简单,一般不会单独用来存放数据。在实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
- 带头双向循环链表:结构最复杂,一般用来单独存储数据。在实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。
二、用带头双向循环链表实现增删查改的函数
因为无头单向非循环链表实现增删查改的函数与用带头双向循环链表实现的原理差不多,所以在这里只是讲解用带头双向循环链表实现增删查改的函数,而用无头单向非循环链表实现的代码在文章的末尾有展现,带头双向循环链表实现的代码在文章的末尾也有展现
1、逻辑图
2、头文件中函数的声明
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
3、 结构体实现原理
- 因为链表中的每个节点都需要有存放数据、下一个节点地址和上一个节点地址的位置,所以,用一个结构体封装起来,data存放数据、结构体指针next存放下一个节点的地址,结构体指针prev存放上一个节点的地址。
- 因为需要存放的数据有可能不是int类型,所以,用typedef将类型名改名为LTDataType,方便后续按需要修改。
三、创建返回链表的头节点函数的实现
1、代码
ListNode* ListCreate()
{
ListNode* head = (ListNode*)malloc(sizeof(ListNode));
head->next = head;
head->prev = head;
return head;
}
2、 实现原理
- 因为一开始,链表中没有数据,也没有其他节点,并且头节点不存储数据,所以,我们可以用malloc在堆上开辟一个内存空间用来存放头节点。因为还没有其他节点,就把next指针和prev指针都指向这个头节点。
四、开辟节点空间的函数实现
1、代码
ListNode* BuyListNode(LTDataType x)
{
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
2、 实现原理
- 因为链表中的节点在存储空间中不一定连续,所以,当我们需要一个节点空间时,就在内存中开辟一个空间。
- 而要插入数据时就需要开辟节点空间,所以,把它封装成一个函数,当需要开辟空间时调用它就会方便许多。
- 因为刚开辟的空间我们不知道它的上一个与下一个节点的地址,所以把它的两个指针都置为空。
- 而存储的数据是知道的,所以该函数的形参就只有一个变量,它代表需要存放在该节点数据区域的值。用开辟的节点的data接收这个值。最后返回这个节点。
五、尾插函数的实现
1、代码
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyListNode(x);
ListNode* tail = pHead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = pHead;
pHead->prev = newnode;
}
2、 实现原理
- 因为是尾插,首先它得有一个头节点才能对这个链表进行插入操作,所以在函数语句块的头部,就先判断传入函数的头节点是否为空,如果为空assert就直接报错。
- 因为要插入节点,则调用BuyListNode函数开辟一块节点空间,用一个指针接收开辟的空间地址。
- 因为链表是带头双向循环链表,所以头节点phead的前一个节点就是尾节点,用一个名为tail的指针接收这个地址。
- 接下来就是进行尾插的操作,将newnode插在tail和phead之间。
六、打印函数的实现
1、代码
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("\n");
}
2、 实现原理
- 为了方便调试,在讲解完一种插入方式后就先讲解打印的函数实现。
- 首先还是一样,头节点不能为空,用assert函数进行判断,下面不再说明。
- 因为头节点不存储数据,所以用一个指针cur接收有数据的节点并在while循环中打印这个节点的数据,记得将cur指向它的下一个节点。
- 因为链表是循环的,所以链表中不会有空指针的出现,而当cur指向的节点为pHead时,说明已经将链表遍历完了。
- 最后为了美观,就打印一个换行。
七、尾删函数的实现
1、代码
void ListPopBack(ListNode* pHead)
{
assert(pHead);
ListNode* tail = pHead->prev;
ListNode* tailPrev = tail->prev;
tailPrev->next = pHead;
pHead->prev = tailPrev;
free(tail);
//下方代码可读性较差
/*ListNode* tail = pHead->prev;
pHead->prev = tail->prev;
tail->prev->next = pHead;
free(tail);*/
ListErase(pHead->prev);
}
2、 实现原理
- 用一个指针tail接收头指针pHead的前一节点,也就是未插入节点时的尾节点,再用一个指针tailprev接收尾节点的前一节点。
- 然后将tail的前一节点和头节点链接在一起,最后将欲删除的尾节点释放掉,避免内存泄漏。
八、头插函数的实现
1、代码
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyListNode(x);
ListNode* first = pHead->next;
pHead->next = newnode;
newnode->prev = pHead;
newnode->next = first;
first->prev = newnode;
//下方代码可读性较差
/*ListNode* newNode = BuyListNode(x);
newNode->next = pHead->next;
pHead->next->prev = newNode;
pHead->next = newNode;
newNode->prev = pHead;*/
}
2、 实现原理
- 因为要插入节点,所以调用BuyListNode函数开辟一块节点空间,用一个指针接收开辟的空间地址。
- 用指针first保存插入前链表中第一个存储数据的地址,然后进行将newnode插入到pHead头指针和first之间的操作。
九、头删函数的实现
1、代码
void ListPopFront(ListNode* pHead)
{
assert(pHead && pHead->next != pHead);
ListNode* del = pHead->next;
ListNode* delnext = del->next;
pHead->next = delnext;
delnext->prev = pHead;
free(del);
del = NULL;
}
2、 实现原理
- 首先,要进行头删,头指针pHead不能为空,由于头指针不存储数据,所以pHead->next不能是pHead,即要有一个存储数据的节点。
- 然后用指针del和delnext分别接收欲删除的节点与其下一节点。接着将pHead与delnext链接在一起,最后将del指向的空间释放掉。
十、查找节点函数的实现
1、代码
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
2、 实现原理
- 首先用指针cur接收链表中第一个存储数据的节点,接着用while循环遍历链表,如果遍历到的节点的data值与欲查找的节点的data值(形参x)相等,就返回该节点的地址(cur)。
- 最后,如果遍历完链表都没找到data值与x相符的节点就返回空(NULL)。
十一、在指定节点前插入节点的函数实现
1、代码
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
// prev newnode pos
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
2、 实现原理
- 首先指定节点不能为空,用assert判断。
- 用指针prev接收指定节点的前一节点,接着开辟空间,然后将newnode节点插入在prev与pos节点之间。
十二、删除指定节点函数的实现
1、代码
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
2、 实现原理
- 首先指定节点不能为空,用assert判断。
- 接着用prev指针保存欲删除节点的前一个节点的地址,用指针next保存欲删除节点的后一个节点的地址,然后将prev与next链接在一起,最后释放掉欲删除的节点的空间。
十三、链表销毁函数的实现
1、代码
void ListDestroy(ListNode* pHead)
{
ListNode* cur = pHead->next;
while (cur != pHead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(pHead);
}
2、 实现原理
- 老样子,用指针cur接收第一个存储数据的节点的地址。
- 因为链表的每一个节点的空间在内存中不一定都是连续的,所以销毁链表就需要遍历链表,对每一个节点的空间进行释放。
- 最后释放掉头指针pHead。
十四、无头单向非循环链表实现增删查改函数的代码
1、slist.h
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
//单链表没有prev指针,但有next指针,故而找到pos位置的下一节点的地址较为简单
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SList* plist);
2、slist.c
#include "SList.h"
SListNode* BuySListNode(SLTDateType x)
{
SListNode* node = (SListNode*)malloc(sizeof(SListNode));
node->data = x;
node->next = NULL;
return node;
}
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
while (cur)
//while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void SListPushBack(SListNode** pplist, SLTDateType x)
{
SListNode* newnode = BuySListNode(x);
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
SListNode* tail = *pplist; //赋值不能是(*pplist)->next,因为(*pplist)->next可能为空
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SListPopBack(SListNode** pplist)
{
SListNode* prev = NULL;
SListNode* tail = *pplist;
// 1.空、只有一个节点
// 2.两个及以上的节点
if (tail == NULL || tail->next == NULL)
{
free(tail);
*pplist = NULL;
}
else
{
while (tail->next)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
// 1.空
// 2.非空
SListNode* newnode = BuySListNode(x);
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
newnode->next = *pplist; //pplist前需加'*'
*pplist = newnode;
}
}
void SListPopFront(SListNode** pplist)
{
// 1.空
// 2.一个
// 3.两个及以上
SListNode* first = *pplist;
if (first == NULL)
{
return;
}
else if (first->next == NULL)
{
free(first);
*pplist = NULL;
}
else
{
SListNode* next = first->next;
free(first);
*pplist = next;
}
}
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
SListNode* cur = plist;
while (cur)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode* next = pos->next;
// pos newnode next
SListNode* newnode = BuySListNode(x);
pos->next = newnode;
newnode->next = next;
}
void SListEraseAfter(SListNode* pos)
{
assert(pos);
// pos next nextnext
SListNode* next = pos->next;
if (next != NULL)
{
SListNode* nextnext = next->next;
free(next);
pos->next = nextnext;
}
}
void SListDestroy(SListNode* plist)
{
assert(plist);
SListNode* next = plist->next;
while (next != NULL)
{
free(plist);
plist = next;
next = next->next;
}
free(plist);
}
3、调试代码
#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"
//尾插测试
void test1()
{
SListNode* sl = NULL;
SListPushBack(&sl, 1);
SListPrint(sl);
SListPushBack(&sl, 2);
SListPrint(sl);
SListPushBack(&sl, 3);
SListPrint(sl);
SListPushBack(&sl, 4);
SListPrint(sl);
SListPushBack(&sl, 5);
SListPrint(sl);
}
//头插测试
void test2()
{
SListNode* sl = NULL;
SListPushFront(&sl, 1);
SListPrint(sl);
SListPushFront(&sl, 2);
SListPrint(sl);
SListPushFront(&sl, 3);
SListPrint(sl);
SListPushFront(&sl, 4);
SListPrint(sl);
SListPushFront(&sl, 5);
SListPrint(sl);
}
//尾删测试
void test3()
{
SListNode* sl = NULL;
SListPushFront(&sl, 1);
SListPushFront(&sl, 2);
SListPushFront(&sl, 3);
SListPushFront(&sl, 4);
SListPushFront(&sl, 5);
SListPrint(sl);
SListPopBack(&sl);
SListPrint(sl);
SListPopBack(&sl);
SListPrint(sl);
SListPopBack(&sl);
SListPrint(sl);
SListPopBack(&sl);
SListPrint(sl);
SListPopBack(&sl);
//SListPrint(sl);
}
//头删测试
void test4()
{
SListNode* sl = NULL;
SListPushFront(&sl, 1);
SListPushFront(&sl, 2);
SListPushFront(&sl, 3);
SListPushFront(&sl, 4);
SListPushFront(&sl, 5);
SListPrint(sl);
SListPopFront(&sl);
SListPrint(sl);
SListPopFront(&sl);
SListPrint(sl);
SListPopFront(&sl);
SListPrint(sl);
SListPopFront(&sl);
SListPrint(sl);
SListPopFront(&sl);
//SListPrint(sl);
}
void test5()
{
SListNode* sl = NULL;
SListPushFront(&sl, 1);
SListPushFront(&sl, 2);
SListPushFront(&sl, 3);
SListPushFront(&sl, 4);
SListPushFront(&sl, 5);
SListPrint(sl);
//查找测试
SListNode* find = SListFind(sl, 4);
if (find == NULL)
printf("找不到\n");
else
printf("%d\n", find->data);
//指定位置插入测试
SListInsertAfter(find, 40);
SListPrint(sl);
find = SListFind(sl, 1);
SListInsertAfter(find, 10);
SListPrint(sl);
find = SListFind(sl, 5);
SListInsertAfter(find, 50);
SListPrint(sl);
printf("\n");
//指定位置删除测试
find = SListFind(sl, 4);
SListEraseAfter(find, 40);
SListPrint(sl);
find = SListFind(sl, 1);
SListEraseAfter(find, 10);
SListPrint(sl);
find = SListFind(sl, 5);
SListEraseAfter(find, 50);
SListPrint(sl);
//销毁测试
find = SListFind(sl, 3);
SListDestroy(sl);
//SListPrint(sl);
SListPrint(find);
}
int main()
{
test5();
return 0;
}
十五、带头双向循环链表实现增删查改函数的代码
1、List.h
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
2、List.c
#include "List.h"
ListNode* BuyListNode(LTDataType x)
{
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
ListNode* ListCreate()
{
ListNode* head = (ListNode*)malloc(sizeof(ListNode));
head->next = head;
head->prev = head;
return head;
}
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("\n");
}
void ListDestroy(ListNode* pHead)
{
ListNode* cur = pHead->next;
while (cur != pHead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(pHead);
}
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
//ListNode* newnode = BuyListNode(x);
phead tail newnode
//ListNode* tail = pHead->prev;
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = pHead;
//pHead->prev = newnode;
ListInsert(pHead, x);
}
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
//ListNode* newnode = BuyListNode(x);
//ListNode* first = pHead->next;
//pHead->next = newnode;
//newnode->prev = pHead;
//newnode->next = first;
//first->prev = newnode;
/*ListNode* newNode = BuyListNode(x);
newNode->next = pHead->next;
pHead->next->prev = newNode;
pHead->next = newNode;
newNode->prev = pHead;*/
ListInsert(pHead->next, x);
}
void ListPopBack(ListNode* pHead)
{
assert(pHead);
//ListNode* tail = pHead->prev;
//ListNode* tailPrev = tail->prev;
pHead tailPrev tail
//tailPrev->next = pHead;
//pHead->prev = tailPrev;
//free(tail);
/*ListNode* tail = pHead->prev;
pHead->prev = tail->prev;
tail->prev->next = pHead;
free(tail);*/
ListErase(pHead->prev);
}
void ListPopFront(ListNode* pHead)
{
assert(pHead && pHead->next != pHead);
ListNode* del = pHead->next;
ListNode* delnext = del->next;
pHead->next = delnext;
delnext->prev = pHead;
free(del);
del = NULL;
}
void ListPopFront(ListNode* pHead)
{
//...
ListErase(pHead->next);
}
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
// prev newnode pos
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
3、调试代码
#define _CRT_SECURE_NO_WARNINGS
#include"List.h"
//尾插、尾删测试
void test1()
{
ListNode* head = ListCreate();
ListPushBack(head, 1);
ListPrint(head);
ListPushBack(head, 2);
ListPrint(head);
ListPushBack(head, 3);
ListPrint(head);
ListPushBack(head, 4);
ListPrint(head);
ListPushBack(head, 5);
ListPrint(head);
printf("*************************************\n");
ListPopBack(head);
ListPrint(head);
ListPopBack(head);
ListPrint(head);
ListPopBack(head);
ListPrint(head);
ListPopBack(head);
ListPrint(head);
ListPopBack(head);
//ListPrint(head);
//ListPopBack(head);
//ListPrint(head);
}
//头插、头删测试
void test2()
{
ListNode* head = ListCreate();
ListPushFront(head, 1);
ListPrint(head);
ListPushFront(head, 2);
ListPrint(head);
ListPushFront(head, 3);
ListPrint(head);
ListPushFront(head, 4);
ListPrint(head);
ListPushFront(head, 5);
ListPrint(head);
printf("*************************************\n");
ListPopFront(head);
ListPrint(head);
ListPopFront(head);
ListPrint(head);
ListPopFront(head);
ListPrint(head);
ListPopFront(head);
ListPrint(head);
ListPopFront(head);
//ListPrint(head);
//ListPopFront(head);
//ListPrint(head);
}
void test3()
{
ListNode* head = ListCreate();
ListPushBack(head, 1);
ListPushBack(head, 2);
ListPushBack(head, 3);
ListPushBack(head, 4);
ListPushBack(head, 5);
ListPrint(head);
//查找测试
ListNode* ret = ListFind(head, 4);
printf("%d\n", ret->_data);
//插入测试
ListInsert(ret, 30);
ListPrint(head);
ListInsert(head, 50);
ListPrint(head);
ret = ListFind(head, 1);
ListInsert(ret, 10);
ListPrint(head);
printf("***************************\n");
//删除测试
ret = ListFind(head, 30);
ListErase(ret);
ListPrint(head);
ret = ListFind(head, 50);
ListErase(ret);
ListPrint(head);
ret = ListFind(head, 10);
ListErase(ret);
ListPrint(head);
//销毁测试
ListDestory(head);
//ListPrint(head);
ret = ListFind(head, 3);
printf("%d\n", ret->_data);
}
int main()
{
test3();
return 0;
}
如有错误或者不清楚的地方欢迎私信或者评论指出