[Data Structure] This article takes you to learn to take the lead in doubly linked list

foreword

This blog mainly explains the implementation of the leading doubly linked list and some details. Compared with the singly linked list, the code implementation of the leading doubly linked list is simpler, but the physical structure is more complex. It can help us quickly operate on the head and tail of the linked list. As far as the linked list is concerned, the leading doubly linked list is undoubtedly the most practical.
insert image description here

Implementation of linked list

List.h

#pragma once
#include<stdio.h>
#include<assert.h>

typedef int LTDataType;
typedef struct ListNode
{
    
    
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

ListNode* ListCreate();//创建返回链表的头节点
void LTInit(ListNode** phead);//初始化头节点

void ListDestroy(ListNode* plist);//双向链表销毁

void ListPrint(ListNode* plist);//双向链表打印

void ListPushBack(ListNode* plist, LTDataType x);//双向链表尾插

void ListPopBack(ListNode* plist);//双向链表尾删

void ListPushFront(ListNode* plist, LTDataType x);//双向链表头插

void ListPopFront(ListNode* plist);//双向链表头删

ListNode* ListFind(ListNode* plist, LTDataType x);//双向链表查找

void ListInsert(ListNode* pos, LTDataType x);//双向链表在pos的前面进行插入

void ListErase(ListNode* pos);//双向链表删除pos位置的结点

Declare each implementation method so that each file after including the header file can call functions defined in other files including the same header file.
The implementation idea of ​​the linked list is as follows: List.h declares, List.c implements each function, and test.c calls each function.

List.c

ListCreate()

Function: create a node and return

//创建返回链表的头节点
ListNode* ListCreate(LTDataType x)
{
    
    
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (!newNode)
	{
    
    
		perror("malloc fail:");
	}
	newNode->data = x;
	newNode->next = NULL;
	newNode->prev = NULL;

	return newNode;
}

LTInit()

Function: Initialize the head node

//初始化头节点
void LTInit(ListNode** phead)
{
    
    
	*phead = ListCreate(-1);
	(*phead)->next = *phead;
	(*phead)->prev = *phead;
}

There are two ways to operate the head node,
one: use ListNode as the return type, and directly return the address of the created node.
Two: Use the secondary pointer, the secondary pointer points to the address of the pointer containing the head node, and after one dereferencing, it is the pointer itself containing the head node.
The two methods have their own strengths. Using the first method can be simpler, but the page will be unsightly when implemented. Using the second method is prone to errors in unfamiliar situations.

ListPushBack()

Function: This function performs tail insertion operation
insert image description here

//双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
    
    
	assert(plist);

	ListNode* Node = ListCreate(x);
	Node->next = plist;
	Node->prev = plist->prev;
	plist->prev->next = Node;
	plist->prev = Node;
}

First judge whether to make an assertion (judging whether the given parameter is empty), the basis for judging whether the assertion is required is whether the given parameter must not be empty, if it must not be empty, you need to assert to prevent passing in a null pointer, the function The main function is to perform tail insertion on the linked list. The parameter passed in is a pointer to the head node. If it is empty, the linked list does not exist, so the passed parameter must not be empty, and an assertion is required.

This operation must first operate on the new node, making it point to its tail node and head node, and then operate on its adjacent nodes. If the operation is directly performed on the adjacent nodes, point the head node to If you want to change the last pointer of the last node to a new node, you cannot easily do it if you want to point the next node pointed by the end node to the new node. You must first traverse the linked list and find the end node before you can operate.

ListPopBack()

Function: delete the tail node
insert image description here

//双向链表尾删
void ListPopBack(ListNode* plist)
{
    
    
	assert(plist);
	ListNode* temp = plist->prev;
	if (temp != plist)
	{
    
    
		temp->prev->next = temp->next;
		plist->prev = temp->prev;
		free(temp);
		temp = NULL;
	}
	else
		printf("该链表只有头节点,无法删除。");
}

First judge whether an assertion is required (assert: judge whether the given parameter is empty), the main task of this function is to perform tail deletion operation, if the linked list is empty, this operation cannot be performed, so the linked list must not be empty.
Connect the node before the tail node to the head node, and use a temporary node to store the address of the tail node. After deleting, release the temporary node and return the space to the memory.

ListPrint()

Function: print linked list

