【C语言】实现双向链表


目录

(一)头文件

(二) 功能实现

(1)初始化

 (2)打印链表

(3) 头插与头删

(4)尾插与尾删

(5)指定位置之后插入

(6)删除指定位置的数据

(7)链表的销毁


 

正文开始:

        在实际应用中,常用的双向链表是双向带头循环链表,本文考虑到实际应用,目的也是实现双向带头循环链表。

(一)头文件

        命名"List.h"

        本文不加解释的给出头文件,按照头文件来实现双向链表的基本功能:包括打印链表,链表的初始化,头插与头删,尾插与尾删,在指定位置之后插入数据,删除指定位置的数据,链表的销毁等共九个功能。

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//链表的数据类型
typedef int LTtype;
//双向链表的节点
typedef struct ListNode
{
	LTtype data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

//双向链表带有哨兵位,插入数据之前先插入哨兵位
//初始化》传入头节点
void LTInit(LTNode** pphead);


//初始化2>返回头节点
LTNode* LtInit0();

//销毁链表
void LTDestory(LTNode** pphead);

//尾插
void LTtail_push(LTNode* phead,LTtype x);

//头插
//是在头节点之后插入,在头节点之前插入等价于尾插
void LThead_push(LTNode* phead,LTtype x);

//打印便于调试
void Print(LTNode* phead);

//头删
void LTpophead(LTNode* phead);

//尾删
void LTpoptail(LTNode* phead);

//查找
//为了找到pos位置
LTNode* LTfind(LTNode* phead, LTtype x);

//在pos之后插入数据
void LTinsert(LTNode* pos, LTtype x);

//删除pos处的数据
void LTerase(LTNode* pos);

(二) 功能实现

        带头相对于不带头有诸多优势:

带头与不带头链表:

        带头链表是指在链表的开头增加一个额外的节点,该节点不存储具体的数据,仅用于辅助操作链表。带头链表的带头节点可以简化链表的操作,提高代码的可读性和可维护性。

        意义:

        带头节点可以避免链表为空的特殊情况处理。在没有带头节点的链表中,如果链表为空,添加、删除等操作都需要额外的处理逻辑。而带头节点的链表中,带头节点一直存在,无论链表是否为空,操作逻辑都是一致的,可以减少代码的复杂性。

        局限:

        造成额外的空间浪费。

(1)初始化

         初始化有两种方法,具体实现如下:

传址初始化

        初始化传递链表的地址(二级指针【通过传址,将形参与实参建立联系】),初始化要插入头指针哨兵位(不保存有效的数据),便于简化代码逻辑。

//双向链表带有哨兵位,插入数据之前先插入哨兵位
void LTInit(LTNode** pphead)
{
	*pphead = (LTNode*)malloc(sizeof(LTNode));
	if (*pphead == NULL)
	{
		perror("malloc");
		exit(1);
	}
	(*pphead)->data = -1;
	(*pphead)->next = (*pphead)->prev = *pphead;
}

接受返回值初始化

        在函数内申请头节点,最后返回到主函数内:

//初始化2>返回头节点
LTNode* LtInit0()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc");
		exit(1);
	}
	phead->data = -1;
	phead->next = phead->prev = phead;
	return phead;
}

 (2)打印链表

        以便于调试,理解代码;

        与打印单链表一样,不同的是:while的跳出条件是 pcur != phead;


