文章目录
目录
一、链表介绍
链表也是线性表的一种,分为数据域和指针域。数据域记录数据,指针域记录的是下一个节点的地址。由于每一个节点都有存放着下一个节点的指针,于是指针将节点一个个链接起来,整个结构就像一条链,于是称为链表。
链表分类可按照是否有头节点、单双向、能否循环。以下为单向、无头、不循环的链表结构。
二、单向无头不循环链表实现
1.节点结构
如上所说,结构体两个成员,一个记录数据一个记录下一个节点地址。
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SLTNode;
2.接口函数实现
1.插入
先设置一个取一个新节点的函数,方便插入。
SLTNode* BuyListNode(SLTDateType x)
{
SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
if (newNode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
由于没有头节点,所以当链表为空的时候插入需要另外处理一下。其他时候就利用next指针域将原来链表的节点与新节点连接起来。
void SListPushBack(SLTNode** pphead, SLTDateType x)
{
SLTNode* tail = *pphead;
SLTNode* newNode = BuyListNode(x);
if (*pphead == NULL)
{
*pphead = newNode;
}
else
{
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newNode;
}
}
void SListPushFront(SLTNode** pphead, SLTDateType x)
{
SLTNode* newNode = BuyListNode(x);
newNode->next = *pphead;
*pphead = newNode;
}
// 在pos位置之前去插入一个节点
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
SLTNode* cur = *pphead;
SLTNode* newNode = BuyListNode(x);
if (pos == *pphead)
{
*pphead = newNode;
newNode->next = pos;
}
else
{
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = newNode;
newNode->next = pos;
}
}
2.删除
删除节点要释放相应的内存空间,重新连接链表,同时也需要注意链表为空时的边界情况。
void SListPopBack(SLTNode** pphead)
{
assert(*pphead);
SLTNode* tail = *pphead;
SLTNode* prev = NULL;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
if (prev == NULL)
{
*pphead = NULL;
}
else
{
prev->next = NULL;
}
}
void SListPopFront(SLTNode** pphead)
{
assert(*pphead);
SLTNode* front = *pphead;
*pphead = front->next;
free(front);
front = NULL;
}
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(*pphead);
SLTNode* cur = *pphead;
SLTNode* prev = NULL;
if (pos == *pphead)
{
*pphead = cur->next;
free(cur);
cur = NULL;
}
else
{
while (cur->next != pos)
{
prev = cur;
cur = cur->next;
}
cur->next = cur->next->next;
free(pos);
pos = NULL;
}
3.其他
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
if (cur == NULL)
{
printf("NULL");
}
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
}
SLTNode* SListFind(SLTNode* phead, SLTDateType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void SListDestory(SLTNode** pphead)
{
SLTNode* cur = *pphead;
while (*pphead != NULL)
{
SListPopFront(pphead);
}
}
三、双向有头循环链表
1.节点结构
双向有头循环链表是链表中最复杂的结构,虽然结构复杂但是在接口的功能实现上相较于上一种结构的链表简单很多。
next域记录下一个节点地址,prev记录前一个节点地址,data域记录数据。
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
2.接口函数实现
1.链表创建和销毁
初始只有一个头节点时,节点的next和prev都指向自己本身。
ListNode* ListCreate()
{
ListNode *tailNode = (ListNode*)malloc(sizeof(ListNode));
if (tailNode == NULL)
{
exit(-1);
}
tailNode->next = tailNode;
tailNode->prev = tailNode;
return tailNode;
}
void ListDestory(ListNode* pHead)
{
while (pHead->next != pHead)
{
ListPopFront(pHead);
}
free(pHead);
pHead = NULL;
}
2.头尾插入和删除
由于存在头节点,不需要考虑从空表开始插入等边界情况。以及两个方向的指针都有也方便了插入和删除。
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* tail = pHead->prev;
ListNode* newNode = BuyNode(x);
tail->next = newNode;
newNode->prev = tail;
newNode->next = pHead;
pHead->prev = newNode;
}
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* tail = pHead->prev;
tail->prev->next = pHead;
pHead->prev = tail->prev;
free(tail);
}
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* next = pHead->next;
ListNode* newNode = BuyNode(x);
pHead->next = newNode;
newNode->prev = pHead;
next->prev = newNode;
newNode->next = next;
}
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(pHead->next != pHead);
ListNode* next = pHead->next;
ListNode* nextNext = next->next;
pHead->next = nextNext;
nextNext->prev = pHead;
free(next);
}
3.指定位置插入删除
利用ListFind函数找到位置后,传入位置进行增删操作,都很方便。
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;
}
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* posPrev = pos->prev;
ListNode* newNode = BuyNode(x);
posPrev->next = newNode;
newNode->prev = posPrev;
newNode->next = pos;
pos->prev = newNode;
}
void ListErase(ListNode* pos)
{
assert(pos);
assert(pos->next != pos);
ListNode* posPrev = pos->prev;
ListNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
总结
无头单向不循环链表:结构简单,当时由于没有头节点,而且链表上只能单向走,实现操作困难。但相较于顺序表的话,有着节约空间,插入删除不需要找尾,更加方便。
有头双向循环链表:结构复杂,也正是结构上的复杂设置,让我们操作链表更加方便。