//双向链表打印
void ListPrint(ListNode* plist)
{
    
    
	assert(plist);
	ListNode* cur = plist->next;

	while (cur != plist)
	{
    
    
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

Start printing and traversing from the first node after the head node, until the head node is encountered, the printing stops.

ListPushFront()

Function: Insert the head of the doubly linked list
insert image description here

//双向链表头插
void ListPushFront(ListNode* plist, LTDataType x)
{
    
    
	assert(plist);
	ListNode* Node = ListCreate(x);
	Node->prev = plist;
	Node->next = plist->next;
	plist->next->prev = Node;
	plist->next = Node;
}

The first node cannot be empty, and an assertion is required.
Doubly linked list head insertion is the same as tail insertion.

ListPopFront()

Function: delete the first node after the head node of the linked list
insert image description here

//双向链表头删
void ListPopFront(ListNode* plist)
{
    
    
	assert(plist);
	ListNode* temp = plist->next;
	if (temp != plist)
	{
    
    
		plist->next = temp->next;
		temp->next->prev = plist;
		free(temp);
		temp = NULL;
	}
	else
		printf("该双向链表只有头节点,无法删除!/n");
}

To delete a node on the linked list, the linked list must not be empty, and an assertion is required.
First use a pointer to receive the first node under the head node to check whether it is the head node, if it is the head node, it means that the linked list has no data, and exit directly.
Otherwise, change the next node pointed to by the head node to the next node of the temporary node, change the previous node pointed to by the next node of the continuous node to the head node, and delete the temporary node.

ListFind()

Function: Find the first node in the linked list whose storage element is x, and return the node

//双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x)
{
    
    
	assert(plist);
	ListNode* cur = plist->next;

	while (cur != plist)
	{
    
    
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

The linked list cannot be empty, otherwise it is meaningless to find the node.
Use the temporary variable cur to receive the address of the first node under the head node, and perform loop judgment. When x is encountered, the address of the node is returned. When the loop is completed, cur points to the position of the head node, indicating that it has traversed once , did not find the node stored for the element, return NULL

ListInsert()

Function: Insert a new node with storage element x before the pos node.
insert image description here

//双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
    
    
	assert(pos);
	ListNode* Node = ListCreate(x);
	Node->next = pos;
	Node->prev = pos->prev;
	pos->prev->next = Node;
	pos->prev = Node;
}

If the pos parameter is empty, it will be meaningless. It must be asserted that
the linked list is a doubly linked list, and its previous node and next node can be found according to a given node, so we can operate on the pos pointer.
If the pos pointer is operated first, its previous node will not be found, and the newly created new node should be operated on the pos node and its previous node respectively, and then the pos node and its previous node should be disconnected The link to the node is enough.
When we have this operation, we can change the first insertion and last insertion of the linked list

//尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
    
    
	assert(plist);
	ListInsert(plist->prev,x);
}

//首插
void ListPushFront(ListNode* plist, LTDataType x)
{
    
    
	assert(plist);
	ListInsert(plist->next,x);
}

ListErase()

Function: Delete the parameter node given in the doubly linked list.
insert image description here

//双向链表删除pos位置的结点
void ListErase(ListNode* pos)
{
    
    
	assert(pos);
	ListNode* Node = pos->prev;
	Node->next = pos->next;
	pos->next->prev = Node;
	free(pos);
	pos = NULL;
}

Same as above, the pos node must not be empty.
Use two local variables to receive the previous node and the next node pointed to by the pos node respectively, which is convenient for operation, and can be deleted directly without using other variables. A variable Node is used here to receive the previous node operation. Give the next node pointed to by the Node node to the next node pointed to by pos, the previous node pointed to by the next node pointed to by pos points to Node, and finally release the pos node to complete the deletion operation.
When we complete this function, we can modify the first delete and tail delete of the linked list to be simpler

//尾删
void ListPopBack(ListNode* plist)
{
    
    
	assert(plist);
	ListErase(plist->prev);
}

//头删
void ListPopFront(ListNode* plist)
{
    
    
	assert(plist);
	ListErase(plist->next);
}

ListErase()

Function: Destroy the created linked list

//双向链表销毁
void ListDestroy(ListNode** plist)
{
    
    
	assert(*plist);

	ListNode* cur = (*plist)->next;
	while (cur != (*plist))
	{
    
    
		ListNode* temp = cur->next;
		free(cur);
		cur = temp;
	}
	free(*plist);
	*plist = NULL;
}

If the linked list is empty, the destruction operation is meaningless and must be asserted.
The destruction of the linked list needs to operate on the head node of the linked list. If the address of the head node is passed, the data in the linked list becomes a random value after releasing the address in VS, but it cannot be blanked, and the head node is printed The data can still be printed out. If you pass the address of the pointer storing the head node and operate on it, you can solve this problem. To operate the head node, you must use a secondary pointer.
Starting from the first node under the head node of the linked list, perform the release operation, and finally release and empty the head node to complete the operation of destroying the linked list.

test.c

Implement each interface. Here I simply tested it, and it is not written in the form of a menu. If you are interested, you can rewrite it yourself

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"

void text(ListNode* phead)
{
    
    
	LTInit(&phead);

	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushBack(phead, 3);
	ListPushBack(phead, 4);
	ListPushBack(phead, 5);
	ListPrint(phead);

	ListPopBack(phead);
	ListPopBack(phead);
	ListPrint(phead);

	ListPopBack(phead);
	ListPopBack(phead);
	ListPrint(phead);

	ListPushFront(phead, 2);
	ListPushFront(phead, 3);
	ListPushFront(phead, 4);
	ListPushFront(phead, 5);
	ListPrint(phead);

	//ListPopFront(phead);
	ListPopFront(phead);
	ListPopFront(phead);
	ListPopFront(phead);
	ListPrint(phead);

	ListNode* pos = ListFind(phead, 3);
	if (pos)
	{
    
    
		printf("[%d|%p]\n", pos->data, pos);
		ListInsert(pos, 8);
		ListErase(pos);
		ListPrint(phead);
	}
}

int main()
{
    
    
	ListNode phead;
	text(&phead);

	return 0;
}

insert image description here

Guess you like

Origin blog.csdn.net/m0_52094687/article/details/127795679