链表的分类多种多样,但是常用的通常为按照以下标准分类:
1、单项或双向;2、带头或不带头;3、循环或非循环
其中最常用的是两种结构:无头单项非循环链表,和带头双向循环链表。
今天主要记录第二种链表的学习心得。
带头双向循环链表看起来结构复杂,但其实在数据的储存、删除、查找、修改方面在程序上更容易实现。它的优势体现在,即使链表中没有存储任何数据,也存在一个头结点,因此,此链表在很多情况下无需考虑是否需要判空;同时,因为链表中的每个存储数据都带有指向上一个结点和下一个节点的指针,因此在增加、删除数据时可以更容易操作,无需向顺序表那样还需修改表中其余存储数据的位置。
带头双向循环链表的每个存储数据都需用结构体来构建,如下:
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
在建立好每个数据单元后,要建立一个链表,首先就要对链表进行初始化,在带头双向循环链表里,初始化意味着要建立一个指向自己的头结点:
ListNode* ListInit(ListNode* pHead)
{
pHead = BuyListNode(-1);
pHead -> next = pHead;
pHead->prev = pHead;
return pHead;
}
因为无论如何链表中都有一个头结点,所以链表始终不为空,但是在此时是没有存储有效数据的,因此需要一个函数来判断链表中是否存在有效数据,即对链表进行判空:
bool ListEmpty(ListNode* pHead)
{
assert(pHead);
return pHead->next == pHead;
}
//bool只能返回真或假,所以在这个函数中,当返回值为真时,链表判空
链表建立之后,要想完全删除需要一个专门的函数来进行,如此可以增加代码的可读性:
void ListDestory(ListNode* pHead)
{
assert(pHead);
assert(!ListEmpty(pHead));
while (pHead->next != pHead)
{
ListNode* tmp = pHead->next;
pHead->next = tmp->next;
tmp->next->prev = pHead;
free(tmp);
}
}
链表里存储的数据需要可以被看到,所以需要一个打印函数:
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* tmp = pHead->next;
while (tmp != pHead)
{
printf("%d ", tmp->data);
tmp = tmp->next;
}
printf("\n ");
}
双向链表的头插头删和尾插尾删的运行逻辑一致,在此只用头插头删来做记录:
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyListNode(x);
ListNode* cur = pHead->next;
newnode->prev = pHead;
newnode->next = cur;
pHead->next = newnode;
cur->prev = newnode;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(!ListEmpty(pHead));
ListNode* cur = pHead->next;
pHead->next = cur->next;
cur->next->prev = pHead;
free(cur);
}
在链表中有时需查到某个数据的位置,需专门的查找函数:
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
assert(!ListEmpty(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* cur = pos->prev;
ListNode* newnode = BuyListNode(x);
cur->next = newnode;
newnode->prev = cur;
newnode->next = pos;
pos->prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* cur = pos->prev;
cur->next = pos->next;
pos->next->prev = cur;
free(pos);
}
总体来说,带头循环双向链表是一个结构相对复杂,但是在编程方面确是很方便使用的一个数据结构。