[Data structure] Linked list: add, delete, check and modify the leading bidirectional circular linked list

The content to be shared in this article is the leading two-way linked list, the following is the catalog of this film

Table of contents

1. All structures of the linked list

2. Take the lead in doubly linked list

2.1 Tail insertion

2.2 Initialization of the sentinel bit

2.3 Head insertion

2.4 Print linked list

2.5 Tail deletion

2.6 Head deletion

 2.7 Finding Nodes

2.8 Insert at any position

2.9 Delete anywhere 


When we first came into contact with the linked list, what we learned was only the single linked list. I believe that when you use the C language to learn the single linked list, you will also be tortured by the secondary pointer. Of course, the singly linked list is only one of the linked list structures. Its structure is very simple, but it is very difficult to understand and operate; and what we are going to study today is the structure of the linked list that is the most complex in the linked list, but the structure of the linked list is the easiest to understand.

1. All structures of the linked list

Understand all the structures of linked lists before learning to take the lead in doubly linked lists

1. One-way or two-way

 2. To lead or not to lead

 3. To loop or not to loop

 You can also combine the above linked list structures

The final linked list has eight structures.

What we want to explain here is the situation of taking the lead and not taking the lead. The head here means the sentinel position. The sentinel position is the data behind the link at the beginning of the linked list, but no data is stored. A separate node needs to be opened to determine the sentinel position. This is what it means to take the lead and not take the lead.

2. Take the lead in doubly linked list

In fact, we don't need to understand so many linked list structures one by one. We usually use two structures the most.

 1. Headless one-way non-circular linked list: the structure is simple, and it is generally not used to store data alone. In practice, it is more of
a substructure of other data structures, such as hash buckets, adjacency lists of graphs, and so on. In addition, this structure appears a lot in written test interviews.
2. 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 the leading bidirectional circular linked list. 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 will be simpler. We will know when we implement the code later.

2.1 Tail insertion

Same as the singly linked list, we also master the addition, deletion, checking and modification of the leading circular linked list, but the addition, deletion, checking and modification of the leading circular linked list will be much easier than the singly linked list.

First define a structure to store the position of the previous node and the position and data of the next node;

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LTDataType Data;
}LTNode;

Next, draw a picture to show you

 The node to be inserted here is newnode, we can directly operate the prev of phead to realize tail insertion.

The same as the tail insertion of the single linked list, the tail insertion must first find the tail. To find the tail in this linked list structure, you only need to use the prev of phead to find the tail, which is much more convenient than the tail search in the single linked list.

Inserting newnode is also very simple, you only need to change the positions of the four pointers, the following is the code for tail insertion

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

After comparing the above two pictures with the code of tail insertion, first write a code for opening a node to open a newnode (to be mentioned later).

newnode has been created, and then change the directions of the four pointers;

First let the sentinel phead find the tail through prev, so that the tail is tail;

Let the next of tail point to the new node newnode;

Let the prev of the new node newnode link to the previous node tail;

Then let the next of the new node newnode point to the sentry position again;

Finally, let the prev of the sentinel bit phead point to the new node newnode; so that newnode can become the end node of the linked list;

This is a complete tail plug;

Brother Meng, compared with the tail insertion of the single linked list, the tail insertion of the leading circular linked list is logically simpler and clear at a glance.

2.2 Initialization of the sentinel bit

So why can it be so simple?

As shown below

You can see our initialization of the sentinel bit. If the linked list is empty, the prev of phead points to itself, and the next of phead can also point to itself. This is the initialization of the sentinel bit.

LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

2.3 Head insertion

Since they are all inserted, we might as well make a bold guess, is it the same as the tail plug?

Next, continue drawing analysis:

The head plug should be inserted after the sentry position, because we need to find the linked list through the sentry position, so it must be inserted behind the sentry position, that is, the head plug.

In the same figure above, a node is first malloced, so how to deal with this node?

Assuming that we are the same as tail insertion, first process the pointer in front of this node

Let phead's next point to newnode;

Let the prev of newnode point to phead;

Then let the next of newnode point to the next node;

Is it really possible to do this?

 Of course not;

Have you found that if we first change the next of phead to point to newnode, can it still find the next node of newnode? Obviously it is not possible, so when inserting the head, we need to change the node behind newnode first, let newnode link with the node behind, and then let him connect with the node in front, so that the head is inserted correctly usage.

Below is the code for header

void PushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode= BuyLTNode(x);

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

}

You can read the code carefully against the above picture, it should not be difficult to understand;

2.4 Print linked list

Since we discussed head and tail plugs above, we might as well verify the results by outputting them

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

Sentinel means the sentinel bit, we need to find these linked lists through the sentinel bit;

We first define a new structure pointer cur, let cur point to the next node of the head node, iterate backwards, and use cur to traverse the linked list, in simple terms, start traversing from the node behind the sentinel bit, when this It is over when the node knows that the sentinel bit is next.

Then combined with the above diagram, we can know that the next of phead will return to the position of phead when it reaches the end, so we might as well let cur=phead be the symbol of the end of the loop, and print the content of the linked list when cur!=phead

