[data structure] two way circular linked list

Keywords: C data structure

Structure of bidirectional linked list

1. Each node of the bidirectional circular linked list includes the following parts:

2. The data field in the header node has no practical significance

3. Bidirectional circular linked list
For example:

Basic operation

data structure

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

Create node

Because the linked list is a dynamic space, you need to apply for space from the heap every time you insert elements.
The specific operation details are as follows:

//Create a node
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
	if (NULL == newNode)
	{
		return NULL;
	}
	newNode->data = x;
	newNode->next = NULL;
	newNode->prev = NULL;
	return newNode;
}

Create header node

For the head node, when creating the head node, it is slightly different from other nodes, and its data field has no practical significance.

// Create the head node of the return linked list
ListNode* ListCreate()
{
	//The data field of the header node has no practical significance
	ListNode* head = BuyListNode(0);
	head->next = head;
	head->prev = head;
	return head;
}

Destruction of bidirectional linked list

Because each node of the linked list is dynamically opened up from the heap, it must be manually released after use, otherwise memory leakage will occur.
See the following figure for detailed analysis:

Pay attention to the problem of parameter transmission. Others are routine operations!
The specific implementation code is as follows:

// Bidirectional linked list destruction
void ListDestory(ListNode** pHead)
{
	assert(pHead);
	
	ListNode* delNode = (*pHead)->next;
	while (delNode != *pHead)
	{
		(*pHead)->next = delNode->next;
		delNode->next->prev = (*pHead);
		free(delNode);
		delNode = (*pHead)->next;
	}
	//Finally, the remaining head nodes are not released
	free(*pHead);
	*pHead = NULL;
}

Printing of bidirectional linked list

Loop through the entire linked list and output the data field content of each node at the same time.
Note: Determination of cycle termination conditions
Because it is a circular double linked list, when the output node is equal to the head node, one round of traversal has been completed.

// Bidirectional linked list printing
void ListPrint(ListNode* pHead)
{
		

	if (pHead == pHead->next)
	{
		printf("Empty linked list, no node!\n");
		return;
	}
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d ",cur->data);
		cur = cur->next;
	}

	printf("\n");
}

Tail insertion of double linked list

To insert a node at the end of the linked list, you need to go through the following steps:
1. Create a new node
2. Find the last node of the original linked list
3. Change the pointer of the linked list so that the new node becomes the last node of the linked list
The first two steps can be easily implemented. Let's analyze the third step:

It can be seen that inserting a node at the end of a two-way circular linked list needs to change the direction of the four pointers in a certain order.
Next, let's look at the specific implementation code

// Bidirectional linked list tail insertion
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* newNode = BuyListNode(x);
	newNode->next = pHead;
	newNode->prev = pHead->prev;
	pHead->prev->next = newNode;
	pHead->prev = newNode;
}

Tail deletion of bidirectional linked list

To delete the end node, the following steps are required:
1. Find the end node

2. Since the end node will be deleted, the relevant pointer should be changed to make the penultimate node of the original linked list become the end node of the new linked list.

3. Release the memory space of the node to be deleted.

For a two-way circular linked list, steps 1 and 3 are easy to implement. Let's analyze step 2 in detail
By changing the pointer, the penultimate node (node 3 in the figure below) is taken as the end node of the new linked list.

See code for complete operation

// Bidirectional linked list tail deletion
void ListPopBack(ListNode* pHead)
{
	assert(pHead);

	if (pHead ==  pHead->next)
	{
		printf("The linked list is empty and cannot be deleted!\n");
		return;
	}

	ListNode* delNode = pHead->prev;
	pHead->prev = delNode->prev;
	delNode->prev->next = pHead;
	free(delNode);
	delNode = NULL;
}

Head insertion of double linked list

To insert a node, you need to perform the following steps:
1. Apply for space for the node to be inserted

2. Find the first node of the original linked list

3. Adjust the relevant pointer so that the prev of the new node is connected with the head node, and the next of the new node is connected with the head node of the original linked list

Focus on step 3:
To insert a node, we need to modify the relevant pointers in the original linked list to add the new node. The specific implementation process is shown in the figure below

The following is the code of the overall implementation

// Bidirectional chain header insertion
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* newNode = BuyListNode(x);

	newNode->next = pHead->next;
	newNode->prev = pHead;
	pHead->next->prev = newNode;
	pHead->next = newNode;
}

Double linked list header deletion

Steps:
1. Find the first node

2. Modify the pointer so that the second node is connected with the head node

3. Release the original node (the node found in step 1)

Mainly analyze the implementation process of step 2:

The complete implementation code is as follows

// Bidirectional linked list header deletion
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	if (pHead == pHead->next)
	{
		printf("No node, cannot delete!\n");
		return;
	}
	ListNode* delNode = pHead->next;

	pHead->next = delNode->next;
	delNode->next->prev = pHead;
	free(delNode);
	delNode = NULL;
}

Search of bidirectional linked list

The main idea is consistent with the output of the linked list. Cycle through the linked list. In the process of traversal, judge whether the data field of each node is equal to the target value. If it is equal, return the address of the node and exit the cycle. If it is still not found after traversing the linked list, it will return NULL value.
This is not drawing analysis, directly on the code

// Bidirectional linked list lookup
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//Loop through the comparison data field and pay attention to the control conditions of the loop
	ListNode* cur  = pHead->next;
	while (pHead != cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

The bidirectional linked list is inserted in front of the pos

Steps:
1. Create space for inserted nodes
2. Modify the relevant pointer and insert the linked list
The specific process is shown in the figure below

The analysis in the figure above is more detailed and directly implements the code

// The bidirectional linked list is inserted in front of the pos
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* newNode = BuyListNode(x);
	newNode->next = pos;
	newNode->prev = pos->prev;
	pos->prev->next = newNode;
	pos->prev = newNode;
}

A bidirectional linked list deletes a node at the pos position

To delete a node, we should establish a relationship between its previous node and the next node, and then release the node to be deleted directly. As shown in the figure below

The operation is relatively simple, so you can directly enter the code

// A bidirectional linked list deletes a node at the pos position
void ListErase(ListNode* pos)
{
	assert(pos);

	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

Complete code

For details, please click My Git

summary

For the two-way circular linked list of the leading node, its characteristics are obvious:
1. The head node closely connects the head and tail nodes of the linked list, which increases the efficiency of finding nodes

2. The internal front and back nodes of the linked list are also more flexible and can be accessed at will. When performing insertion, deletion and other operations, it is very efficient. It can be realized only by changing the direction of the corresponding pointer.

3. Support insertion and deletion with time complexity of O(1) at any location, without capacity expansion and space waste.

Ladies and gentlemen, the third company supports Yibo~~~

Posted by spillage on Tue, 30 Nov 2021 15:27:55 -0800