数据结构---双向链表

2024-10-09 16:07:50 浏览数 (3)

前言

1.概念

单向链表:一块内存指向下一个内存。 单链表存在一些缺陷: 1.查找速度慢。 2.不能从后往前找。 3.找不到前驱。 链表的结构分为8种: 1.单向和双向 2.带头和不带头 带头的链表有一个带哨兵位的头结点,这个节点不存储有效数据。 好处:尾插更方便,不需要二级指针了,带头结点不需要改变传过来的指针,,也就意味着不需要传二级指针 3.循环和不循环 不循环:尾是指向空的 循环:尾是指针头的 一、无头单向肺循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的领接表等等。 另外这种结构在笔试面试中出现很多。 二、带头双向循环链表:结构复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表,另外这个结构虽然结构复杂, 但是使用代码实现以后会发现带来很多优势,实现反而简单了。

2.初始化

代码语言:javascript复制
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}ListNode;

next表示指向下一个节点 prev表示前驱指针,指向上一个节点

一、双向链表的应用

1.打印链表和创建新的节点

代码语言:javascript复制
//创建新的节点
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

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

2.链表的初始化和销毁

代码语言:javascript复制
//初始化
ListNode* ListInit()
{
	ListNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
//销毁
void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

3.链表的首插和尾插

代码语言:javascript复制
//尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* tail = phead->prev;
	ListNode* newnode = BuyListNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}
//上面的尾插针对空链表也是成立的,可以画个图理解一下
//双向带头循环链表结构虽然复杂了,但是操作反而简单了。
//结构设计的优势
//内存占用增加,每个节点多了一个前驱指针
//STL中的List原码就是这个结构

//头插
void ListPushfront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* newnode = BuyListNode(x);
	ListNode* first = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}

4.链表的尾删和头删

代码语言:javascript复制
//尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* tail = phead->prev;
	ListNode* last = tail->prev;
	free(tail);
	last->next = phead;
	phead->prev = last;
	tail = NULL;
}

//头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* first = phead->next;
	ListNode* second = first->next;
	free(first);
	phead->next = second;
	second->prev = phead;
	first = NULL;
}

5.链表的查找和指定位置的插入删除

代码语言:javascript复制
//查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur!=phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

//插入
void ListInsert(ListNode* phead, ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* last = pos->prev;
	ListNode* newnode = BuyListNode(x);
	last->next = newnode;
	newnode->prev = last;
	newnode->next = pos;
	pos->prev = newnode;
}

//删除pos位置的数
void ListErase(ListNode* plist, ListNode* pos)
{
	assert(pos);
	ListNode* last = pos->prev;
	ListNode* following = pos->next;
	free(pos);
	last->next = following;
	following->prev = last;
	pos = NULL;
}

二、封装文件

注:以上函数封装在List.c中

1. List.h文件

代码语言:javascript复制
#pragma once
//双向链表定义
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}ListNode;


ListNode* ListInit();
void ListPrint(ListNode* plist);
void ListDestory(ListNode* plist);
void ListPushBack(ListNode* plist, LTDataType x);
void ListPushfront(ListNode* plist, LTDataType x);
void ListPopBack(ListNode* plist);
void ListPopFront(ListNode* plist);
ListNode* ListFind(ListNode* plist, LTDataType x);
//在pos位置之前插入一个值
void ListInsert(ListNode* plist, ListNode* pos, LTDataType x);
//删除pos位置的值
void ListErase(ListNode* plist, ListNode* pos);

2.测试函数

代码语言:javascript复制
#include "List.h"

void TestList1()
{
	ListNode* plist = ListInit();

	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);

	ListPrint(plist);
	ListDestory(plist);
}
void TestList2()
{
	ListNode* plist = ListInit();

	ListPushfront(plist, 1);
	ListPushfront(plist, 2);
	ListPushfront(plist, 3);
	ListPushfront(plist, 4);

	ListPrint(plist);
	ListDestory(plist);
}
void TestList3()
{
	ListNode* plist = ListInit();

	ListPushfront(plist, 1);
	ListPushfront(plist, 2);
	ListPushfront(plist, 3);
	ListPushfront(plist, 4);

	ListPrint(plist);
	printf("n");
	ListPopBack(plist);
	ListPrint(plist);
	ListDestory(plist);
}

void TestList4()
{
	ListNode* plist = ListInit();

	ListPushfront(plist, 1);
	ListPushfront(plist, 2);
	ListPushfront(plist, 3);
	ListPushfront(plist, 4);

	ListPrint(plist);
	printf("n");
	ListPopFront(plist);
	ListPrint(plist);
	ListDestory(plist);
}

void TestList5()
{
	ListNode* plist = ListInit();

	ListPushfront(plist, 1);
	ListPushfront(plist, 2);
	ListPushfront(plist, 3);
	ListPushfront(plist, 4);
	ListPrint(plist);
	ListNode* pos = ListFind(plist, 3);
	if (pos)
	{
		//查找同时可以附带修改
		printf("找到了");
		pos->data = pos->data * 10;
	}
	else
	{
		printf("没有找到");
	}
}
void TestList6()
{
	ListNode* plist = ListInit();

	ListPushfront(plist, 1);
	ListPushfront(plist, 2);
	ListPushfront(plist, 3);
	ListPushfront(plist, 4);
	ListPrint(plist);
	printf("n");
	ListNode* pos = ListFind(plist, 3);
	if (pos)
	{
		ListInsert(plist, pos, 100);
		ListErase(plist, pos->prev);
	}
	ListPrint(plist);
}
int main()
{
	TestList6();
	return 0;
}

用以上函数进行测试

总结

虽然双向链表结构付咱,但是理解其结构可发现各个接口比单向链表的接口写起来更加简单,更加容易上手

1 人点赞