[Data structure--C language] Realization of two-way leading circular linked list

Table of contents

1. Introduction of two-way leading circular linked list

2. The interface of bidirectional leading circular linked list

3. Interface implementation

3.1 Open up nodes

3.2 Create the head node of the returned linked list

3.3 Determine whether the linked list is empty

3.4 print

3.5 Doubly linked list search

3.6 The doubly linked list is inserted in front of pos

3.6.1 Plug

3.6.2 Rear plug

3.6.3 Update the writing method of head insert and tail insert

3.7 Doubly linked list deletes the node at position pos

3.7.1 Header deletion

3.7.2 Tail deletion

3.7.3 Update head and tail deletion

3.8 Doubly linked list destruction

4. Complete code

5. Function test


1. Introduction of two-way leading circular linked list

We split this topic to extract three keywords: two-way, leading, and looping. Let's start with these three keywords:

The first is two-way : two-way means that this node can find its predecessor and successor, which is essentially different from the singly linked list;

The second is the lead : the lead shows that the linked list has a head node, which can also be called the head node of the sentinel position. This node is the same as other nodes, except that its data field is filled with random values ​​( Some will store the length of the linked list);

The last is the loop : the loop shows that its structure is a ring, the predecessor node of the head node stores the tail node, and the successor node of the tail node stores the head node.

2. The interface of bidirectional leading circular linked list

The function of the two-way leading circular linked list is the same as that of the single linked list, and both can be added, deleted, checked and modified, but the characteristics of the two-way leading circular linked list greatly improve our efficiency when writing such a function.

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

// 创建返回链表的头结点
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

3. Interface implementation

3.1 Open up nodes

Our linked list is a dynamic linked list, so we need to malloc a node every time we insert it, and we encapsulate it into a function to facilitate subsequent code reuse.

ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (NULL == newnode)
	{
		perror("malloc fail:");
		return NULL;
	}
	
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

3.2 Create the head node of the returned linked list

This step is where we create a sentinel head node for the linked list. Because it is a two-way leading circular linked list, we let the prev and next pointers both point to ourselves, so that even if there is only one head node, we are also a two-way circular structure.

ListNode* ListCreate()
{
	ListNode* phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

3.3 Determine whether the linked list is empty

This step is an empty-judgment function that we encapsulate for subsequent deletions. When the linked list exists, it does not rule out the case that the linked list is empty, so we encapsulate this function so that the subsequent interfaces can be reused.

bool ListEmpty(ListNode* pHead)
{
	assert(pHead);

	return pHead->next == pHead;
}

3.4 print

We are already familiar with the printing function, but we should pay attention to the printing termination condition of the two-way leading circular linked list. Here is no longer the previous cur == NULL, but cur != pHead. When cur reaches pHead, it means that we have traversed. The whole linked list is gone.

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;

	printf("guard");
	while (cur != pHead)
	{
		printf("<==>%d", cur->data);
		cur = cur->next;
	}
	printf("<==>\n");
}

3.5 Doubly linked list search

It is not difficult to find, but here it should be noted that the first node of the two-way leading linked list is the sentinel node, so we need to traverse the search from the next node of the sentinel (cur = pHead->next), the end of the loop The condition is still cur != pHead.

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->next;

	while (cur != pHead)
	{
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}
	return NULL;
}

3.6 The doubly linked list is inserted in front of pos

After we find the pos position, we insert it before the pos position, and we execute it according to the following process:

1. If we want to insert a data4 before data3, we first define a structure pointer variable prev, and first save the data3->prev (data2) node in the prev variable;

2. Then change prev->next to data4, and then change data4->prev to prev;

3. Finally, set data4->next = data3, and then set data3->prev = data4.

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);
	
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

3.6.1 Plug

For head insertion, we are consistent with inserting before the pos position, first save the first node, and then insert.

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* newnode = BuyListNode(x);
	ListNode* first = pHead->next;//多写这一个变量下面的插入语句就可以无序写

	pHead->next = newnode;
	newnode->next = first;
	newnode->prev = pHead;
	first->prev = newnode;
}

3.6.2 Rear plug

For tail insertion, first save the tail node of the linked list, and then insert it, which is still the same as inserting before the pos position.

void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* tail = pHead->prev;
	ListNode* newnode = BuyListNode(x);

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

3.6.3 Update the writing method of head insert and tail insert

Comparing the insertion code, it is not difficult to find that the logic of the head insertion and tail insertion is the same as the code inserted before the pos position, and the implemented functions have basically not changed, so we can directly reuse ListInsert when inserting the head and tail. The interface can be realized.

1> plug

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListInsert(pHead->next, x);
}

2> tail plug

Because it is inserted before the pos position, the linked list is a two-way cycle, so the first parameter passed is pHead, and the node before the sentinel node is the tail node.

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListInsert(pHead, x);
}

3.7 Doubly linked list deletes the node at position pos

After we find the pos node, we delete the pos node, and we execute the following process:

1. Define two structure pointer variables posPrev and posNext, and store the nodes before and after the pos position in the posPrev and posNext pointer variables respectively;

2. Set posPrev->next = posNext, then set posNext->prev = posPrev;

3. Release the pos node, free(pos).

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* posPrev = pos->prev;
	ListNode* posNext = pos->next;

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

3.7.1 Header deletion

If the head is deleted, we first save the pHead->next node and the pHead->next->next node, connect the sentinel node to the next node of the head node, and then release the head node.

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));

	ListNode* first = pHead->next;
	ListNode* second = first->next;

	pHead->next = second;
	first->prev = pHead;
	free(first);
}

3.7.2 Tail deletion

If the tail is deleted, we first save the pHead->prev node and the pHead->prev->prev node, connect the sentinel node with the previous node of the tail node, and then release the tail node.

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));

	ListNode* tail = pHead->prev;
	ListNode* tailPrev = tail->prev;

	tailPrev->next = pHead;
	pHead->prev = tailPrev;
    free(tail);
}

Summary : Before deleting the head and tail, it is necessary to judge whether the linked list is empty.

3.7.3 Update head and tail deletion

Comparing the delete code, it is not difficult to find that the code logic of deleting the head delete and tail delete is the same as that of deleting the pos position node, and the implemented functions have basically not changed, so we can directly reuse ListErase when deleting the head and tail. The interface can be realized.

1> head delete

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));

	ListErase(pHead->next);
}

2> tail delete

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));

	ListErase(pHead->prev);
}

3.8 Doubly linked list destruction

The destruction of the linked list is mainly to traverse the linked list once, starting from cur = pHead->next, the condition of loop termination is cur != pHead, after traversing, the sentinel node is released, and the entire linked list is released.

void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;

	while (cur != pHead)
	{
		ListNode* next = cur->next;
		free(cur);

		cur = next;
	}
	free(pHead);
}

4. Complete code

The complete code is in the code warehouse, the entry: C language: C language learning code, review more - Gitee.com

5. Function test

Guess you like

Origin blog.csdn.net/Ljy_cx_21_4_3/article/details/130729905