//打印链表
void Print(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

(3) 头插与头删

        插入操作需要申请新节点:


//获取新节点
LTNode* LTbuynewnode(LTtype x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = NULL;
	return newnode;
}

 

        头插与头删

         头插,是在头节点之后插入,在头节点之前插入等价于尾插;

         先对新节点进行操作,因为新节点与原节点没有联系,不会因为对指针进行操作而丢失节点,造成数据丢失和内存泄漏;

        我们可以直接得到phead,因此先让phead的下一个节点的前驱指针指向newnode,再改变phead的next指针;(如果反过来,先改变phead的next指向newnode,此时newnode后面的节点就找不到了。)


//头插,是在头节点之后插入,在头节点之前插入等价于尾插
void LThead_push(LTNode* phead, LTtype x)
{
	assert(phead);

	LTNode* newnode = LTbuynewnode(x);

    //先对新节点进行操作
	newnode->next = phead->next;
	newnode->prev = phead;


	phead->next->prev = newnode;
	phead->next = newnode;

}

头删

        断言传过来的指针不为空,链表不为空,通过assert实现;

        通过创建del指针(要被删除的节点),保存旧头节点;

        通过创建next指针,保存旧头节点的next节点;

        这样命名后进行头删,逻辑清晰,不易出错;


//头删
void LTpophead(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTNode* del = phead->next;//被删除的节点
	LTNode* next = del->next;//del的后置节点

	phead->next = next;
	next->prev = phead;

	free(del);
	del = NULL;
}

 

(4)尾插与尾删

尾插

        与头插的逻辑完全相同


//尾插
void LTtail_push(LTNode* phead, LTtype x)
{
	assert(phead);

	LTNode* newnode = LTbuynewnode(x);

	newnode->next = phead;
	newnode->prev = phead->prev;

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

尾删

        与头删的逻辑完全相同


//尾删
void LTpoptail(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTNode* del = phead->prev;//被删除的节点
	LTNode* prev = del->prev;//del的前置节点

	prev->next = phead;
	phead->prev = prev;

	free(del);
	del = NULL;
}

 

(5)指定位置之后插入

查找

         查找数据值为x的节点,找到返回此节点,找不到返回NULL;


//查找
LTNode* LTfind(LTNode* phead,LTtype x)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

 

 在指定位置之后插入数据

        根据find找到的pos位置,申请新节点,插入到pos之后


//在pos位置之后插入数据
void LTinsert(LTNode* pos,LTtype x)
{
	assert(pos);

	LTNode* newnode = LTbuynewnode(x);
	newnode->next = pos->next;
	newnode->prev = pos;

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

 

(6)删除指定位置的数据

         删除指定位置的数据,需要的指针有pos的前驱节点,pos节点。


//删除pos处的数据
void LTerase(LTNode* pos)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* next = pos->next;

	prev->next = next;
	next->prev = prev;

	free(pos);
	pos = NULL;
}

 

(7)链表的销毁

        链表的销毁共有两种方法:

传址销毁


//销毁链表
void LTDestory(LTNode** pphead)
{
	assert(pphead);
	//哨兵位不为空
	assert(*pphead);

	LTNode* pcur = (*pphead)->next;

	while (pcur != *pphead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//释放哨兵位
	free(pphead);
	//此时可以改变哨兵位的
	pphead = NULL;

}

 传值销毁

        一级指针phead传递的是值,不是phead的地址,所以在函数中改变phead并不会改变phead的实际值
        函数返回后,仍需要手动phead = NULL;


//推荐使用一级,为了保持接口的一致性
void LTDestory0(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;//无效的置空,当做是习惯
}
//一级指针phead传递的是值,不是phead的地址,所以在函数中改变phead并不会改变phead的实际值
//函数返回后,仍需要手动phead = NULL;

 

完~

未经作者同意禁止转载 

  • 51
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
双向链表(Doubly Linked List)是一种数据结构,它与单向链表相似,但是每个节点除了指向下一个节点的指针之外,还有一个指向上一个节点的指针。这使得在双向链表中可以方便地向前或向后遍历节点。以下是使用C语言实现双向链表的全部功能的代码: ```c #include <stdio.h> #include <stdlib.h> // 定义双向链表的节点结构体 typedef struct Node { int data; struct Node* prev; struct Node* next; } Node; // 初始化双向链表 Node* init() { Node* head = (Node*)malloc(sizeof(Node)); head->data = -1; head->prev = NULL; head->next = NULL; return head; } // 在双向链表末尾添加节点 void append(Node* head, int data) { Node* new_node = (Node*)malloc(sizeof(Node)); new_node->data = data; new_node->prev = head; new_node->next = NULL; Node* p = head; while (p->next != NULL) { p = p->next; } p->next = new_node; } // 在双向链表指定位置插入节点 void insert(Node* head, int pos, int data) { Node* new_node = (Node*)malloc(sizeof(Node)); new_node->data = data; Node* p = head; for (int i = 0; i < pos; i++) { p = p->next; if (p == NULL) { printf("Error: position out of range\n"); return; } } new_node->prev = p->prev; new_node->next = p; p->prev->next = new_node; p->prev = new_node; } // 删除双向链表指定位置的节点 void remove_node(Node* head, int pos) { Node* p = head; for (int i = 0; i < pos; i++) { p = p->next; if (p == NULL) { printf("Error: position out of range\n"); return; } } p->prev->next = p->next; if (p->next != NULL) { p->next->prev = p->prev; } free(p); } // 获取双向链表指定位置的节点的数据 int get(Node* head, int pos) { Node* p = head; for (int i = 0; i < pos; i++) { p = p->next; if (p == NULL) { printf("Error: position out of range\n"); return -1; } } return p->data; } // 修改双向链表指定位置的节点的数据 void set(Node* head, int pos, int data) { Node* p = head; for (int i = 0; i < pos; i++) { p = p->next; if (p == NULL) { printf("Error: position out of range\n"); return; } } p->data = data; } // 获取双向链表的长度 int length(Node* head) { Node* p = head; int len = 0; while (p->next != NULL) { len++; p = p->next; } return len; } // 打印双向链表的所有节点的数据 void print(Node* head) { Node* p = head->next; while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("\n"); } // 反向打印双向链表的所有节点的数据 void reverse_print(Node* head) { Node* p = head; while (p->next != NULL) { p = p->next; } while (p->prev != NULL) { printf("%d ", p->data); p = p->prev; } printf("\n"); } // 释放双向链表的所有节点的内存 void destroy(Node* head) { Node* p = head->next; while (p != NULL) { Node* q = p; p = p->next; free(q); } free(head); } int main() { Node* head = init(); append(head, 1); append(head, 2); append(head, 3); insert(head, 1, 4); remove_node(head, 2); set(head, 2, 5); printf("Length: %d\n", length(head)); print(head); reverse_print(head); destroy(head); return 0; } ``` 在上面的代码中,`init`函数用于初始化双向链表,返回一个头节点;`append`函数用于在双向链表末尾添加一个节点;`insert`函数用于在双向链表指定位置插入一个节点;`remove_node`函数用于删除双向链表指定位置的节点;`get`函数用于获取双向链表指定位置的节点的数据;`set`函数用于修改双向链表指定位置的节点的数据;`length`函数用于获取双向链表的长度;`print`函数用于打印双向链表的所有节点的数据;`reverse_print`函数用于反向打印双向链表的所有节点的数据;`destroy`函数用于释放双向链表的所有节点的内存。在`main`函数中,我们对双向链表进行了一些操作,例如添加节点、插入节点、删除节点、修改节点、获取长度、打印节点等等。最后,我们使用`destroy`函数释放了双向链表的内存。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水墨不写bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值