Data structure - double linked list (C language)

Table of contents

​edit

 Initialization of the double linked list:

 Printing of double linked list:

Tail insertion of double linked list:

The head plug of the double linked list: 

Tail deletion of double linked list:

 

Delete the head of the doubly linked list:

Insertion before the pos position of the double linked list:

Deletion of the pos position of the double linked list:

About the difference between sequential list and linked list:


 

  1. The previous article explained to you the headless one-way circular linked list. Its characteristics: simple structure, generally not used to store data alone. In practice, it is more used as a substructure of other data structures, such as hash buckets, adjacency lists of graphs, and so on. However, singly linked lists are still very common in written tests.
  2. Today I will explain to you the leading two-way linked list. Its characteristics: complex structure, generally used to store data separately. The linked list data structure used in practice is a two-way circular linked list with the lead. In addition, although this structure is complicated, you will find that the structure will bring many advantages after using the code to implement it.

cc840b67ee0c4b93a06677d959337c5e.png

As shown in the picture, this is the double linked list structure I will explain to you today. Let's follow my train of thought and hope to give you a new understanding of linked lists.

 


 Initialization of the double linked list:

  • Today I will bring you another way to change the structure of the linked list. If you want to know how to change the structure of the linked list with double pointers, you can refer to my previous blog about single linked list.
  • Idea: I created a node, then assigned the node to phead, and then let its previous position and next position point to itself, and finally returned to phead, which is the head node of the sentinel position we want.
ListNode* ListCreate(ListDateType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}
ListNode* ListInit()
{
	ListNode* phead = ListCreate(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

 


 Printing of double linked list:

  • Because I want the terminal to look like the following, I use the print function.
  • First of all, it is still an old routine. I will assert it first to prevent problems with the passed parameters.
  • Because the phead here is a sentinel bit, which stores invalid data, so I define a cur node, use a loop to print all the values ​​in the linked list, and mark their directions.

34fadb9e31fc4dfe935653401f14cf48.png

void ListPrint(ListNode* phead)
{
	assert(phead);

	printf("phead<->");
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<->", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

 


Tail insertion of double linked list:

  • When inserting at the end of a double-linked list, its advantages are reflected. If it is a single-linked list, if you want to insert at the end, you can only traverse to find the tail node. However, if it is a doubly-linked list, the previous node of phead is the tail node. , it does not need to find the tail, nor does it need to traverse. This is also one of the advantages of double linked list.
  • Tail insertion idea: assert first, then use tail to save the tail node, create a new node, and then change the link relationship between the tail node and the head node, so that newnode is the new tail node.

462c58bf083848549b3bd955e5a0ac03.png

 

void ListPushBack(ListNode* phead,ListDateType x)
{
	assert(phead);
	ListNode* tail = phead->prev;
	ListNode* newnode = ListCreate(x);

	tail->next = newnode;
	newnode->prev = tail;

	newnode->next = phead;
	phead->prev = newnode;


}

 


The head plug of the double linked list: 

  • Idea: If the head is inserted, it is to save the position of the head node first, then create a new node, and then change their link relationship. Because I saved their positions first, it doesn’t matter whoever links first. If you don’t save them, you have to be logical and don’t find the position where the head node can’t be found.
void ListPushFront(ListNode* phead, ListDateType x)
{
	assert(phead);
	ListNode* newnode = ListCreate(x);
	ListNode* first = phead->next;

	phead->next = newnode;
	newnode->prev = phead;

	newnode->next = first;
	first->prev = newnode;
	
}

 


Tail deletion of double linked list:

  • Idea: If the tail is deleted, it is necessary to record the previous node of the tail node, and then change the link relationship between phead and the previous node of the tail node.
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	ListNode* tail = phead->prev;
	ListNode* tailprev = phead->prev->prev;
	tailprev->next = phead;
	phead->prev = tailprev;

	free(tail);

}

 


Delete the head of the doubly linked list:

  • Idea: The idea of ​​head deletion is actually similar to that of tail deletion, except that the second node after phead is saved here. Then it is to change the link relationship.
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead != phead->next);

	ListNode* first = phead->next;
	ListNode* second = first->next;

	phead->next = second;
	second->prev = phead;

	free(first);

}

