Introduction and implementation of bidirectional leading circular linked list

Posted by Strikebf on Fri, 29 Oct 2021 15:06:28 +0200

The two-way leading circular linked list is the most complex one in structure, but it also brings advantages that other linked lists do not have because of its complex structure. Today we will introduce the two-way leading circular linked list and complete its construction.

1. What is a two-way cycle linked list?

1.1 one way non leading non circulating linked list

Before explaining the two-way cycle linked list, let's take a look at the most simple linked list for comparison. Its structural characteristics are: one-way, no lead and no cycle. The structure diagram is as follows:

  This linked list is characterized by simple structure and easy to understand. Each node has only one pointer to the next node, and the address of the previous node is not saved. Secondly, it has no header node, and the first node saves the data. Finally, the linked list does not have a circular structure. The post pointer of the last node stores a null pointer to indicate the end of the linked list.

After introducing the simplest linked list, we also need to understand two-way linked list, leading linked list and circular linked list. They represent three different forms of linked lists. After understanding these three linked lists, the two-way lead cycle linked list is easy to understand.

1.2 bidirectional linked list

Bidirectional linked list means that each node has a pointer to the next node and a pointer to the previous node. Its structure diagram is as follows:

Compared with the basic linked list, the bidirectional linked list does not need to traverse when deleting / adding nodes at random positions. Instead, it can find the front and rear nodes of the nodes to be deleted / added through the front pointer, and complete the deletion / addition of nodes at any position with O(1) time complexity  .

1.3 lead node linked list

The leading node linked list means that the first node of the linked list does not store data, but is only used as the "head" of the linked list. This node is also called sentry position. Its structure diagram is as follows:

 

A linked list with a head node is much more convenient when operating the first node. If there is no head node, the pointer to the head of the linked list should be changed when the linked list is empty. With a head node, you only need to change the pointer of the head node.  

1.4 circular linked list

The post pointer of the last node of the circular linked list does not save the null pointer, but the address of the first node. Its structure diagram is as follows:

  For the single cycle linked list, its advantage is that it can traverse all nodes in the linked list from any node, and the more advantages of the cycle should be reflected in the two-way leading cycle linked list.

1.5 two way cycle linked list

  Combined with the characteristics of the above three linked lists, you can get a two-way leading circular linked list. Its structure diagram is as follows:

  Although the structure of this linked list looks very complex, it also contains the advantages of the above three linked lists, which can be well reflected when we realize it.

2. Implementation of two-way leading circular linked list

Define linked list nodes

typedef int ListDataType;
typedef struct ListNode
{
	struct ListNode* prev;//Leading pointer
	struct ListNode* next;//Post pointer
	ListDataType data;//data
}list;

Because it is a two-way linked list, in addition to the post pointer next, we also add the pre pointer prev to refer to the previous node. typedef is used to define the type of data, so as to change the data type saved in the data field.

Initialize header node

list* list_init()
{
	list* head = (list*)malloc(sizeof(list));//Pioneer node
	if (head == NULL)//Determine whether the header node is successfully opened
	{
        printf("malloc fail");
		exit(-1);
	}
	head->next = head;//The post pointer points to itself
	head->prev = head;//The leading pointer also points to itself
	return head;//Returns the initialized header node
}

The initialization function uses malloc to open up a dynamic space to save the header node. And initialize the head node so that its pre and post pointers point to itself, which means that the linked list is empty. Finally, the header node pointer is returned. We can receive the initialized header node by using a pointer outside the function. The following is an illustration of the initialized linked list:

Destroy linked list

void list_destroy(list* head)
{
	assert(head);//The assertion determines whether the header node exists
	while (head->next != head)//Exit the loop when the linked list has only the head node
	{
		list_pop_back(head);//Call tail deletion function
	}
	free(head);//Release header node
}

When the destructor has more than one header node in the linked list, it first enters the loop for tail deletion. Because the linked list is a circular structure, when the post pointer of the head node points to itself, it means that only the head node is left in the linked list. At this point, exit the loop and release the U-turn node. However, it should be noted that the pointer outside the function becomes a wild pointer, and the destruction of the linked list is completed only by setting it null.

Tail insertion

void list_push_back(list* head,ListDataType x)
{
	assert(head);
	list* tail = head->prev;//Tail finding
	list* newnode = (list*)malloc(sizeof(list));//Create a new node
	if (newnode == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}

	newnode->next = head;//Connection node
	head->prev = newnode;
	newnode->prev = tail;
	tail->next = newnode;

	newnode->data = x;//Assign a value to the data field of the new node
}

  Because the linked list is a two-way cycle, we can easily find the tail node through the front pointer of the head pointer. The tail insertion can be completed by connecting the new tail node with the old tail node and head node.

