Playing with the lead doubly linked list - [data structure]

W...Y's homepage

Today we will continue to talk about the data structure-leading doubly linked list

Table of contents

Implementation of the lead doubly linked list

structure creation

Initialize soldiers to create sentinel nodes

Release the linked list so the content 

print linked list function

tail plug

tail delete

plug

​edit

head delete

Count function implementation

Find the corresponding position function of the data

insert before pos position 

Delete at pos position 

The difference between sequence list and linked list


Compared with the ordinary doubly linked list, the Doubly Linked List with Head adds a head node. The head node does not store any actual data, but is only used to indicate the starting position of the linked list. Here are some advantages of leading doubly linked list:

  1. The linked list is easy to operate: the leading two-way linked list provides the ability to directly access the head and tail of the linked list, making operations such as insertion and deletion of the linked list more efficient. You can quickly insert the first element through the head node, or quickly insert new elements through the tail node. At the same time, due to the bidirectionality of the linked list, you can easily insert and delete at any position in the linked list.

  2. Traversal flexibility: The headed doubly linked list can be traversed from the beginning to the end or from the end to the head. This means that you can choose the appropriate traversal method according to your needs, whether you are traversing the linked list from front to back or from back to front, you can easily get elements.

  3. Reverse lookup: An important advantage of the headed doubly linked list is that the previous node of the node can be quickly accessed from any node through the back link. This makes reverse lookups in linked lists efficient. The reverse lookup time complexity of a headed doubly linked list is reduced from O(n) to O(1) compared to a singly linked list, which can be very important for some applications.

  4. Convenient node deletion: In the leading doubly linked list, deleting a node does not need to visit its previous node, but only needs to modify the front and rear links of the current node. In this way, the node deletion operation becomes more convenient and efficient.

Leading two-way circular linked list: the most 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 the structure is complex, after using the code to implement it, you will find that the structure will bring many advantages, and the implementation is simple. We will know when we implement the code later.

Implementation of the lead doubly linked list

structure creation

typedef int LTDataType;

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

Create a structure and use typedef to rename it to LTNode for easy use, and create next and prev nodes to save the next node and the previous node.

Initialize soldiers to create sentinel nodes

When we create a sentinel position, we can add -1 in the middle of the data, and after creation, point the next and prev to ourselves. But this function is very similar to how to add the node function, so we can create the node function first, and then call it when initializing the sentinel to prevent redundancy.

LTNode* BuyLTNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	node->data = x;
	node->prev = NULL;
	return node;
}
LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

 When we create two more functions, let the LTInit function call the BuyLTNode function to initialize the sentinel bit.

Release the linked list so the content 

Because the space opened by the linked list using realloc is all in the heap area, it is necessary to release all the space opened by the linked list to prevent memory leaks.

void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

print linked list function

When printing the linked list, we need to distinguish the secondary linked list from the single linked list. The single-linked list only needs to find the tail NULL to stop, and the tail of the double-linked list points to the sentinel bit, so we can use another method for detection.

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("phead<->");
	while (cur != phead)
	{
		printf("%d<->", cur->data);
		cur = cur->next;
	}
}

We create a traversal pointer cur, let cur = phead->next point to the next node of the sentinel position. Then traverse, if the traverse returns to the sentinel position phead stop.

tail plug

Tail insertion is also very convenient compared to the singly linked list. You don't need to traverse to find the tail node, you only need to visit the prev of phead to find the tail node. 

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyLTNode(x);

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

tail delete

We first have to check whether the phead sentinel bit is empty. If the linked list is empty, we cannot delete it normally, so we have to continue to judge phead->next != phead. Your own next node cannot point to yourself.

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* tail = phead->prev;
	phead->prev = tail->prev;
	tail->prev->next = phead;
	free(tail);

}

Deletion is very simple. Visit the last node as tail, switch the address of phead connected to the tail node to the previous node of the tail node, and then replace the next node of the tail node with the sentinel address, and free the tail node. Can. 

plug

The principle of head plug and tail plug is basically the same, only need to change the content of the sentinel position.

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyLTNode(x);
	/*newnode->next = phead->next; 
	phead->next->prev = newhead;
	phead->next = newhead;
	newhead->prev = phead;*/
	LTNode* first = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	first->prev = newnode;
	newnode->next = first;
}

 We can refer to the above code for header insertion, and there are two methods. The first (annotated) is to not create any pointer variables for exchange replacement, and the second (unannotated) is to create variables for exchange. The first method must be rigorous in the order of exchange, first interact with the tail node, and then interact with the first node, otherwise the exchange will fail and error data will appear.

head delete

When deleting, be sure to check whether the linked list is empty, and whether the linked list can only be deleted with sentinel bits.

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* first = phead->next;
	LTNode* second = first->next;
	free(first);
	phead->next = second;
	second->prev = phead;
}

Count function implementation

When we enter a large amount of data, the function created for the convenience of counting. '

int LTsize(LTNode* phead)
{
	assert(phead);
	int size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}
	return size;
}

Here it is only necessary to traverse each node of the linked list until it returns to the sentinel position.

Find the corresponding position function of the data

When the operator wants to know the position of a certain data in the linked list and operate it, we can call this function to return the address of the node where the corresponding data is located.

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while(cur != phead)
	{
		if (cur->data == x)
			return cur;
	}
		return NULL;
}

Here we use the brute force search algorithm to search the entire set of linked lists, and return the corresponding data after finding it.

insert before pos position 

Here we need to call it in conjunction with the find location function.

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* newnode = BuyLTNode(x);
	posprev->next = newnode;
	newnode->next = pos;
	newnode->prev = posprev;
	pos->prev = newnode;
}

This is actually the same as the principle of head plugging and tail plugging, so we can also use this function to perform head plugging and tail plugging, just pass in the pos parameter correctly, which are phead->next (head plugging), phead->prev (tail plugging ).

Delete at pos position 

void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;
	free(pos);
	posprev->next = posnext;
	posnext->prev = posprev;
}

This is also the same as the principle of head deletion and tail deletion, as long as the parameters are passed in correctly, it can become head deletion and tail deletion.

The difference between sequence list and linked list

Therefore, it is very important to choose different data structures in different situations. We need to understand the differences and advantages enough to meet the different needs of various problems.

Guess you like

Origin blog.csdn.net/m0_74755811/article/details/132122390