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