学习笔记—数据结构—双链表

目录

前言:

一、双链表基本介绍

1.1操作概念

1.2操作类型

1.3实现图

二、双链表的实现

2.1定义结构体

2.2双链表的创建新节点

2.3初始化

2.4遍历打印

2.5插入

头插

尾插

指定位置之后插入

2.6删除

判断链表是否为空

头删

尾删

指定位置删除

2.7查找

2.8销毁

三、总结

头文件DList.h

DList.c

测试文件test.c

之前两篇介绍了单链表(无头单向非循环链表),接下来我将介绍它的同源兄弟:双链表

前言:

双链表又称带头双向循环链表,它是一种特殊的数据结构,它结合了双向链表和循环链表的特点。在双链表中,每个节点除了拥有指向下一个节点的指针外,还拥有指向上一个节点的指针。此外,列表的头节点和尾节点通过这些指针相互连接,形成一个闭环。这种结构允许从任何一个节点开始,既可以向前遍历,也可以向后遍历,直到回到起点,从而实现高效的双向遍历。

一、双链表基本介绍

1.1操作概念

  ●  定义节点结构:每个节点包含数据部分和两个指针部分,分别指向前一个节点和后一个节点。

  ●  创建头节点:头节点是双向循环列表的起始点,它的前驱指针指向自己,后继指针也指向自己,形成一个闭环。

  ●  插入节点:在特定位置插入新节点时,需要更新新旧节点的前驱和后继指针,确保列表的完整性。

  ●  删除节点:删除特定节点时,同样需要更新前后节点的指针,避免出现悬空指针。

  ●  遍历列表:可以通过前驱指针或后继指针从任意节点开始,向前或向后遍历整个列表。

1.2操作类型

常见操作包括初始化、插入、删除、查找遍历。这些操作通常涉及到对链表节点的指针进行操作,以实现数据的动态管理。

1.3实现图

头结点head被称为哨兵节点,是一个附加的链表节点。该节点作为第一个节点,它的值域不存储任何东西。只是为了操作的方便而引入的。如果一个链表有哨兵节点的话,那么线性表的第一个元素应该是链表的第二个节点

二、双链表的实现

2.1定义结构体

由于是双向带头,所以需要定义两个指针,prev指向上一个节点、next指向下一个节点。

typedef int ListDataType;

typedef struct DList
{
	int data;
	struct Dlist* prev;
	struct Dlist* next;

}ListNode;

2.2双链表的创建新节点

新节点newcode,两个指针都置为空,内含的值data为x

ListNode* BuyNewNode(ListDataType x)
{
	ListNode* newcode = (ListNode*)malloc(sizeof(ListNode));
	if (newcode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	newcode->data = x;
	newcode->next = NULL;
	newcode->prev = NULL;
	return newcode;

}

2.3初始化

//链表初始化创建哨兵节点
ListNode* ListInit()
{
	ListNode* head = BuyNewNode(-1);
	head->next = head;
	head->prev = head;
	return head;
}

2.4遍历打印

//链表打印
void DlistPrint(ListNode* phead)
{
	ListNode* cur = phead->next;
	printf("<-head->");
	while (cur != phead)
	{
		printf("%d<->", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

2.5插入

头插

在哨兵节点下创建新节点nowcode,进行插入。

//链表头插
void DLlistPushFront(ListNode* phead, ListDataType x)
{
	assert(phead);

	ListNode* newcode = BuyNewNode(x);
	ListNode* cur = phead->next;
	phead->next = newcode;
	newcode->prev = phead;
	newcode->next = cur;
	cur->prev = newcode;
}

尾插

在哨兵节点上创建新节点newcode,进行插入。

//链表尾插
void DLlistPushBack(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* newcode = BuyNewNode(x);
	ListNode* tail = phead->prev;
	tail->next = newcode;
	newcode->prev = tail;
	newcode->next = phead;
	phead->prev = newcode;

}

指定位置之后插入

在pos位置之后新建节点newnode,进行插入。

//链表pos插入
void DListInset( ListNode* pos,ListDataType x)
{
	assert(pos);
	
	ListNode* newcode = BuyNewNode(x);

	ListNode* cur = pos->prev;

	cur->next = newcode;
	newcode->prev = cur;
	newcode->next = pos;
	pos->prev = newcode;

}

2.6删除

判断链表是否为空

我们利用bool类型判断真假

bool ListEmpty(ListNode* phead)
{
	assert(phead);
	return phead->prev == phead;
}

头删

把哨兵节点之后的节点del删除。

void DLlistPopFront(ListNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));

	ListNode* cur = phead->next;
	ListNode* curnext = cur->next;
	phead->next = curnext;
	curnext->prev = phead;
	free(cur);
	cur = NULL;

}

尾删

在哨兵节点之前的节点del删除。

//链表尾删
void DLlistPopBack(ListNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));
	ListNode* tail = phead->prev;
	ListNode* tailprev = tail->prev;

	tailprev->next = phead;
	phead->prev = tailprev;
	free(tail);
	tail = NULL;
}

指定位置删除

删除指定节点pos。

//链表pos删除
void DListErase(ListNode* phead, ListNode* pos)
{
	assert(!ListEmpty(phead));
	assert(pos);

	ListNode* posprev = pos->prev;
	ListNode* posnext = pos->next;

	posprev->next = posnext;
	posnext->prev = posprev;
	free(pos);
    //pos = NULL;
	//由于一级指针,NULL改变不了形式参数
	
}

2.7查找

链表的查找的时候返回写成结构体指针的形式,以便于进行指定位置的插入和删除。

