[Data structure] C language to implement double linked list

Table of contents

Preface

Double linked list node definition

Interface function implementation

initialization function

Create node

Print doubly linked list

tail insert node

Tail delete node

 header node

 Head delete node

 Insert before specified position

 Delete the node at the specified location

overwrite insert delete 

Determine whether the linked list is empty

Calculate the length of the linked list

Destroy linked list

Complete code of double linked list

A brief discussion on linked lists and sequence lists


Preface

We have implemented a headless one-way non-cyclic linked list before, now we will implement a headed two-way circular linked list.

Double linked list node definition

//双链表节点定义
typedef int LDataType;
typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LDataType data;
}LNode;

Unlike a singly linked list, when implementing a doubly linked list, we need to create an initialization function. The initialization of a doubly linked list requires a head node, and the prev pointer and next pointer of this head node need to point to itself.

Interface function implementation

initialization function

The initialization function creates a head node (sentinel guard). This sentinel guard does not store values ​​and makes its prev and next point to itself. This forms a closed loop.

//初始化双链表
LNode* LInit()
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
    //prev与next均指向自身
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

 Similar to the implementation of a singly linked list, we also implement two functions to detect and assist the implementation of other functions.

Create node

//创建节点、
LNode* BuyListNode(LDataType x)
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
    //创建后的节点prev与next均指向NULL
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

We just need to make the prev and next pointers of the newly created node point to NULL. Because we will also deal with its link relationship later, it just needs to point to null pointers. 

Print doubly linked list