Insertion before the pos position of the double linked list:

  • Idea: If it is an insertion, is it similar to tail insertion and head insertion? Here I save the node before pos, and then create a new node, let the node before pos point to the new node, and make the new node and pos Then establish a connection relationship.
void ListInsert(ListNode* pos, ListDateType x)
{
	assert(pos);

	ListNode* posprev = pos->prev;
	ListNode* newnode = ListCreate(x);

	posprev->next = newnode;
	newnode->prev = posprev;

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

 


Deletion of the pos position of the double linked list:

  • Idea: If the pos position needs to be deleted, save the previous node of pos and the next node of pos, then free the pos position, and then change their link relationship.
  1. void ListErase(ListNode* pos)
    {
    	assert(pos);
    	ListNode* posprev = pos->prev;
    	ListNode* posnext = pos->next;
    
    	posprev->next = posnext;
    	posnext->prev = posprev;
    	
    	free(pos);
    }

Everyone has seen the deletion of the pos position and the insertion before the pos. Do you feel that it is similar to the previous tail-plug deletion and head-plug deletion? In fact, the last two functions can be reused.

  •  Tail insertion: In fact, it is enough to pass phead in the ListInsert function, so that the tail insertion is realized.
	ListInsert(phead, x);
  • Head plug: The head plug is to change the position after phead, so just pass phead->next directly.
	ListInsert(phead->next, x);
  • Head delete and tail delete: Because the delete function I wrote is to delete the pos position, so just pass the position to be deleted.
	ListErase(phead->prev);
	ListErase(phead->next);

 

 


About the difference between sequential list and linked list:

  •  In terms of storage space: the sequence table is physically continuous, while the linked list is logically continuous, but not necessarily physically continuous.
  • On random access: the sequential list supports random access, but the linked list does not support it. To access the nodes in the linked list, it needs to be traversed.
  • Insertion and deletion at any position: The linked list has a great advantage here. The linked list can be inserted and deleted at any position only by changing the direction. But for sequential tables, it may need to move elements, which is too inefficient.
  • Insertion: Because the sequence table is continuous, there may be malloc expansion above the insertion, but malloc is consumed. If it is expanded twice at a time, but it is not used much, it will cause a waste of space. If The space for one expansion is +1, and the situation may be faced with multiple expansions, and the consumption of malloc is not low, so this is not advisable. The linked list does not have the concept of a container, which has advantages in this regard.
  • Cache utilization: The cache hit rate of the sequential table is high, while the linked list is low.

#define  _CRT_SECURE_NO_WARNINGS 1
#include "DoubleList.h"

ListNode* ListCreate(ListDateType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

ListNode* ListInit()
{
	ListNode* phead = ListCreate(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

void ListPrint(ListNode* phead)
{
	assert(phead);

	printf("phead<->");
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<->", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

void ListPushBack(ListNode* phead,ListDateType x)
{
	assert(phead);
	//ListNode* tail = phead->prev;
	//ListNode* newnode = ListCreate(x);

	//tail->next = newnode;
	//newnode->prev = tail;

	//newnode->next = phead;
	//phead->prev = newnode;

	ListInsert(phead, x);

}

void ListPushFront(ListNode* phead, ListDateType x)
{
	assert(phead);
	//ListNode* newnode = ListCreate(x);
	//ListNode* first = phead->next;

	//phead->next = newnode;
	//newnode->prev = phead;

	//newnode->next = first;
	//first->prev = newnode;
	
	ListInsert(phead->next, x);
}

void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	//ListNode* tail = phead->prev;
	//ListNode* tailprev = phead->prev->prev;
	//tailprev->next = phead;
	//phead->prev = tailprev;

	//free(tail);
	
	ListErase(phead->prev);
}

void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead != phead->next);

	//ListNode* first = phead->next;
	//ListNode* second = first->next;

	//phead->next = second;
	//second->prev = phead;

	//free(first);

	ListErase(phead->next);
}

int ListSize(ListNode* phead)
{
	assert(phead);

	int size = 0;
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

void ListInsert(ListNode* pos, ListDateType x)
{
	assert(pos);

	ListNode* posprev = pos->prev;
	ListNode* newnode = ListCreate(x);

	posprev->next = newnode;
	newnode->prev = posprev;

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

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* posprev = pos->prev;
	ListNode* posnext = pos->next;

	posprev->next = posnext;
	posnext->prev = posprev;
	
	free(pos);
}

 


 

What is cache utilization:

Regarding "Cache Line", the cache is to load data to a location far away from itself. For the CPU, the CPU stores it piece by piece. And this is called "Chche Line".

The programs we write actually form different instructions and let the CPU execute them. However, the CPU executes fast and the memory cannot keep up, so the CPU generally puts the data in the cache. For small bytes For example, it is read directly from the register, and the large byte will use the third-level cache.

In short, the data is first read, then calculated, and finally put into the main memory, and if there is no command, continue.

As for the sequence table, its physical structure is continuous. It may not hit at the beginning, but once the cache hits, it may be hit continuously, so this is also the reason for the high cache utilization of the sequence table, and the linked list is also because His physical structure leads to low cache utilization.

 

4707fe47c7da4fdfa2c91bac6296089f.png

The following is the source code of the double linked list:

#define  _CRT_SECURE_NO_WARNINGS 1
#include "DoubleList.h"

ListNode* ListCreate(ListDateType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

ListNode* ListInit()
{
	ListNode* phead = ListCreate(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

void ListPrint(ListNode* phead)
{
	assert(phead);

	printf("phead<->");
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<->", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

void ListPushBack(ListNode* phead,ListDateType x)
{
	assert(phead);
	//ListNode* tail = phead->prev;
	//ListNode* newnode = ListCreate(x);

	//tail->next = newnode;
	//newnode->prev = tail;

	//newnode->next = phead;
	//phead->prev = newnode;

	ListInsert(phead, x);

}

void ListPushFront(ListNode* phead, ListDateType x)
{
	assert(phead);
	//ListNode* newnode = ListCreate(x);
	//ListNode* first = phead->next;

	//phead->next = newnode;
	//newnode->prev = phead;

	//newnode->next = first;
	//first->prev = newnode;
	
	ListInsert(phead->next, x);
}

void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	//ListNode* tail = phead->prev;
	//ListNode* tailprev = phead->prev->prev;
	//tailprev->next = phead;
	//phead->prev = tailprev;

	//free(tail);
	
	ListErase(phead->prev);
}

void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead != phead->next);

	//ListNode* first = phead->next;
	//ListNode* second = first->next;

	//phead->next = second;
	//second->prev = phead;

	//free(first);

	ListErase(phead->next);
}

int ListSize(ListNode* phead)
{
	assert(phead);

	int size = 0;
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

void ListInsert(ListNode* pos, ListDateType x)
{
	assert(pos);

	ListNode* posprev = pos->prev;
	ListNode* newnode = ListCreate(x);

	posprev->next = newnode;
	newnode->prev = posprev;

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

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* posprev = pos->prev;
	ListNode* posnext = pos->next;

	posprev->next = posnext;
	posnext->prev = posprev;
	
	free(pos);
}
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

typedef int ListDateType;

typedef struct ListNode
{
	ListDateType val;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

ListNode* ListCreate(ListDateType x);
void ListPrint(ListNode* phead);
ListNode* ListInit();
void ListPushBack(ListNode* phead,ListDateType x);
void ListPushFront(ListNode* phead, ListDateType x);
void ListPopBack(ListNode* phead);
void ListPopFront(ListNode* phead);
int ListSize(ListNode* phead);
void ListInsert(ListNode* pos, ListDateType x);
void ListErase(ListNode* pos);

 

 

 

 

 

 

 

 

 

 

 

Guess you like

Origin blog.csdn.net/m0_72165281/article/details/132074165
Recommended