c语言实现带头双向循环链表(附:c++不带头版本实现)

前言

本篇博客使用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。

链表的释放

为了避免内存泄漏,在使用完链表需要释放链表。释放分为两种

  1. 不释放头节点
  2. 释放头节点,最彻底的释放,也就是销毁
  • 用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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是Java实现带头双向循环链表的完整源码,供参考: ``` public class DoublyCircularLinkedList<T> { private Node<T> head; // 头节点 // 节点类 private static class Node<T> { T data; Node<T> prev; Node<T> next; Node(T data) { this.data = data; this.prev = null; this.next = null; } } // 构造函数 public DoublyCircularLinkedList() { head = new Node<>(null); head.prev = head; head.next = head; } // 在链表末尾添加元素 public void add(T data) { Node<T> node = new Node<>(data); node.prev = head.prev; node.next = head; head.prev.next = node; head.prev = node; } // 在指定位置插入元素 public void insert(int index, T data) { Node<T> node = new Node<>(data); Node<T> p = head.next; int i = 0; while (p != head && i < index) { p = p.next; i++; } if (p == head || i > index) { throw new IndexOutOfBoundsException(); } node.prev = p.prev; node.next = p; p.prev.next = node; p.prev = node; } // 删除指定位置的元素 public void remove(int index) { Node<T> p = head.next; int i = 0; while (p != head && i < index) { p = p.next; i++; } if (p == head || i > index) { throw new IndexOutOfBoundsException(); } p.prev.next = p.next; p.next.prev = p.prev; p.prev = null; p.next = null; } // 获取指定位置的元素 public T get(int index) { Node<T> p = head.next; int i = 0; while (p != head && i < index) { p = p.next; i++; } if (p == head || i > index) { throw new IndexOutOfBoundsException(); } return p.data; } // 获取链表长度 public int size() { Node<T> p = head.next; int size = 0; while (p != head) { size++; p = p.next; } return size; } } ``` 该代码实现带头双向循环链表数据结构,支持在链表末尾添加元素、在指定位置插入元素、删除指定位置的元素、获取指定位置的元素、获取链表长度等操作。在算法实现中,通过一个Node类来表示链表中的节点,包含数据域、前驱指针和后继指针。同时,链表的头节点也是一个Node对象,通过头节点来连接链表的首尾,形成双向循环链表

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值