双向链表的实现


我们上篇博客进行了单链表的实现,这次就实现一下双链表:

1. 项目头文件

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

2. 具体实现功能

2.1 双向链表的初始化

ListNode* ListCreate()
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	if (phead == NULL)
	{
		perror("malloc");
		return NULL;
	}
	phead->_data = -1;
	phead->_next = phead;
	phead->_prev = phead;
	return phead;
}

创建一个哨兵位头结点,前后都指向自己,再返回链表的头结点。

2.2 双向链表尾插

ListNode* BuyNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc");
		return NULL;
	}
	newnode->_next = NULL;
	newnode->_prev = NULL;
	newnode->_data = x;

	return newnode;
}

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = BuyNode(x);
	ListNode* tail = pHead->_prev;

	newnode->_prev = tail;
	tail->_next = newnode;
	newnode->_next = pHead;
	pHead->_prev = newnode;
}

和单链表一样,添加操作时都需要创建一个新的结点,但与单链表不同的是,此时不必再判断是否为空(因为有了哨兵位头结点),并且尾插时找尾结点也不用依次遍历,直接找头结点的前驱。

2.3 双向链表头插

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = BuyNode(x);
	ListNode* first = pHead->_next;
	pHead->_next = newnode;
	newnode->_prev = pHead;
	newnode->_next = first;
	first->_prev = newnode;
}

此时,也不用判断是否为空了(因为有了哨兵位头结点)。

2.4 双向链表尾删

bool LTEmpty(ListNode* pHead)
{
	return pHead->_next == pHead;
}


// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!LTEmpty(pHead));
	ListNode* tail = pHead->_prev;
	ListNode* tailPrev = tail->_prev;
	pHead->_prev = tailPrev;
	tailPrev->_next = pHead;
	free(tail);
}

尾删时,直接断言哨兵位不能为空。

2.5 双向链表头删

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!LTEmpty(pHead));
	ListNode* first = pHead->_next;
	ListNode* second = first->_next;

	free(first);
	pHead->_next = second;
	second->_prev = pHead;

}

头删也一样,直接断言哨兵位不能为空。

2.6 双向链表查找

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		if (cur->_data == x)
		{
			return cur;
		}
		cur = cur->_next;
	}
	return NULL;
}

需要注意的是,开始是从哨兵位头结点后一个结点开始遍历的,结束的条件是当其和哨兵位头结点相同时,则循环结束。所以这整个链表的设计是非常之巧妙的,带有工学之美的。

2.7 双向链表在pos的前面进行插入

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* prev = pos->_prev;
	ListNode* newnode = BuyNode(x);

	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = pos;
	pos->_prev = newnode;

}

在进行插入时,需要与查找函数相互配合。我们把查找到的结点返回给插入函数的pos。

2.8 双向链表删除pos位置的节点

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* posPrev = pos->_prev;
	ListNode* posNext = pos->_next;

	posPrev->_next = posNext;
	posNext->_prev = posPrev;
	free(pos);

}

删除也是一样的,跟查找函数进行配合。

2.9 双向链表打印

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->_next;
	printf("哨兵位<=>");
	while (cur != pHead)
	{
		printf("%d<=>",cur->_data);
		cur = cur->_next;
	}
	printf("\n");
}

2.10 双向链表销毁

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		ListNode* next = cur->_next;
		free(cur);
		cur = next;
	}
	free(pHead);
}

打印和销毁也非常简单,就不细说了。总体而言,带头+双向+循环链表的实现比单链表简单多了。

代码参考: https://gitee.com/yujin518/test_c/tree/master/test_3_17

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值