用C语言实现双链表数据结构

1.总体思路

  在我们平时所遇到的链表根据三个维度可以进行如下划分:单向与双向;带头与不带头;循环与非循环。

单向与双向 

带头与不带头  

 循环与非循环

  我们经过对单链表的学习不难发现,单向不带头非循环链表虽然看似结构简单,在力扣牛客上很常见,但是我们不能发现这种链表在处理头删、尾删、头插、尾插时对条件判断的繁琐和代码的冗长。这时候我们难免想去做出一些改动,因此双向带头非循环链表产生了。

2.代码实现

①类型声明

typedef int MyType;
typedef struct ListNode
{
	MyType data;
	struct ListNode* next;
	struct ListNode* prev;
}LT;

(将int类型typedef为MyType的好处是如果以后要将整型改为浮点型只需要在typedef处修改即可,操作简单)

②函数声明

LT* InitLT(void);
void LTPushFront(LT* phead, MyType x);//头插
void LTPopFront(LT* phead);//头删
void LTPushBack(LT* phead, MyType x);//尾插
void LTPopBack(LT* phead);//尾插
void LTInsert(LT* pos, MyType x);//中间pos位置前插入
void LTDelete(LT* pos);//删除pos位置处结点
void LTPrint(LT* phead);//打印链表

  (增删查改是链表的常用操作)

③创建新结点

LT* CreateNewNode(MyType x)
{
	struct ListNode* newnode = (LT*)malloc(sizeof(LT));
	assert(newnode);
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

(无论是哪种插入都需要创建新结点,后续都会调用这一部分所以我们为了降低耦合性,把搭建新结点单独分装成一个函数) 

④在pos结点前新结点

void LTInsert(LT* pos, MyType x)
{
	assert(pos);
	LT* newnode = CreateNewNode(x);
	struct ListNode* prev = pos->prev;

	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

(之所以先写在中间位置插入的函数,是因为有了中间插入函数之后,头插和尾插将会变得很简单,这也正是双向带头循环链表的优越性)

⑤在头结点插入

 void LTPushFront(LT* phead, MyType x)
 {
	 if (phead->next == phead)
	 {
		 LT* newnode = CreateNewNode(x);
		 phead->next = newnode;
		 newnode->prev = phead;
		 newnode->next = phead;
		 phead->prev = newnode;
	 }
	 else
	 {
		 LTInsert(phead->next, x);
	 }
 }

(需要注意点在于只有头结点,即只含哨兵位的结点时,不能调用LTInsert函数)

⑥在尾结点后面插入

void LTPushBack(LT* phead, MyType x)
 {
	 if (phead->prev == phead)
	 {
		 struct ListNode* newnode = CreateNewNode(x);
		 phead->prev = newnode;
		 phead->next = newnode;
		 newnode->next = phead;
		 newnode->prev = phead;
	 }
	 else
	 {
		 LTInsert(phead, x);
	 }
 }

⑦删除pos处的结点

void LTDelete(LT* pos)
{
	assert(pos);
	struct ListNode* posPrev = pos->prev;
	struct ListNode* posNext = pos->next;

	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}

⑧删除头结点

 void LTPopFront(LT* phead)
 {
	 assert(phead);
	 struct ListNode* first = phead->next;
	 assert(first!= phead);
	 struct ListNode* second = first->next;
	 phead->next = second;
	 second->prev = phead;
	 free(first);
	 first = NULL;
    /*assert(phead);
      struct ListNode*first=phead->next;
      assert(first!=phead);
      LTDelete(first)*这是第二种写法,直接调用前面的LTDelete函数*/

 }

⑨删除尾结点

 void LTPopBack(LT* phead)
 {
	 assert(phead);
	 struct ListNode* last = phead->prev;
	 assert(last != phead);
	 struct ListNode* prev = last->prev;
	 prev->next = phead;
	 phead->prev = prev;
	 free(last);
	 last = NULL;
      /*  第二种写法 assert(phead);
           struct ListNode*last=phead->prev;
           assert(last!=phead);
           LTDelete(last);*/
 }

⑩打印链表

 void LTPrint(LT* phead)
 {
	 printf("head<=>");
	 struct ListNode* cur = phead->next;
	 while (cur != phead)
	 {
		 printf("%d<=>", cur->data);
		 cur = cur->next;
	 }
	 printf("\n");
 }

3.代码演示

void Test(void)
{
	LT* head = InitLT();
	LTPrint(head);
	LTPushFront(head, 1);
	LTPrint(head);
	LTPushFront(head, 2);
	LTPrint(head);
	LTPushBack(head, 2);
	LTPrint(head);
	LTPushBack(head, 3);
	LTPrint(head);
	LTPushBack(head, 4);
	LTPrint(head);	
	LTPushBack(head, 5);
	LTPrint(head);	
	LTPushBack(head, 6);
	LTPrint(head);	
	LTPushBack(head, 7);
	LTPrint(head);
	LTPopBack(head);
	LTPrint(head);
	LTPopBack(head);
	LTPrint(head);
	LTPopBack(head);
	LTPrint(head);
	LTPopBack(head);
	LTPrint(head);
}


int main()
{
	Test();
	return 0;
}

  调用以上test函数测试,测试结果如下:

4.反思总结

  通过双链表的代码实现,结合单链表的使用体验我们不免有这样的感受:单向不带头非循环链表就像是抽血时抽手指血,看似简单实则很痛;而双向带头循环链表就像是抽静脉血,看似高级实则并不痛苦。双向带头循环链表在各种插入删除方面都有其优势,所以日后我们可以多多使用双链表来存储数据,以达到简洁明了。

(作者第一篇文章,有所不足希望能在评论区指正,另外,跪求关注,老爷们的每个关注都很重要,求求啦)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值