//链表查找
ListNode* DListFind(ListNode* phead, ListDataType pos)
{
	assert(phead);

	ListNode* cur = phead->next;
	while (cur != phead && cur->data != pos)
	{
		if (cur->data == pos)
		{
			break;
		}
		cur = cur->next;
	}
	return cur;
}

2.8销毁

//链表销毁
void DListDestory(ListNode* phead)
{
	assert(phead);

	ListNode* cur = phead->next;

	while (cur != phead)
	{
		ListNode* curnext = cur->next;
		free(cur);
		//在循环第一句自动下一步
		cur = curnext;
		
	}
	//释放哨兵位头节点
	//不需要置空,形参数
	free(phead);
}

三、总结

头文件DList.h

#pragma once


#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>


typedef int ListDataType;

typedef struct DList
{
	int data;
	struct Dlist* prev;
	struct Dlist* next;

}ListNode;

//链表初始化创建哨兵节点
ListNode* ListInit();
//双链表的创建新节点
ListNode* BuyNewNode(ListDataType x);
//打印
void DlistPrint(ListNode* phead);
//尾插
void DLlistPushBack(ListNode* phead, ListDataType x);
//尾删
void DLlistPopBack(ListNode* phead);
//头插
void DLlistPushFront(ListNode* phead, ListDataType x);
//头删
void DLlistPopFront(ListNode* phead);
//查找
ListNode* DListFind(ListNode* phead, ListDataType pos);
//pos插入
void DListInset(ListNode* pos, ListDataType);
//pos删除
void DListErase(ListNode* phead, ListNode* pos);
//销毁
void DListDestory(ListNode* phead);

DList.c

#define  _CRT_SECURE_NO_WARNINGS   1

#include "DList.h"

ListNode* BuyNewNode(ListDataType x)
{
	ListNode* newcode = (ListNode*)malloc(sizeof(ListNode));
	if (newcode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	newcode->data = x;
	newcode->next = NULL;
	newcode->prev = NULL;
	return newcode;

}

//链表初始化创建哨兵节点
ListNode* ListInit()
{
	ListNode* head = BuyNewNode(-1);
	head->next = head;
	head->prev = head;
	return head;
}

//打印
void DlistPrint(ListNode* phead)
{
	ListNode* cur = phead->next;
	printf("<-head->");
	while (cur != phead)
	{
		printf("%d<->", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

//尾插
void DLlistPushBack(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* newcode = BuyNewNode(x);
	ListNode* tail = phead->prev;
	tail->next = newcode;
	newcode->prev = tail;
	newcode->next = phead;
	phead->prev = newcode;

}

//判断链表是不是空
bool ListEmpty(ListNode* phead)
{
	assert(phead);
	return phead->prev == phead;
}


//尾删
void DLlistPopBack(ListNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));
	ListNode* tail = phead->prev;
	ListNode* tailprev = tail->prev;

	tailprev->next = phead;
	phead->prev = tailprev;
	free(tail);
	tail = NULL;
}

//头插
void DLlistPushFront(ListNode* phead, ListDataType x)
{
	assert(phead);

	ListNode* newcode = BuyNewNode(x);
	ListNode* cur = phead->next;
	phead->next = newcode;
	newcode->prev = phead;
	newcode->next = cur;
	cur->prev = newcode;
}

//头删
void DLlistPopFront(ListNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));

	ListNode* cur = phead->next;
	ListNode* curnext = cur->next;
	phead->next = curnext;
	curnext->prev = phead;
	free(cur);
	cur = NULL;

}

//查找
ListNode* DListFind(ListNode* phead, ListDataType pos)
{
	assert(phead);

	ListNode* cur = phead->next;
	while (cur != phead && cur->data != pos)
	{
		if (cur->data == pos)
		{
			break;
		}
		cur = cur->next;
	}
	return cur;
}

//pos插入
void DListInset( ListNode* pos,ListDataType x)
{
	assert(pos);
	
	ListNode* newcode = BuyNewNode(x);

	ListNode* cur = pos->prev;

	cur->next = newcode;
	newcode->prev = cur;
	newcode->next = pos;
	pos->prev = newcode;

}

//pos删除
void DListErase(ListNode* phead, ListNode* pos)
{
	assert(!ListEmpty(phead));
	assert(pos);

	ListNode* posprev = pos->prev;
	ListNode* posnext = pos->next;

	posprev->next = posnext;
	posnext->prev = posprev;
	free(pos);
	//由于一级指针,NULL改变不了形式参数
	
}

//销毁
void DListDestory(ListNode* phead)
{
	assert(phead);

	ListNode* cur = phead->next;

	while (cur != phead)
	{
		ListNode* curnext = cur->next;
		free(cur);
		//在循环第一句自动下一步
		cur = curnext;
		
	}
	//释放哨兵位头节点
	//不需要值空,形参数
	free(phead);
}

测试文件test.c

#define  _CRT_SECURE_NO_WARNINGS   1

#include "DList.h"

void testDlist1()
{
	ListNode* plist = ListInit();
	DLlistPushBack(plist, 1);
	DLlistPushBack(plist, 2);
	DLlistPushBack(plist, 3);
	DLlistPushBack(plist, 4);

	
	ListNode* ret = DListFind(plist, 2); 
	//ret->data = (ret->data) * 2;
	DListInset(ret, 40);
	DListErase(plist, ret);

	DLlistPopBack(plist);

	DlistPrint(plist);

}
void testDlist2()
{
	ListNode* plist = ListInit();
	DLlistPushFront(plist, 1);
	DLlistPushFront(plist, 2);
	DLlistPushFront(plist, 3);
	DLlistPushFront(plist, 4);
	DLlistPushFront(plist, 5);

	DLlistPopFront(plist);
	DLlistPopFront(plist);



	DlistPrint(plist);

}


int main()
{
	testDlist1();
	//testDlist2();


	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值