[initial level of data structure]: take the lead in two-way circular linked list (implemented in C language and explained in detail in the attached figure)

1, Lead two-way circular linked list

We realized it earlier Headless one-way acyclic linked list , features: simple structure, generally not used to store data separately. In fact, it is more used as a substructure of other data structures, such as hash bucket, adjacency table of graph and so on. In addition, this structure appears a lot in OJ questions, so we need to simulate the implementation and fully understand its characteristics.

The headless one-way acyclic linked list has disadvantages, that is, the last node must be found when tail inserting and tail deleting. The time complexity is O(N), and it is inconvenient to consider whether it is deleted for the head node.
In view of these shortcomings, we will implement the lead two-way circular linked list. The structure of the lead two-way circular linked list is complex. It is generally used to store data separately. The structure is a little complex, but the implementation is very simple.

2, Take the lead in the realization of two-way circular linked list

Similar to the previous implementation of headless one-way acyclic linked list, we also need to create a node data type, which is the data field and pointer field, but the pointer field needs to store the address of the previous node and the address of the next node.

typedef int DataTypedef; //Rename the int type DataType

struct ListNode
{
	DataTypedef data;        //Data domain
	struct ListNode* next;   //Pointer field, which stores the address of the next node
	struct ListNode* prev;   //Pointer field, which stores the address of the previous node
};

//Rename the linked list type struct ListNode LNode
typedef struct ListNode LNode;

Then, after having the type, we can create a linked list type pointer plist, which is used to maintain the head node (sentinel bit) of the leading two-way circular linked list.
Then use the pointer plist to maintain the sentinel node opened on the heap, but pay attention to one thing:
To transfer the address of pointer plist, the content of pointer plist cannot be transferred;
Because if you want to change the content stored by the pointer plist, you need to pass the address of the pointer plist, and then use the secondary pointer to receive the address of the plist, so as to dereference the content stored by the pointer plist, so as to change the sentinel node maintained by the pointer plist.

3, Linked list pointer and node memory layout

4, Initialization of two-way circular linked list

Initialization needs to open up a sentinel node to prepare for the later interface, and the sentinel node stores the addresses of the previous node and the next node. This design can reduce a lot of trouble. We will analyze later. We will implement initialization first. The code is as follows:

//Initialization function
void ListInit(LNode** pphead)
{
	LNode* tmp = (LNode*)malloc(sizeof(LNode));
	if (tmp == NULL)
	{
		perror("erron ");
		exit(-1);
	}

	tmp->next = tmp;
	tmp->prev = tmp;
	tmp->data = 0;
	*pphead = tmp;
}

5, Implementation of two-way circular linked list interface:

1. Insert data in the tail


The code implementation is as follows:

//Open up new node function
LNode* BuyNewNode(DataTypedef x)
{
	LNode* newNode = (LNode*)malloc(sizeof(LNode));
	if (newNode == NULL)
	{
		perror("erron ");
		exit(-1);
	}
	newNode->data = x;
	return newNode;
}

//Tail insertion
void ListPushBack(LNode* phead,DataTypedef x)
{
	assert(phead);
	LNode* newNode = BuyNewNode(x); //Open up a node

	//Find the last node. The front of the sentry position is the tail node
	LNode* tail = phead->prev;

	// tail   newNode  phead;
	//Link a new node after the last node
	tail->next = newNode;
	newNode->prev = tail;
	phead->prev = newNode;
	newNode->next = phead;
}

If we have implemented a headless one-way acyclic linked list, we will consider many situations in this process. For example, we will ask why we do not consider the case that the linked list is empty and there is only one sentry position? Can such a special situation be realized?

This shows the advantage of storing the next and previous nodes of the sentry bit as their own addresses during initialization. This design can be realized even if the chain is empty. You can draw and analyze the structural design, which is very ingenious. As long as there are sentinels, you don't have to consider whether the linked list is empty. This is also a supplement to the disadvantages of headless one-way acyclic linked list.

2. Insert data into the header

//Head insert
void ListPushFront(LNode* phead, DataTypedef x)
{
	assert(phead);
	LNode* newNode = BuyNewNode(x);
	//Find the first node first
	LNode* next = phead->next;

	//phead   newNode   next
	//Insert between the sentinel node and the first node
	phead->next = newNode;
	newNode->prev = phead;
	newNode->next = next;
	next->prev = newNode;
}

3. Delete data at the end


The code implementation is as follows:

