前言
本篇博客使用c语言分模块的实现带头双向循环链表的每个功能(c++版本实现的不带头版本在最后)
链表初始化
比起单链表,双链表还需要一个prev指针指向前一个节点
typedef int ListData;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
ListData data;
}ListNode;
ListNode* ListInit()
{
ListNode* newNode = CreatNode(0);
//链表为空时,头节点的两个指针都指向自己
newNode->next = newNode;
newNode->prev = newNode;
return newnode;
}
并且这个双链表带头节点(哨兵位),头节点不存储数据,但是头指针是链表中必须的。先将链表初始化,创建一个头节点,使其next与prev都指向自己,让头指针指向头节点
打印链表,使cur指针指向第一个节点,如果cur指针不为头节点,则cur指向下一个节点,打印。
void ListPrint(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);//不打印空链表
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
尾插
- 创建一个新节点,头节点的prev就是尾节点,用tail指针表示尾节点
- tail的next指向newNode,newNode的prev指向tail
- 头节点的prev指向newNode,newNode的next指向头节点
而且不用区分链表是否为空的情况,如果链表为空,即只有一个头节点,tail就是phead(初始化函数将头节点的prev和next都指向了自己)。
void ListPushBack(ListNode* phead, ListData x)
{
assert(phead);
ListNode* newNode = CreatListNode(x);
ListNode* tail = phead->prev;
tail->next = newNode;
newNode->prev = tail;
phead->prev = newNode;
newNode->next = phead;
}
尾删
- 用tail表示尾节点,tailprev表示尾节点上一个节点
- tailprev的next指向phead,phead的prev指向tailprev
- 最后释放掉tail。让链表为空时不能进行删除操作,所以先断言
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->prev != phead);//空链表不删除
ListNode* tail = phead->prev;
ListNode* tailPrev = tail->prev;
tailPrev->next = phead;
phead->prev = tailPrev;
free(tail);
}
头插
- 用front表示头节点的下一个节点(链表第一个有效节点),创建新的节点
- phead的next指向newNode,newNode的prev指向phead
- front的prev指向newNode,newNode的next指向front。
void ListPushFront(ListNode* phead, ListData x)
{
assert(phead);
ListNode* front = phead->next;
ListNode* newNode = CreatListNode(x);
phead->next = newNode;
newNode->prev = phead;
front->prev = newNode;
newNode->next = front;
}
头删
- 用front表示头节点的下一个节点(链表第一个有效节点),second表示front的下一个节点
- phead的next指向second,second的prev指向phead
- 最后释放front
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* front = phead->next;
ListNode* second = front->next;
phead->next = second;
second->prev = phead;
free(front);
}
查找
- 就是遍历链表
- cur指向第一个节点,cur的data不等于要查找的数据时,cur指向下一个节点
- 当cur指向phead时循环停止,返回该节点的地址
ListNode* ListNodeFind(ListNode* phead, ListData x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
任意位置插入与删除
- 双链表的插入是插入到数据的前面
- posPrev指针表示pos前一个节点,创建新节点
- posPrev的next指向newNode,newNode的prev指向posPrev
- pos的prev指向newNode,newNode的next指向pos
void ListInsert(ListNode* pos, ListData x)
{
assert(pos);
//记录pos的前后节点
ListNode* posPrev = pos->prev;
ListNode* newNode = CreatListNode(x);
posPrev->next = newNode;
newNode->prev = posPrev;
pos->prev = newNode;
newNode->next = pos;
}
ListErase是删除地址为pos的节点
- posPrev表示pos的前一个节点,posNext表示pos的下一个节点
- posPrev的next指向posNext,posNext的prev指向posPrev
- 最后释放pos
void ListErase(ListNode* pos)
{
assert(pos);
assert(pos->next != pos);//只有一个节点不能删除
//记录pos的前后节点
ListNode* posprev = pos->prev;
ListNode* posnext = pos->next;
//修改pos前后两节点的关系
posprev->next = posnext;
posnext->prev = posprev;
//释放pos节点
free(pos);
pos = NULL;
}
头插头删尾插尾删都可以复用ListInsert和ListErase。
链表的释放
为了避免内存泄漏,在使用完链表需要释放链表。释放分为两种
- 不释放头节点
- 释放头节点,最彻底的释放,也就是销毁
- 用cur表示第一个节点,curNext表示其下一个节点
- 释放cur,将curNext赋值给cur,重复上面的步骤
void ListClean(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* curNext = cur->next;
free(cur);
cur = curNext;
}
phead->next = phead;
phead->prev = phead;
}
为了防止野指针phead的产生,在释放phead后需要将其置为空,所以这里要传址。ListDestory是彻底的释放链表,ListClean只是清除链表中的所有节点
void ListDestory(ListNode** pphead)
{
assert(*pphead);
ListClear(*pphead);
free(*pphead);
*pphead = NULL;
}
c++版本:实现不带头双链表
以下是大二复习时用c++重新手撕的版本,与c语言版本不同的是
- 任意位置的插入与删除中,pos表示节点的序号值,不再是地址
- 并且这个版本没有头节点,一些接口实现会更为复杂
- 当然,c++的封装比c语言更好,关于这一点如果你不熟悉的话,就去看看类和对象吧
关于insert和erase的逻辑需要特别严谨,我已经在代码中以注释的形式体现
template <class T>
class list
{
typedef T value_type;
typedef list_node<T> node;
public:
~list();
// 在第pos个节点后插入新节点
bool insert(int pos, const value_type& x);
bool push_front(const value_type& x) { return insert(0, x); }
bool push_back(const value_type& x) { return insert(0, _size); }
// 删除第pos个节点
bool erase(int pos);
bool pop_front() { return erase(1); }
bool pop_back() { return erase(_size); }
// 返回链表中第一个值为x的节点
size_t find(const value_type& x);
void print();
private:
node *_head = nullptr;
size_t _size = 0;
};
template <class T>
list<T>::~list()
{
node *cur = _head;
node *next = nullptr;
for (int cur_i = 1; cur_i <= _size; ++cur_i)
{
next = cur->_next;
delete cur;
cur = next;
}
}
// 在第pos个节点后插入节点
template <class T>
bool list<T>::insert(int pos, const value_type& x)
{
// 首先是pos合法性的判断
if (pos > _size || pos < 0)
return false;
node *new_node = new node(x);
// 分为两种情况:_head是否要修改,也就是是否为头插
if (pos == 0)
{
// 若头插且链表为空
if (_size == 0)
{
new_node->_next = new_node;
new_node->_prev = new_node;
}
// 若头插且链表不为空
else
{
node *tail = _head->_prev;
new_node->_next = _head;
new_node->_prev = tail;
_head->_prev = new_node;
tail->_next = new_node;
}
// 最后不要忘记修改_head的值
_head = new_node;
}
// 非头插情况,不要修改_head的值
else
{
// 用cur遍历链表,注意遍历的方向,选择最优的方向遍历
node *cur = _head;
// 从尾开始往前找
if (pos > _size / 2)
{
// 指向尾节点
cur = _head->_prev;
for (int cur_i = _size; cur_i > pos; --cur_i)
{
cur = cur->_prev;
}
}
// 从头开始往后找
else
{
for (int cur_i = 1; cur_i < pos; ++cur_i)
{
cur = cur->_next;
}
}
// 遍历完成,在cur之后插入节点
node *next = cur->_next;
cur->_next = new_node;
next->_prev = new_node;
new_node->_prev = cur;
new_node->_next = next;
}
++_size;
return true;
}
// 删除第pos个节点
template <class T>
bool list<T>::erase(int pos)
{
// pos合法性判断
if (pos > _size || pos == 0 || pos < 0)
return false;
// 同样,也是判断是否头删
// cur指向链表第一个节点,注意:cur的释放在最后
node *cur = _head;
if (pos == 1)
{
// 若头删且节点数量为1,直接修改_head
if (_size == 1)
_head = nullptr;
// 若头删且节点数量大于1
else
{
node *prev = cur->_prev;
node *next = cur->_next;
prev->_next = next;
next->_prev = prev;
// 最后不要忘记修改_head
_head = next;
}
}
// 非头删,不需要修改_head
else
{
// 从尾开始往前找
if (pos > _size / 2)
{
// 指向尾节点
cur = _head->_prev;
for (int cur_i = _size; cur_i > pos; --cur_i)
{
cur = cur->_prev;
}
}
// 从头开始往后找
else
{
for (int cur_i = 1; cur_i < pos; ++cur_i)
{
cur = cur->_next;
}
}
node *prev = cur->_prev;
node *next = cur->_next;
prev->_next = next;
next->_prev = prev;
}
delete cur;
--_size;
return true;
}
template <class T>
size_t list<T>::find(const value_type& x)
{
node *cur = _head;
int cur_i = 1;
while (cur && cur_i <= _size)
{
if (cur->_data == x)
return cur_i;
cur = cur->_next;
++cur_i;
}
return 0;
}
// for debug
template <class T>
void list<T>::print()
{
node *cur = _head;
int cur_i = 1;
while (cur && cur_i <= _size)
{
std::cout << cur->_data << ' ';
cur = cur->_next;
++cur_i;
}
std::cout << std::endl;
}