文章目录
1. 双向、带头、循环链表介绍
带头双向循环链表:结构最复杂,但实现接口时比较简单,一般用在单独存储数据。实际使用的很多链表数据结构大多都是带头双向循环链表。
2. 双向链表结构体、常用函数接口
// 双向、带头、循环
typedef int LTDataType;
typedef struct ListNode
{ // 双向链表的一个结点
struct ListNode* _prev;
LTDataType _val;
struct ListNode* _next;
}ListNode;
typedef struct List
{
ListNode* _head; // 头指针
}List;
定义了两个结构体,第一个结构体是双向链表的结点(前驱指针、数据域、后继指针),第二个结构体定义了指向双向链表头指针。
单链表的函数接口:
ListNode* BuyListNode(LTDataType x);// 建立新结点
void ListInit(List* plt);// 初始化
void ListDestroy(List* plt);// 销毁
void ListPushBack(List* plt, LTDataType x);// 尾插
void ListPopBack(List* plt);// 尾删
void ListPushFront(List* plt, LTDataType x);// 头插
void ListPopFront(List* plt);// 头删
ListNode* ListFind(List* plt, LTDataType x); // 查找结点x,返回指向结点x的指针
void ListInsert(ListNode* pos, LTDataType x); // 在pos的前面插入结点x
void ListErase(ListNode* pos);// 删除pos位置的结点
void ListRemove(List* plt, LTDataType x);// 删除结点x
int ListSize(List* plt);//双向链表的大小
int ListEmpty(List* plt);//判断双向链表是否为空
void ListPrint(List* plt);// 打印
3. 双向链表接口的实现
3.1 建立新结点
建立新结点时,先malloc一个新结点空间,再将给定值赋给新结点的数据域,最后把新结点的前后指针置空。
ListNode* BuyListNode(LTDataType x)// 建立新结点
{
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
node->_val = x;
node->_prev = NULL;
node->_next = NULL;
return node;
}
3.2 初始化
先调用BuyListNode函数申请一个头结点,因为是循环链表,所以所有指针都指向它自己。
void ListInit(List* plt)// 初始化
{
assert(plt);
ListNode* head = BuyListNode(-1);
// plt->_head->_next = plt->_head;
// 应该指向第一个结点,但是此时没有第一个结点和最后一个结点,
// 它指向它自己,所以简化一下,如下:
head->_prev = head;
head->_next = head;
plt->_head = head;
}
3.3 销毁
定义一个指向第一个结点的指针,(第一个结点不是头结点),循环遍历,依次释放,并将指针指向下一个要释放的位置。循环结束后,头结点需要单独释放,并将头指针置空。
void ListDestroy(List* plt)// 销毁
{
assert(plt);
if (plt->_head == NULL)
{
return 0;
}
ListNode* cur = plt->_head->_next;//cur是指向第一个元素的指针
while (cur != plt->_head) // 注意这个循环结束条件
{
ListNode* next = cur->_next;
free(cur);
cur = next;
}
free(plt->_head);//释放头结点
plt->_head = NULL;//头结点指针置空
}
3.4 尾插
根据头结点的前驱指针找到最后一个结点,将新结点连接到最后一个结点之后,头结点之前,依次改变四个指针的指向。
void ListPushBack(List* plt, LTDataType x)// 尾插
{
assert(plt);
ListNode* head = plt->_head;
ListNode* tail = head->_prev;
ListNode* newnode = BuyListNode(x);
tail->_next = newnode; // 将新结点连接到最后一个结点的后面
newnode->_prev = tail;
head->_prev = newnode; // 将新结点连接到头结点前面
newnode->_next = head;
}
3.5 尾删
定义两个指针,找到倒数第二个结点和最后一个结点,直接把倒数第二个结点和头结点连接,释放掉最后一个结点即可。
void ListPopBack(List* plt)// 尾删
{
assert(plt);
if (plt->_head == NULL || plt->_head->_next == plt->_head)
{
return 0;
} // 保证至少有一个结点
ListNode* cur = plt->_head->_prev; // cur一定不为空
ListNode* prev = cur->_prev; // 找到倒数第二个结点
prev->_next = plt->_head;
plt->_head->_prev = prev;
free(cur);
cur = NULL;
}
3.6 头插
定义一个指向第一个结点的指针,将新结点和头结点和第一个结点连接起来,改变四个指针的指向。
void ListPushFront(List* plt, LTDataType x)// 头插
{
assert(plt);
ListNode* newnode = BuyListNode(x); // 申请一个新结点
if (plt->_head->_next == plt->_head)
{ // 链表为空,头插相当于尾插
ListPushBack(&plt, x);
return 0;
}
ListNode* first = plt->_head->_next; // 指向第一个结点
plt->_head->_next = newnode; // 头结点和新结点连接
newnode->_prev = plt->_head;
first->_prev = newnode; // 第一个结点和新结点连接
newnode->_next = first;
}
3.7 头删
定义两个指针,分别指向第一个结点和第二个结点,再把头结点和第二个结点连接起来,释放掉第一个结点。
void ListPopFront(List* plt)// 头删
{
assert(plt);
ListNode* head = plt->_head;
if (plt->_head->_next == plt->_head)
{ // 只有头结点
return 0;
}
ListNode* first = head->_next; // 指向第一个结点
ListNode* second = first->_next; // 指向第二个结点
free(first); // 释放掉第一个结点
head->_next = second; // 连接
second->_prev = head;
}
3.8 查找结点x
定义一个指针,从头结点开始依次查找,找到返回指向该值的指针。注意循环结束的条件。
ListNode* ListFind(List* plt, LTDataType x) // 查找结点x,返回指向结点x的指针
{
assert(plt);
ListNode* cur = plt->_head->_next;
while (cur != plt->_head) // 结束条件
{
if (cur->_val == x)
{
return cur;
}
cur = cur->_next;
}
return NULL;
}
3.9 给定位置前插入结点
先申请一个新结点,定义一个指针找到pos前一个结点,连接pos前一个结点和新结点,最后连接pos和新结点。
void ListInsert(ListNode* pos, LTDataType x) // 在pos的前面插入结点x
{
assert(pos);
ListNode* newnode = BuyListNode(x); // 申请新结点
ListNode* prev = pos->_prev; // pos前一个结点
prev->_next = newnode; // 连接pos前一个结点和新结点
newnode->_prev = prev;
pos->_prev = newnode; // 连接pos和新结点
newnode->_next = pos;
}
3.10 删除给定位置结点
定义两个指针,分别指向pos的前后结点,将前后两个结点连接起来,释放掉pos结点。
void ListErase(ListNode* pos) // 删除pos位置的结点
{
assert(pos);
ListNode* prev = pos->_prev; // pos前驱
ListNode* next = pos->_next; // pos后继
prev->_next = next; // 前驱连接后继
next->_prev = prev;
free(pos);
}
3.11 删除给定值结点
一般定义一个指针,遍历找到结点,调用ListErase函数直接删除;也可以用ListFind函数找到该结点,再调用ListErase函数直接删除。
void ListRemove(List* plt, LTDataType x)// 删除结点x
{
// 一般方法:
//assert(plt);
//ListNode* cur = plt->_head->_next;
//while (cur != plt->_head)
//{
// if (cur->_val == x)
// {
// ListErase(cur);
// return 0;
// }
//}
// 更方便:
assert(plt);
ListNode* tmp = ListFind(plt, 1);
if (tmp)
{
ListErase(tmp);
}
}
3.12 返回双向链表大小
定义一个计数器,从第一个结点开始计数,注意循环结束条件(不算头结点)。
int ListSize(List* plt)//双向链表的大小
{
assert(plt);
ListNode* cur = plt->_head->_next; // 从第一个结点开始计算
int count = 0;
while (cur != plt->_head)
{
++count;
cur = cur->_next;
}
return count;
}
3.13 判断双向链表是否为空
一般判断头结点的指针是否指向它自己本身,如果是,链表就为空。
或者更直接就用ListSize函数判断链表大小即可。
int ListEmpty(List* plt)//判断双向链表是否为空,空为0,非空为-1
{
assert(plt);
// 一般的判断条件:
//if (plt->_head->_next == plt->_head)
//{
// return 0;
//}
//else return -1;
// 更直接:
return ListSize(plt) == 0 ? 0 : -1;
}
3.14 打印双向链表
打印的格式没有什么特殊要求,简洁明了就行。
void ListPrint(List* plt)// 打印
{
assert(plt);
if (plt->_head == NULL)
{
printf("NULL\n");
return 0;
}
ListNode* cur = plt->_head->_next;
printf("<==>head<==>");
while (cur != plt->_head)
{
printf("%d<==>", cur->_val);
cur = cur->_next;
}
printf("\n");
}