Tail deletion

void list_pop_back(list* head)
{
	assert(head&&head->next!=head);//Assertion ensures that there is at least one node in the linked list except the head node
	list* backprev = head->prev->prev;//Save the penultimate node
	free(head->prev);//Release tail node

	backprev->next = head;//Connection node
	head->prev = backprev;
}

The assertion in the function related to deletion not only ensures the validity of the head node, but also ensures the existence of at least one node in the linked list. Otherwise, the head node may be released and serious errors may occur. After assertion, find and save the penultimate node through the front pointer, and then connect the penultimate node with the head node to complete tail deletion.

Head insert

void list_push_front(list* head,ListDataType x)
{
	assert(head);
	list* next = head->next;//Save the next node of the header node
	list* newnode = (list*)malloc(sizeof(list));
	if (newnode == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}

	head->next = newnode;//Connection node
	newnode->prev = head;
	next->prev = newnode;
	newnode->next = next;

	newnode->data = x;//assignment
}

First save the next node of the header node, then create a new node, and let the new node connect the upper header node and the next node to complete the header insertion.

Header deletion

void list_pop_front(list* head)
{
	assert(head && head->next != head);//Assert
	list* next = head->next;//Save the node to release

	head->next->next->prev = head;//Connection node
    head->next = head->next->next;
	

	free(next);//release
}

Header deletion also ensures that there is at least one node in the linked list that saves data through assertion, otherwise the header node may be deleted. Then save the node to be deleted first, and the connection node can complete the header deletion.

Print linked list

void list_print(list* head)
{
	assert(head);
	list* cur = head->next;//Start at the next position of the ab initio node
	while (cur != head)//Ends the loop when the pointer points to the header node
	{
		printf("%d ", cur->data);//Here, take int data as an example
		cur = cur->next;//The pointer points to the next node
	}
	printf("\n");
}

  Because there are header nodes, the nodes that traverse the linked list during output should traverse from the next node of the node. Because it is a loop structure, when cur points to the head node, it means that the linked list has completed a traversal, and then exit the loop.  

lookup

list* list_find(list* head, ListDataType x)
{
	assert(head);
	list* cur = head->next;//Save the next node of the header node
	while (cur != head)//When the traversal has not been found, exit the loop
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;//cur points to the next node
	}
	return NULL;//Null if not found
}

The search also needs to ensure that the linked list is traversed from the next node of the head node through assertion. If it is found, the pointer of the node is returned. Otherwise, when the pointer is equal to the head, it means that the linked list has been traversed but not found. Exit the loop and return NULL.

Inserts a new node before the specified node

void list_insert(list * pos, ListDataType x)
{
	assert(pos);
	list* prev = pos->prev;//Save the previous node for connection

	list* newnode = (list*)malloc(sizeof(list));//Create a new node
	if (newnode == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}

	prev->next = newnode;//Connection node
	newnode->prev = prev;
	pos->prev = newnode;
	newnode->next = pos;

	newnode->data = x;//assignment
}

  First save the previous node at the specified location to facilitate the connection of new nodes, and then create new nodes and connect them.

Delete specified node

void list_erase(list* pos)
{
	assert(pos&&pos->next!=pos);//Assert

	pos->prev->next = pos->next;//Connect front and rear nodes
	pos->next->prev = pos->prev;

	free(pos);//release
}

Connect the nodes before and after the node to be deleted, and then release the space of the node to be deleted to complete the deletion.

3. Summary

By constructing a two-way leading circular linked list, we can find that although the structure of this linked list is complex:

1. With head node.

2. Each node contains a pre pointer and a post pointer.

3. The post node of the last node points to the head node, and the front pointer of the head node points to the last node, forming a two-way loop.

It can be seen from the above implementation stage that although the structure of this linked list is very complex, it greatly reduces the difficulty of implementation: because of the circular structure, there is no need to find the tail during tail insertion. You can directly find the tail node through the front pointer of the head pointer for insertion. With the bidirectional structure, when inserting a node in front of any node, you do not need to traverse the linked list to find the previous pointer, but you can directly find the previous node through the previous pointer to complete the insertion. With the header node, there is no need to judge whether the linked list is empty during insertion.

And this structure also has great advantages in use: it saves a lot of traversal (such as finding the tail and finding the previous node), which greatly reduces the time complexity of this linked list in operation, and greatly improves the efficiency compared with the simplest linked list.

However, this structure also has a small disadvantage. Because of the existence of the front pointer, every time a node is created, it takes an extra pointer space. However, this can not deny that the two-way leading circular linked list is very practical.

 

 

 

 

 

Topics: data structure linked list