数据结构 --- c语言实现双向链表

双向链表的组成

双向链表主要分为三部分

  • 前项指针

  • 后项指针

  • 数据域

  • 一般情况下,创建单一个体,前项指针和后项指针都赋值为空

  • 一般采用记录头节点和记录尾节点,再封装的方式写双向链表

双向链表的结构体描述(节点)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct Node
{
	int data;            //数据域
	struct Node* front;  //前项指针
	struct Node* tail;   //后项指针
}NODE,*LPNODE;

再定义一个结构体(链表) 

  • 通过记录头节点和尾节点的方式去描述链表

typedef struct DList 
{
	LPNODE frontNode;    //头节点
	LPNODE tailNode;     //尾节点
	int curSize;         //当前节点个数
}DLIST,*LPDLIST;

创建节点

把用户的数据变成一个结构体变量

LPNODE createNode(int data) 
{
    //申请内存
	LPNODE newNode = (LPNODE)malloc(sizeof(NODE));
    //断言处理
	assert(newNode);
    //前项指针、后项指针置为空
	newNode->front = NULL;
	newNode->tail = NULL;
    //给数据做初始化
	newNode->data = data;
	return newNode;
}

创建链表

  • 描述链表最初的状态

LPDLIST createDList() 
{
	LPDLIST list = (LPDLIST)malloc(sizeof(DLIST));    //用结构体变量描述最初状态 做动态内存申请
	assert(list);
	list->frontNode = NULL;                           //链表刚开始是空的 头尾节点指向空
	list->tailNode = NULL;
	list->curSize = 0;                                //当前元素个数为0
	return list;
}

头插法

①新节点的后项指针指向原来链表的头节点

②原来链表的头节点的前项指针指向新节点

③把原来的头节点移到新节点的位置

//要插入的链表 插入的数据
void push_front(LPDLIST list,int data) 
{
    //创建新节点
	LPNODE newNode = createNode(data);
    //list为空 没办法做插入
	if (list == NULL)
		return;
    //只有一个节点 插入的节点既是头节点也是尾节点
	if (list->curSize == 0) 
	{
        //头节点和尾节点都是指向这个节点
		list->frontNode = newNode;
		list->tailNode = newNode;
		list->curSize++;
	}
	else 
	{
        //新节点的后项指针指向原来链表的头节点
		newNode->tail = list->frontNode;
        //原来链表的头节点的前项指针指向新节点
		list->frontNode->front = newNode;
        //把原来的头节点移到新节点的位置 
		list->frontNode = newNode;
		list->curSize++;
	}
}

    //测试代码
    LPDLIST list = createDList();
	push_front(list, 1);              //1
	push_front(list, 2);              //2 1
	printByFront(list);               //1 2 通过前项指针做打印 从后往前
	printByTail(list);                //2 1 通过后项指针做打印 从前往后

打印链表

可以通过前项指针去做打印,也可以通过后项指针去做打印

通过前项指针做打印

从最后一个节点往前走,由于记录头节点和尾节点,所以就非常简单啦🙄

//要打印的链表
void printByFront(LPDLIST list)
{
    //定义一个移动的指针指向最后一个节点
	LPNODE pmove = list->tailNode;
    //不为空 
	while (pmove != NULL) 
	{
        //打印数据
		printf("%d\t", pmove->data);
        //用前项指针去做遍历
		pmove = pmove->front;
	}
	printf("\n");
}

通过后项指针做打印

类似单链表的 next 指针的方式做遍历

//要打印的链表
void printByTail(LPDLIST list) 
{
    //定义一个移动的指针指向第一个节点
	LPNODE pmove = list->frontNode;
    //不为空 
	while (pmove) 
	{
        //打印数据
		printf("%d\t", pmove->data);
        //用后项指针去做遍历
		pmove = pmove->tail;  
	}
	printf("\n");
}

尾插法

由于记录了尾节点的位置,所以就非常简单啦🙄

①原来链表的尾节点的后项指针指向新节点

②新节点的前项指针指向原来链表的尾节点

③把原来的尾节点移到新节点的位置

void push_back(LPDLIST list, int data) 
{
	LPNODE newNode = createNode(data);
    //list为空 没办法做插入
	if (list == NULL)
		return;
    //只有一个节点 插入的节点既是头节点也是尾节点
	if (list->curSize == 0) 
	{
        //头节点和尾节点都是指向这个节点
		list->frontNode = newNode;
		list->tailNode = newNode;
		list->curSize++;
	}
	else 
	{
        //原来链表的尾节点的后项指针指向新节点
		list->tailNode->tail = newNode;
        //新节点的前项指针指向原来链表的尾节点
		newNode->front = list->tailNode;
        //把原来的尾节点移到新节点的位置 
		list->tailNode = newNode;
		list->curSize++;
	}
}
    //测试代码
    push_back(list, 999);        
	printByTail(list);                //2 1 999
	printByFront(list);               //999 1 2

在指定位置前做插入

①首先找到相邻的两个节点

②创建新节点做插入四步操作

单链表只需要两步操作,双向链表需要四步,因为有二个指针

