上一节里实现的是最简单的链表,在实际中那种链表不会单独用来存储数据,更多是作为其他数据结构的子结构,如图的邻接表等。而比较常用的就是带头双向循环链表。
通过对比我们可以看出有三个不同,多了头节点,链表中的元素之间不再是单向而是双向,头尾节点相连,构成循环链表。
虽然结构比起之前的复杂了一点,但是优势却十分明显。
如普通链表要访问最后的元素时,只能通过遍历链表来取得,而这个可以直接取头节点的前一个来找到尾巴。链表之间是双向的,所有可以实现回溯等操作。多了头节点可以直接在头节点上对链表就行修改,不需要再使用二级指针等
数据结构
typedef struct ListNode
{
DATATYPE data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
我们需要用到两个指针,一个指向当前位置的前面,一个指向后面
实现的接口
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* list);
// 双向链表打印
void ListPrint(ListNode* list);
// 双向链表尾插
void ListPushBack(ListNode* list, DATATYPE x);
// 双向链表尾删
void ListPopBack(ListNode* list);
// 双向链表头插
void ListPushFront(ListNode* list, DATATYPE x);
// 双向链表头删
void ListPopFront(ListNode* list);
// 双向链表查找
ListNode* ListFind(ListNode* list, DATATYPE x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, DATATYPE x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
创建返回链表的头结点
ListNode* ListCreate()
{
ListNode* head = (ListNode*)malloc(sizeof(ListNode));
head->data = 0;
head->next = head;
head->prev = head;
return head;
}
我们首先要创建头节点
在一开始我们就要确保双向循环,所以前后指针都指向自己
在pos的前面进行插入
我先不讲头插和尾插,而是直接讲插入,因为当我们能实现在pos位置插入时,就可以通过这个函数来实现头插和尾插
如图,如果我们要让他在pos前面插入,就必须先让cur处理好他与pos的prev的关系,因为如果我们先让他变成pos的前一个,那么原本的前一个节点的位置就会丢失,所以我们必须要注意好顺序
void ListInsert(ListNode* pos, DATATYPE x)
{
assert(pos);
ListNode* cur = (ListNode*)malloc(sizeof(ListNode));
cur->data = x;
pos->prev->next = cur;
cur->prev = pos->prev;
cur->next = pos;
pos->prev = cur;
}
头插
void ListPushFront(ListNode* list, DATATYPE x)
{
assert(list);
ListNode* head = list;
ListNode* tail = head->prev;
ListNode* cur = (ListNode*)malloc(sizeof(ListNode));
cur->data = x;
cur->next = head->next;
head->next->prev = cur;
head->next = cur;
cur->prev = head;
//ListInsert(list->next, x);
}
这里的实现思路一样,要处理好head的下一个节点与cur的关系,我们也可以用刚刚的插入函数直接实现,插入的位置就是头节点的下一个位置
尾插
void ListPushBack(ListNode* list, DATATYPE x)
{
assert(list);
ListNode* head = list;
ListNode* tail = head->prev;
ListNode* cur = (ListNode*)malloc(sizeof(ListNode));
cur->data = x;
cur->prev = tail;
cur->next = head;
tail->next = cur;
head->prev = cur;
//ListInsert(list, x);
}
这里与前两个一样,就不罗嗦了,这里也可以用之前的函数,插入位置就是list也就是头节点,头节点的前一个位置就是尾节点,在这插入就是尾插
删除pos位置的节点
删除的操作和插入的思路基本一样,就不多说了
void ListErase(ListNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
}
头删
void ListPopFront(ListNode* list)
{
assert(list);
ListNode* head = list;
ListNode* next = head->next;
next->next->prev = head;
head->next = next->next;
free(next);
//ListErase(list->next);
}
尾删
void ListPopBack(ListNode* list)
{
assert(list);
ListNode* head = list;
ListNode* tail = list->prev;
head->prev = tail->prev;
tail->prev->next = head;
free(tail);
//ListErase(list->prev);
}
打印链表
遍历输出即可
void ListPrint(ListNode* list)
{
assert(list);
ListNode* cur = list->next;
while (cur != list)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
查找链表
ListNode* ListFind(ListNode* list, DATATYPE x)
{
assert(list);
ListNode* cur = list->next;
while (cur != list)
{
if (cur->data == x)
return cur;
else
cur = cur->next;
}
return NULL;
}
销毁链表
void ListDestory(ListNode* list)
{
assert(list);
ListNode* head = list;
ListNode* cur = list->next;
while (cur != head)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(head);
head = NULL;
}
完整代码
头文件
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#define _CRT_SECURE_NO_WARNINGS
#define LISTSIZE 10
typedef int DATATYPE;
typedef struct ListNode
{
DATATYPE data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* list);
// 双向链表打印
void ListPrint(ListNode* list);
// 双向链表尾插
void ListPushBack(ListNode* list, DATATYPE x);
// 双向链表尾删
void ListPopBack(ListNode* list);
// 双向链表头插
void ListPushFront(ListNode* list, DATATYPE x);
// 双向链表头删
void ListPopFront(ListNode* list);
// 双向链表查找
ListNode* ListFind(ListNode* list, DATATYPE x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, DATATYPE x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
函数实现
#include "Double-linked circular list.h"
ListNode* ListCreate()
{
ListNode* head = (ListNode*)malloc(sizeof(ListNode));
head->data = 0;
head->next = head;
head->prev = head;
return head;
}
void ListPrint(ListNode* list)
{
assert(list);
ListNode* cur = list->next;
while (cur != list)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void ListPushBack(ListNode* list, DATATYPE x)
{
assert(list);
ListNode* head = list;
ListNode* tail = head->prev;
ListNode* cur = (ListNode*)malloc(sizeof(ListNode));
cur->data = x;
cur->prev = tail;
cur->next = head;
tail->next = cur;
head->prev = cur;
//ListInsert(list, x);
}
void ListPushFront(ListNode* list, DATATYPE x)
{
assert(list);
ListNode* head = list;
ListNode* tail = head->prev;
ListNode* cur = (ListNode*)malloc(sizeof(ListNode));
cur->data = x;
cur->next = head->next;
head->next->prev = cur;
head->next = cur;
cur->prev = head;
//ListInsert(list->next, x);
}
void ListDestory(ListNode* list)
{
assert(list);
ListNode* head = list;
ListNode* cur = list->next;
while (cur != head)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(head);
head = NULL;
}
ListNode* ListFind(ListNode* list, DATATYPE x)
{
assert(list);
ListNode* cur = list->next;
while (cur != list)
{
if (cur->data == x)
return cur;
else
cur = cur->next;
}
return NULL;
}
void ListInsert(ListNode* pos, DATATYPE x)
{
assert(pos);
ListNode* cur = (ListNode*)malloc(sizeof(ListNode));
cur->data = x;
pos->prev->next = cur;
cur->prev = pos->prev;
cur->next = pos;
pos->prev = cur;
}
void ListErase(ListNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
}
void ListPopBack(ListNode* list)
{
assert(list);
ListNode* head = list;
ListNode* tail = list->prev;
head->prev = tail->prev;
tail->prev->next = head;
free(tail);
//ListErase(list->prev);
}
void ListPopFront(ListNode* list)
{
assert(list);
ListNode* head = list;
ListNode* next = head->next;
next->next->prev = head;
head->next = next->next;
free(next);
//ListErase(list->next);
}