//Tail deletion
void ListPopBack(LNode* phead)
{
	assert(phead);
	//To ensure that there are nodes in the linked list that can be deleted, the sentry position cannot be deleted
	assert(phead->next != phead);
	LNode* tail = phead->prev;
	//To find the previous of the tail node
	LNode* tailPrev = tail->prev;

	//tailPrev  phead
	//Connect the previous one of the tail node with the sentinel position
	tailPrev->next = phead;
	phead->prev = tailPrev;
	
	//Finally, release the tail node
	free(tail);
}

4. Delete header data


The code implementation is as follows:

//Header deletion
void ListPopFront(LNode* phead)
{
	assert(phead);
	//To ensure that there are nodes in the linked list that can be deleted, the sentry position cannot be deleted
	assert(phead->next != phead);

	//Find the second node
	LNode* start = phead->next->next;
	//Release U-turn node
	free(phead->next);

	//phead   start
	//Just connect the sentry position with the second node
	phead->next = start;
	start->prev = phead;
}

5. Display data

Display the data and print out the data field in the node. The code is as follows:

//Display data
void ListPrint(LNode* phead)
{
	assert(phead);
	//Traverse from the next node of the sentry position
	LNode* cur = phead->next;

	//Equal to the sentinel position is to return to the starting point and stop traversal
	while (cur != phead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

6. Find data

Find the information in the data field. If they are equal, return the address of the change node. The code is as follows:

//Find data
LNode* ListFind(LNode* phead, DataTypedef x)
{
	assert(phead);
	//Start from the next node of the sentry position
	LNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	//Null pointer if not found
	return NULL;
}

7. Insert data in front of the node


The code is as follows:

//Insert before node
void ListInsert(LNode* pos, DataTypedef x)
{
	assert(pos);
	//Find the node in front of pos position
	LNode* posPrev = pos->prev;
	LNode* newNode = BuyNewNode(x);


	//posPrev  newNode  pos
	//Just insert between them
	posPrev->next = newNode;
	newNode->prev = posPrev;
	newNode->next = pos;
	pos->prev = newNode;

}

8. Delete current location data


The implementation code is as follows:

//Delete current node data
void ListErase(LNode* pos)
{
	assert(pos);
	//Find the node before and after the pos position
	LNode* posPrev = pos->prev;
	LNode* posNext = pos->next;

	// posPrev   posNext
	//Just connect them
	posPrev->next = posNext;
	posNext->prev = posPrev;
}

9. Memory release

//Memory release
void ListDestroy(LNode** pphead)
{
	assert(pphead);
	//Release from the next node of the sentry position
	LNode* cur = (*pphead)->next;
	while (cur != *pphead)
	{
		LNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//Finally, release the sentry post
	free(*pphead);
	*pphead = NULL;
}

6, Modification of the plug with head and tail

You can find that our insertion and deletion functions are very similar to the head and tail plug deletion, so we don't need to write a head and tail plug deletion again; We can call the insert and delete functions directly.
The transformation is as follows:

//Tail insertion
void ListPushBack(LNode* phead,DataTypedef x)
{
	assert(phead);
	
	//Inserting in front of the sentinel node is the tail insertion
	//Just call the insert function
	ListInsert(phead, x);
}

//Head insert
void ListPushFront(LNode* phead, DataTypedef x)
{
	assert(phead);
	
	//The insertion in front of the next node of the sentinel node is the head insertion
	//Direct call to insert function
	ListInsert(phead->next, x);
}

//Tail deletion
void ListPopBack(LNode* phead)
{
	assert(phead);
	//To ensure that there are nodes in the linked list that can be deleted, the sentry position cannot be deleted
	assert(phead->next != phead);

	//Delete the front position of the sentinel position node is the tail deletion
	//Direct call to delete function
	ListErase(phead->prev);
}

//Header deletion
void ListPopFront(LNode* phead)
{
	assert(phead);
	//To ensure that there are nodes in the linked list that can be deleted, the sentry position cannot be deleted
	assert(phead->next != phead);

	//The next node to delete the sentry position is header deletion
	//Direct call to delete function
	
	ListErase(phead->next);
}

With this transformation, we can greatly improve the efficiency of our implementation and quickly realize a two-way circular linked list.

7, Summary (with source code)

The above is the implementation of the two-way circular linked list. In the front, I only split the source file List.c and the test file test.c for analysis. If you want to take the lead in the two-way circular linked list, go to gitee to get it

Get the address from the source file and click

Tags: C data structure linked list

Posted on Wed, 27 Oct 2021 12:54:06 -0400 by brianlange