//要插入的链表 用数据充当指定位置 要插入的数据
void push_appoin(LPDLIST list, int posData, int data) 
{
    //有可能会修改表头 如果list为空 没办法做插入
	if (list == NULL||list->curSize==0)
		return;
    //第一个节点里面的数据等于指定位置的数据
	if (list->frontNode->data == posData)
	{
        //头插法
		push_front(list, data);
	}
	else 
	{
        //其他情况 定义两个移动的指针做查找 单向还是双向是无关紧要的 只需要遍历一遍就可以了 往哪边遍历都可以
		LPNODE preNode = list->frontNode;
		LPNODE curNode = list->frontNode;
        //当前节点不等于空 并且当前节点里面的数据不等于指定数据
		while (curNode != NULL && curNode->data != posData) 
		{
            //并排往下走 前面那个节点保存一下这个位置
			preNode = curNode;
            //当前节点走到原来位置的下一个
			curNode = preNode->tail;
		}
        //分析结果做插入 当前节点等于空表示没有找到指定位置 无法做插入
		if (curNode == NULL) 
		{
			printf("未找到指定位置,无法插入!\n");
		}
		else 
		{
            //找到了 创建一个新节点做插入
			LPNODE newNode = createNode(data);
            //tail指向
			preNode->tail = newNode;
			newNode->tail = curNode;
            //front指向
			curNode->front = newNode;
			newNode->front = preNode;
			list->curSize++;
		}
	}
}
    //测试代码
    push_appoin(list, 999, 888);      //在999前面插入888
	printByTail(list);                //2 1 888 999
	printByFront(list);               //999 888 1 2

头删法

保存表头的下一个然后删除表头

需要处理表头下一个节点的前项指针,如果前项指针没有做处理,通过前项做遍历会陷入死循环,要让前项指针等于空作为结束条件

void pop_front(LPDLIST list) 
{
    //只有0个节点 没办法做删除
	if (list == NULL || list->curSize == 0) 
	{
		printf("无法删除!链表为空\n");
		return;
	}
    //保存表头的下一个节点
	LPNODE nextNode = list->frontNode->tail;  //nextNode=NULL
    //删除表头
	free(list->frontNode);
    //把头节点移到 nextNode 的位置,只有1个节点删除的节点是表头,把表头置为空 NULL
	list->frontNode = nextNode;				 
    //如果只有1个节点,删除的节点是表头,nextNode=NULL,没办法把nextNode->front置为空,所以要判断nextNode!=NULL 
	if (nextNode != NULL)
	{
        //把nextNode的前项指针置为空
		nextNode->front = NULL;
	}
	else 
	{
        //如果nextNode=NULL,删除的节点也是表尾,尾节点内存被释放了,需要把表尾置为空
		list->tailNode = NULL;
	}
	list->curSize--;
}
    //测试代码
    pop_front(list); 

尾删法

由于记录了尾部的位置,因此双向链表的尾删法不需要找尾部的上一个,单向链表用记录尾部的方式,依然需要去找尾节点的上一个

删掉最后一个节点一定会影响它上一个节点,尾节点的上一个节点通过尾节点的前项指针就可以找到,只需要处理尾节点上一个节点的后项指针就可以了,把它置为空,然后把尾节点释放,最后把表尾移到上一个位置

void pop_back(LPDLIST list) 
{
    //只有0个节点 没办法做删除
	if (list == NULL || list->curSize == 0) 
	{
		printf("链表为空无法删除!\n");
		return;
	}
    //保存表尾的上一个节点
	LPNODE preNode = list->tailNode->front;	
    //删除表尾
	free(list->tailNode);
    //把尾节点移到 preNode 的位置
	list->tailNode = preNode;
    //如果只有一个节点preNode=NULL
	if (preNode != NULL) 
	{
		preNode->tail = NULL;              
	}
	else 
	{
        //如果preNode=NULL,删除的节点也是表头,需要把表头置为空
		list->frontNode = NULL;
	}
	list->curSize--;
}
    //测试代码
    pop_back(list);

指定位置删除

①preNode 的后项指针指向 curNode->tail

②curNode->tail 的前项指针指向 preNode

//要删除的链表 指定数据
void pop_appoin(LPDLIST list, int posData) 
{
    //只有0个节点 没办法做删除
	if (list == NULL || list->curSize == 0) 
	{
		printf("链表为空无法删除!");
		return;
	}
    //头节点的数据等于指定数据
	if (list->frontNode->data == posData) 
	{
        //头删法
		pop_front(list);
		return;
	}
    //其他情况 定义两个移动的指针做查找
	LPNODE preNode = list->frontNode;
	LPNODE curNode = list->frontNode;
    //当前节点不等于空 并且当前节点里面的数据不等于指定数据
	while (curNode != NULL&&curNode->data!=posData)
	{
        //并排往下走 前面那个节点保存一下这个位置
		preNode = curNode;
        //当前节点走到原来位置的下一个
		curNode = preNode->tail;
	}
    //分析结果做插入 当前节点等于空表示没有找到指定位置 无法做删除
	if (curNode == NULL)
	{
		printf("未找到指定位置无法删除!\n");
	}
	else 
	{
        //尾节点的数据等于指定数据
		if (list->tailNode == curNode) 
		{
            //尾删法
			pop_back(list);
		}
		else 
		{
            //释放之前做连接
			preNode->tail = curNode->tail;
			//判断curNode->tail是不是不空 当删除表尾的时候,curNode->tail等于空
			curNode->tail->front = preNode;
			free(curNode);
			curNode = NULL;
			list->curSize--;
		}
	}
}
    //测试代码
	pop_appoin(list, 1314);
	printByTail(list);
	printByFront(list);
  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

qiuqiuyaq

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

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

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

打赏作者

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

抵扣说明:

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

余额充值