//打印双链表
void LPrint(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

PS:

        The reason why printing linked list information starts from phead->next is because our phead does not store a value, and the node information of the linked list starts from phead->next.

tail insert node

//尾插节点
void LPushBack(LNode* phead,LDataType x)
{
    //防止传入空指针
	assert(phead);
    //先将x存入创建的节点中
	LNode* newnode = BuyListNode(x);
    //链表最后一个节点就是phead的prev指针
	LNode* tail = phead->prev;
    //将newnode节点链接到链表中
	tail->next = newnode;
	phead->prev = newnode;
	newnode->next = phead;
	newnode->prev = tail;
}

        Some people may ask, why is the parameter passed when inserting the end of a double linked list a first-level pointer? When inserting a node at the end of a singly linked list, the secondary pointer is passed. That's because the double linked list we are implementing now has a head node (sentinel guard), and precisely because of it, we don't need to pass the secondary pointer. Why? As we said before, the reason why a singly linked list is passed in is a secondary pointer. That's because under special circumstances our first node becomes a null pointer. It's different with the sentinel guard. If we only have one node in the doubly linked list now, and we delete it, we only need to point phead's prev and next to itself. If there is no sentinel guard, we have to delete phead. Set it to a null pointer, so that the parameters passed in are changed, so you need to pass a secondary pointer.

        In fact, just one point, if the linked list has a sentinel guard, just pass in the first-level pointer directly.

Tail delete node

//尾删节点
void LPopBack(LNode* phead)
{
	assert(phead);
    //防止传入的链表是一个空链表
	assert(phead->prev != phead);
	LNode* tail = phead->prev;
	LNode* tailPrev = tail->prev;
    //每个节点都是动态开辟出来的,记得释放
	free(tail);
	phead->prev = tailPrev;
	tailPrev->next = phead;
}

 header node

//头插节点
void LPushFront(LNode* phead, LDataType x)
{
	assert(phead);
	LNode* newnode = BuyListNode(x);
	LNode* first = phead->next;
	phead->next = newnode;
	first->prev = newnode;
	newnode->prev = phead;
	newnode->next = first;
}

 Head delete node

//头删节点
void LPopFront(LNode* phead)
{
	assert(phead);
    //防止链表为空
	assert(phead->next != phead);
	LNode* first = phead->next;
	LNode* newfirst = first->next;

	free(first);
	phead->next = newfirst;
	newfirst->prev = phead;
}

 Insert before specified position

//指定位置前插入
void LInsert(LNode* pos, LDataType x)
{
	assert(pos);
	LNode* newnode = BuyListNode(x);
	LNode* posPrev = pos->prev;
   
	newnode->next = pos;
	pos->prev = newnode;
	posPrev->next = newnode;
	newnode->prev = posPrev;
}

 Delete the node at the specified location

//删除指定位置节点
void LErase(LNode* pos)
{
	assert(pos);
	LNode* posNext = pos->next;
	LNode* posPrev = pos->prev;
	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}

overwrite insert delete 

With the function of deleting the node at the specified position and the function of inserting before the specified position, we can rewrite the functions of tail insertion and tail deletion and head plug deletion.

//尾插节点
void LPushFront(LNode* phead, LDataType x)
{
	LInsert(phead, x);
}
//尾删节点
void LPopBack(LNode* phead)
{
	LErase(phead->prev);
}
//头插节点
void LPushFront(LNode* phead, LDataType x)
{
	LInsert(phead->next, x);
}
//头删节点
void LPopFront(LNode* phead)
{
	LErase(phead->next);
}

Determine whether the linked list is empty

//判断链表是否为空
bool LIsEmpty(LNode* phead)
{
	assert(phead);
	return phead == phead->next;
}

Calculate the length of the linked list

//计算链表长度
int LSize(LNode* phead)
{
	int count = 0;
	LNode* cur = phead->next;
	while (cur != phead)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

Destroy linked list

//销毁链表
void LDestroy(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
    //将每个节点都释放
	while (cur != phead)
	{
		LNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//自己手动将phead置为空指针
	free(phead);
}

Complete code of double linked list

List.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <stdbool.h>

typedef int LDataType;
typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LDataType data;
}LNode;

//初始化双链表
LNode* LInit();

//打印双链表
void LPrint(LNode* phead);

//创建一个节点
LNode* BuyListNode(LDataType x);

//尾插尾删
void LPushBack(LNode* phead, LDataType x);
void LPopBack(LNode* phead);

//头插头删
void LPushFront(LNode* phead, LDataType x);
void LPopFront(LNode* phead);

//在指定位置前插入,删除指定位置
void LErase(LNode* pos);
void LInsert(LNode* pos, LDataType x);

//销毁链表
void LDestroy(LNode* phead);

//判断链表是否为空
bool LIsEmpty(LNode* phead);

//计算链表节点个数
size_t LSize(LNode* phead);

List.c

#include "List.h"


LNode* LInit()
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}

LNode* BuyListNode(LDataType x)
{
	LNode* newnode = (LNode*)malloc(sizeof(LNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

void LPushBack(LNode* phead,LDataType x)
{
	assert(phead);
	LNode* newnode = BuyListNode(x);

	LNode* tail = phead->prev;
	tail->next = newnode;
	phead->prev = newnode;
	newnode->next = phead;
	newnode->prev = tail;

	//LInsert(phead, x);
}

void LPopBack(LNode* phead)
{
	assert(phead);
	assert(phead->prev != phead);
	LNode* tail = phead->prev;
	LNode* tailPrev = tail->prev;

	free(tail);
	phead->prev = tailPrev;
	tailPrev->next = phead;
	//LErase(phead->prev);
}

void LPrint(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void LPushFront(LNode* phead, LDataType x)
{
	assert(phead);
	LNode* newnode = BuyListNode(x);

	LNode* first = phead->next;
	phead->next = newnode;
	first->prev = newnode;
	newnode->prev = phead;
	newnode->next = first;

	//LInsert(phead->next, x);
}

void LPopFront(LNode* phead)
{
	//assert(phead);
	//assert(phead->next != phead);
	//LNode* first = phead->next;
	//LNode* newfirst = first->next;

	//free(first);
	//phead->next = newfirst;
	//newfirst->prev = phead;

	LErase(phead->next);
}

LNode* LFind(LNode* phead, LDataType x)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void LErase(LNode* pos)
{
	assert(pos);
	LNode* posNext = pos->next;
	LNode* posPrev = pos->prev;
	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}

void LInsert(LNode* pos, LDataType x)
{
	assert(pos);
	LNode* newnode = BuyListNode(x);
	LNode* posPrev = pos->prev;

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

void LDestroy(LNode* phead)
{
	assert(phead);
	LNode* cur = phead->next;
	while (cur != phead)
	{
		LNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//自己手动置为空指针
	free(phead);
}

bool LIsEmpty(LNode* phead)
{
	assert(phead);
	return phead == phead->next;
}

int LSize(LNode* phead)
{
	int count = 0;
	LNode* cur = phead->next;
	while (cur != phead)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

A brief discussion on linked lists and sequence lists

Linked lists and sequential lists are both linear lists. What are the similarities and differences between them? Such as the following form:

difference Sequence table linked list
On storage space must be physically continuous Logically continuous, not necessarily physically continuous
random access Support O(1) O(N) is not supported
Insert and delete elements anywhere Elements may need to be moved, which is inefficient; O(N) Just modify the pointer to
insert Dynamic sequence table needs to be expanded when there is not enough space. No concept of capacity
Application scenarios Efficient storage of elements + frequent access Frequent insertion and deletion at any location
cache utilization high Low

The above is all the content of the double linked list implementation, I hope it can help everyone. If there is something wrong, please correct me in the comment area (bow).

Guess you like

Origin blog.csdn.net/m0_74459723/article/details/128521866