目录
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构
单向/双向带头链表:在链表的前段带一个头结点,不用于存储数据,方便进行头部删除和插入数据时不需要更改头节点的位置;
单向/双向不带头链表:链表的第一个元素作为头节点,在往头部插入或删除节点时,需要调整头节点的位置;
单向/双向循环链表:链表的最后一个节点指向头结点
单向/双向非循环链表:链表最后一个节点指向NULL
单向链表
例如下图:
单向链表,链表的上半存储数据,下半存储指向下一个节点的地址,链表最后一个节点的下半存储空指针;
typedef struct ListNode
{
int data;
struct ListNode* next;
}ListNode;
这里就是一个单向链表,data存储数据,next存储下一个节点的地址,
插入链表数据
需要给链表存储数据时,则创建一块内存,数据存入data,next连接下一个链表节点,如果没有则设为空指针,例如下列代码:
//开辟新内存空间用于存储节点
SListNode* BuySListNode(SLTDateType x)//创建节点
{
SListNode* lsjg = (SListNode*)malloc(sizeof(SListNode));
assert(lsjg);
lsjg->data = x;
lsjg->next = NULL;//初始化时默认下一个节点指向空
return lsjg;
}
(从链表尾部插入节点)
void SListPushBack(SListNode** pplist, SLTDateType x)//尾插数据
{
SListNode* ls = BuySListNode(x);//开辟链表节点
if (*pplist == NULL)//如果pplist中没有链表节点,则将这次的节点设为头节点
{
*pplist = ls;
}
else
{
SListNode* lsbr = *pplist;
while (lsbr->next)//将链表循环至最后一个节点的位置
{
lsbr = lsbr->next;
}
lsbr->next = ls;//插入新节点
}
}
(从链表头部插入节点)
void SListPushFront(SListNode** pplist, SLTDateType x)
{
SListNode* ls = BuySListNode(x);//开辟链表节点
ls->next = *pplist;//将新节点的next指向头节点
*pplist = ls;//将新节点设为头节点
}
(单链表在pos位置之后插入x)
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
if (pos->next)
{
SListNode* ls = pos->next;//记录pos后的一个节点
//插入新节点并连接ls
pos->next = BuySListNode(x);
pos->next->next = ls;
}
else//pos为最后一个节点则调用尾部插入函数即可
{
SListPushBack(&pos,x);
}
}
删除链表数据
删除数据时,将需要删除的链表节点释放掉,再恢复原链表走向;
(从链表尾部删除节点)
void SListPopBack(SListNode** pplist)
{
assert(*pplist);
SListNode* ls = *pplist;
SListNode* ls1 = NULL;
while (ls->next)//将链表循环至最后一个节点的位置
{
ls1 = ls;//保存前一个节点的位置
ls = ls->next;
}
free(ls);//释放最后的节点
if (ls1)
ls1->next = NULL;//链表中还有数据,则将前一个当做最后节点next指向NULL,
else
*pplist = NULL;//链表中没有数据,则将链表设为空
}
(从链表头部删除节点)
void SListPopFront(SListNode** pplist)
{
assert(*pplist);
SListNode* ls = *pplist;
if (ls->next)//判断链表中是否只剩最后一个节点
{
//释放原头节点,将头节点设为下一个节点
ls = ls->next;
free(*pplist);
*pplist = ls;
}
else
{
free(*pplist);
*pplist = NULL;
}
}
(单链表删除pos位置之后的值)
void SListEraseAfter(SListNode* pos)
{
assert(pos);
if (pos->next)
{
SListNode* ls = pos->next;//记录需要删除的数据
pos->next = ls->next;//先将pos连向后一个节点
free(ls);//再释放需要删除的节点
}
}
单链表打印
void SListPrint(SListNode* plist)
{
if (plist)
{
while (plist->next)
{
printf("%d->", plist->data);
plist = plist->next;
}
printf("%d->", plist->data);
}
printf("\n");
}
单链表查找
//找链表中的x值
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist);
while (plist->next&& plist->data != x)
{
plist = plist->next;
}
if (plist->data == x)
return plist;
return NULL;
}
双向链表
例如下图:
(双向链表)
(双向循环链表)
在单向链表中我们只能读取到链表往后的节点,无法往前读取节点,双向链表则弥补了这个缺陷,既可以向前读取也可以像后读取;
data为链表存储的数据,next为下一个节点的地址,prev为上一个节点的地址;
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
这里示范为带头双向循环链表
(初始化创建链表的头结点)
ListNode* ListCreate()
{
ListNode* xtsr = (ListNode*)malloc(sizeof(ListNode));
if (xtsr == NULL)
return;
xtsr->_next = xtsr;
xtsr->_prev = xtsr;
return xtsr;
}
此时链表头节点一个元素,所以next和prev都指向自身;
双向链表插入数据
(双向链表在pos的前面进行插入)
ListNode* ccskfar(int x)//开辟内存给链表
{
ListNode* krsc = (ListNode*)malloc(sizeof(ListNode));
if (krsc == NULL)
return;
krsc->_data = x;
return krsc;
}
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* nextcr = ccskfar(x);
ListNode* prevp = pos->_prev;//记录链表前一个内存的地址
//将prevp与新节点之间互相连接
prevp->_next = nextcr;
nextcr->_prev = prevp;
//将新节点与pos之间互相连接
nextcr->_next = pos;
pos->_prev = nextcr;
}
(双向链表尾插/双向链表头插)尾插与头插直接调用上面函数即可,
void ListPushBack(ListNode* pHead, LTDataType x)/// 双向链表尾插
{
assert(pHead);
ListInsert(pHead, x);
//实现尾插
//ListNode* pisds = pHead->_prev;
//ListNode* crds = ccskfar(x);
//pisds->_next = crds;
//crds->_prev = pisds;
//crds->_next = pHead;
//pHead->_prev = crds;
}
void ListPushFront(ListNode* pHead, LTDataType x)// 双向链表头插
{
assert(pHead);
ListInsert(pHead->_next, x);
//实现头插
//ListNode* nedxign = ccskfar(x);
//ListNode* nedes = pHead->_next;
//pHead->_next = nedxign;
//nedxign->_prev = pHead;
//nedxign->_next = nedes;
//nedes->_prev = nedxign;
}
双向链表删除数据
(双向链表删除pos位置的节点)
void ListErase(ListNode* pos)
{
assert(pos);
//记录pos前后两个节点
ListNode* posprev = pos->_prev;
ListNode* posnext = pos->_next;
//释放pos后重新连接前后节点
free(pos);
posprev->_next = posnext;
posnext->_prev = posprev;
}
(双向链表尾删/双向链表头删)尾删头删调用上面函数即可
void ListPopBack(ListNode* pHead)// 双向链表尾删
{
assert(pHead && pHead->_next != pHead);
ListErase(pHead->_prev);
//ListNode* wei = pHead->_prev->_prev;
//free(wei->_next);
//wei->_next = pHead;
//pHead->_prev = wei;
}
void ListPopFront(ListNode* pHead)// 双向链表头删
{
assert(pHead);
ListErase(pHead->_next);
//ListNode* nesdx = pHead->_next->_next;
//free(pHead->_next);
//pHead->_next = nesdx;
//nesdx->_prev = pHead;
}
双向链表打印/查找
void ListPrint(ListNode* pHead)// 双向链表打印
{
assert(pHead);
ListNode* tstls = pHead->_next;
while (tstls!=pHead)//因为是循环链表,所以结束条件为==头节点地址
{
printf("%d ", tstls->_data);
tstls = tstls->_next;
}
printf("\n");
}
ListNode* ListFind(ListNode* pHead, LTDataType x)// 双向链表查找
{
assert(pHead);
ListNode* ndex = pHead->_next;
while (ndex!= pHead)
{
if (ndex->_data == x)
return ndex;
ndex = ndex->_next;
}
return NULL;
}
双向链表销毁
void ListDestory(ListNode* pHead)// 双向链表销毁
{
assert(pHead);
ListNode* Des = pHead->_next;
while (Des != pHead)//依次释放前一个节点的内存,直到只剩头结点
{
Des = Des->_next;
free(Des->_prev);
}
//重新初始化头节点
pHead->_data = pHead;
pHead->_prev = pHead;
}