We apply the two insertion functions above to verify that

 You can see that our head insertion, tail insertion, and printing functions are all very successful.

2.5 Tail deletion

Let's take a look at the tail deletion of the singly linked list first.

 It can be seen that the steps are quite cumbersome, because it is not only necessary to find the tail, but also to judge whether there is only one data, which is very, very troublesome. It can be said that it has all kinds of shortcomings.

But the tail deletion of the leading doubly linked list is very cool to write

Continue to draw pictures to understand

 To delete the tail of the leading doubly linked list, you can find the tail node tail only through the prev of phead, and after finding the tail node tail, you can continue to find the previous node of tail through tail->prev, we will call it tailPrev

Then point the prev of phead to the node of tailPrev;

Then point the next of the tailPrev node to the phead sentry position, so that the tail is isolated, and tailPrev becomes the new tail node

Finally, release the tail with free to achieve the result of tail deletion

Below is the code

void PopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* tail = phead->prev;
	LTNode* tailprev = tail->prev;
	free(tail);

	tailprev->next = phead;
	phead->prev = tailprev;
}

You can carefully understand the content of the code according to the above figure and text steps, it should not be difficult to understand.

2.6 Head deletion

Since it is the deletion of the head, then continue to do something on the sentry position, let's continue to draw pictures to understand

 I believe this picture is also very clear. Find the next of the next node through the sentinel phead, that is, the next of next, and then free the next to achieve the effect of deletion, and then let the prev of the original next next point to the sentry position, so The second node replaces the first node to achieve the effect of head deletion, the following is the code

void PopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTNode* first = phead->next;
	LTNode* second = first->next;

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

	free(first);

}

We might as well define phead->next as first, and define next->next as second, so that the readability of the code will be greatly improved. It should not be difficult to understand the code carefully compared with the above text description and pictures.

Of course, one thing that needs to be done to enhance the readability of the code is the assertion of assert; you can see that the above code appears

assert(!LTEmpty(phead));

How does assert assert a function in such a string of code? So what does this mean?

We write a function with bool

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

It means that if there are no elements in the linked list and the next of phead is still equal to phead, it means that the linked list is empty. The advantage of this is that it can remind you that there are no elements in the linked list for you to delete, so as to achieve brute force inspection and let the compiler The effect of reporting errors.

We use the following delete function we wrote above in the main function

 It can be seen that our tail delete has deleted the 4 inserted at the end, so what happens if we use the delete function multiple times

 It can be seen that four data are directly inserted into the main function, but the delete function is used five times, and an error will be reported at runtime, and the content of the error report is the Boolean function we just wrote, which can greatly enhance the readability of the code .

 2.7 Finding Nodes

It is not difficult to find nodes in the leading two-way circular list. The same is to traverse the linked list. What we want to find is a node, so when defining the function type, it must be a structure pointer type. If it is found, it will return its node, and if it cannot find it, it will return empty. This is the general idea

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

}

Here we need to pass two parameters, one is the head node of the linked list so that we can find the linked list and traverse the search, and the other is the number x we ​​want to find.

Of course, it is also necessary to reset a structure pointer to traverse the array, and the loop condition is the same as that of the print function. When the next node is detected, the sentinel is at phead, and the loop will stop, because the next node is phead. Traverse the entire linked list.

At this time, it is necessary to operate the data Data in the structure. If the data Data in the node is equal to the parameter x we ​​passed in when traversing, then return to this node, and return empty if it is not found.

In fact, the search function can be nested with other functions, because the search function returns a structure pointer type, and the parameters of other functions are also structure pointer types, we can use it together with the insertion function and the deletion function.

2.8 Insert at any position

Just like other insertion methods, you only need to change the content pointed to by the pointer.

The following is the legend

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyLTNode(x);

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

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

The middle insertion does not need to use phead anymore, because the phead is a sentinel position, and the middle insertion needs other nodes, that is, the nodes found through the search function just discussed, you will find that the search function It is connected with other functions in this way.

First define a structure pointer prev to store the previous node of the found node;

Then create a new node newnode;

Then just change the content pointed by the pointer in the same way as the tail insertion.

You can also use it in the main function to verify

The meaning here is to find the position of 3 through the search function, and insert 30 in front of 3, and the verification is correct;

2.9 Delete anywhere 

Similarly, it is also necessary to determine the deletion position through the search function, the following is the legend

 It should not be difficult to understand after carefully studying the content of head deletion and tail deletion.

First, find a node in front of pos and a node in the back, and then directly point the next node of the previous node to the next node of pos;

Point the prev of the next node of pos to the previous node of pos;

Finally, free the position of pos to complete the deletion operation;

Below is the code to delete anywhere

void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
	;
}

 Continue to use it in the mian function 

You can see that 3 is deleted.

The above is all the content used by the leader in adding, deleting, checking and modifying the two-way circular linked list. If it is helpful to you, please give us a lot of support. Thank you for reading.

Guess you like

Origin blog.csdn.net/wangduduniubi/article/details/130690948
Recommended