双向链表知识点(附源代码)

双向链表的特点

带头链表⾥的头结点,实际为"哨兵位",哨兵位结点不存储任何有效元素,只是站在这⾥"放哨的"
例图解析

双向链表与单链表的区别

双向链表:除了存储的数据外还有两个指针,具有头节点,还有循环的特点 (因此就算链表只有一个节点的时候它前后节点是指向自己 的而不是为null)。双向链表相比单链表在插入、删除和某些遍历操作上更高效。

单链表:可以有头节点也无头节点,不循环的。

如何构建一个双向链表

1.链表的初始化

双向链表有一个"标兵位",这里新建的节点当作标兵位,这里有一点注意事项,主要是看进行传二级指针还是找有返回值这两种。如下是具体的代码实现。

cpp 复制代码
void LTInit(LTNode** pphead)
{
    //创建一个头结点(哨兵位)
    pphead = LTBuyNode(-1);
}
//
LTNode* LTInit()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}

2.尾插双向链表

尾插例图

尾插的时候一定要注意,不要先改变标兵位的指向,当标兵位置发生改变的时候后面节点的指向会出现指向错误。

3.头插双向链表

头插示意图

这里的头插,插入的是标兵位置后面,并不是首位置,图中可以看出首位置前面插入其实就是尾插而并不是头插。

4.头删双向链表

5.尾删双向链表

6.找到双向链表的指定位置

这里方法较为简单就不进行图像示意图,从"标兵位"后面开始寻找,找到所要寻找的元素进行返回就可以。

7.删除指定位置后面

通过找到的指定位置进行标记,然后通过标记位置对其后面进行插入,方法和尾插时的方法无异。

9.双向链表的销毁

双向表的销毁也是两种方法法一:不用二级指针进行传址,手动制空首元素,因为不进行二级指针传参这样形参的改变并不会改变实参,所以需要进行手动进行制空。

cpp 复制代码
void LTDesTroy2(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	free(phead);
	phead = pcur = NULL;
}

总结

单链表适合简单的单向遍历操作,内存消耗小;而双链表适合需要双向遍历或者频繁涉及节点前驱访问的场景,但是相应地需要更多的内存空间来存储额外的指针。最后希望各位大佬进行指正,并留下一键三连(点赞,收藏,关注)。

原码

list.h

cpp 复制代码
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef struct ListNode ListNode;
typedef  int LTDataType;
struct ListNode
{
	LTDataType data;
	ListNode* next;
	ListNode* pre;
};
// 创建返回链表的头结点.
ListNode* ListInit();
// 双向链表销毁
void ListDestory(ListNode* pHead);
//判断是否为空
bool ListEmpty(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);
//双向链表的销毁
void ListDestory(ListNode** pos);

list.c

cpp 复制代码
#include"List.h"
//开辟一个节点
ListNode* LTBuyNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode)); //开节点是开一个节点的空间
	if (newnode == NULL)
	{
		perror("fail!!!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->pre = newnode;
	return newnode;
}
//初始化
ListNode* ListInit()
{
	ListNode* pHead = LTBuyNode(-1);
	return pHead;
}
//判断是否为空
bool ListEmpty(ListNode* pHead)
{
	assert(pHead);
	return pHead->next == pHead;
}
//尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = LTBuyNode(x);

	newnode->next = pHead;
	newnode->pre = pHead->pre;
	pHead->pre->next = newnode;
	pHead->pre=newnode;
	
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);

	assert(!ListEmpty(pHead));
	ListNode* del = pHead->pre;
	pHead->pre = del->pre;
	del->pre->next = pHead;
	free(del);
	del = NULL;
}
//头插入
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newnode = LTBuyNode(x); //带头节点的头插
	newnode->next = pHead->next;
	pHead->next->pre = newnode;

	pHead->next = newnode;
	newnode->pre = pHead;


}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	ListNode* del = pHead->next;
	pHead->next = del->next;
	del->next->pre = pHead;
	free(del);
	del = NULL;
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* pcur = pHead->next;
	while (pcur!= pHead)
	{
		
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	//pos前插入
	ListNode* newNode = LTBuyNode(x);
	newNode->next = pos;
	pos->pre->next = newNode;
	newNode->pre = pos->pre;
	pos->pre = newNode;
}
// 双向链表在pos的后面进行插入
void ListbackInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	//pos前插入
	ListNode* newNode = LTBuyNode(x);
	newNode->pre = pos;
	newNode->next = pos->next;
	pos->next->pre = newNode;
	pos->next = newNode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	pos->pre->next=pos->next;
	pos->next->pre=pos->pre;
	free(pos);
	pos = NULL;

}
//双向链表的销毁
void ListDestory(ListNode** pHead)
{
	assert(pHead && *pHead);
	ListNode* pcur = (*pHead)->next;
	while (pcur != *pHead)
	{
		ListNode *next= pcur->next;
		free(pcur);
		pcur = next;
	}
	//对头节点进行释放
	free(*pHead);
	*pHead = NULL;
	pcur = NULL;
}
//打印
void ListPrint(ListNode* pHead)
{
	//此处一点要记得头节点其实是一个哨兵位
	ListNode* pcur = pHead->next;
	while (pcur != pHead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

test.c

cpp 复制代码
#include"List.h"
void ListTest()
{
	ListNode* list = ListInit();
	//尾插
	ListPushBack(list, 1);
	ListPushBack(list, 2);
	ListPushBack(list, 3);
	ListPushBack(list, 4);
	ListPrint(list);
	//双向链表尾删
	ListPopBack(list);
	ListPrint(list);

	//头插入
	ListPushFront(list, 5);
	ListPushFront(list, 6);
	ListPushFront(list, 7);
	ListPrint(list);
	
	//双向链表的头删
	ListPopFront(list);
	ListPrint(list);

	// 双向链表查找
	ListNode* pos = ListFind(list, 6);
	if (pos == NULL)
	{
		printf("未找到!!!\n");
	}
	else
	{
		printf("找到了!!!\n");
	}

	// 双向链表在pos的前面进行插入
	ListInsert(pos, 66);
	ListPrint(list);

	//双向链表在pos的后面进行插入
	ListbackInsert(pos, 88);
	ListPrint(list);

	// 双向链表删除pos位置的节点
	ListErase( pos);
	ListPrint(list);
	ListDestory(&list);
}
int main()
{
	ListTest();
	return;
}
相关推荐
CSCN新手听安1 小时前
list的常用操作
数据结构·list
梅茜Mercy3 小时前
数据结构:链表(经典算法例题)详解
数据结构·链表
青春男大3 小时前
java栈--数据结构
java·开发语言·数据结构·学习·eclipse
Zer0_on3 小时前
数据结构栈和队列
c语言·开发语言·数据结构
一只小bit3 小时前
数据结构之栈,队列,树
c语言·开发语言·数据结构·c++
我要学编程(ಥ_ಥ)4 小时前
一文详解“二叉树中的深搜“在算法中的应用
java·数据结构·算法·leetcode·深度优先
szuzhan.gy5 小时前
DS查找—二叉树平衡因子
数据结构·c++·算法
一只码代码的章鱼6 小时前
排序算法 (插入,选择,冒泡,希尔,快速,归并,堆排序)
数据结构·算法·排序算法
青い月の魔女6 小时前
数据结构初阶---二叉树
c语言·数据结构·笔记·学习·算法
我要出家当道士7 小时前
Nginx单向链表 ngx_list_t
数据结构·nginx·链表·c