Data structure: Implementation of doubly linked list (C implementation)

insert image description here

Personal homepage: Personal homepage
Personal column: "Data Structure" "C Language"


foreword

In this blog, what will be implemented is the leading bidirectional circular linked list . This structure realizes tail insertion and tail deletion only needs O(1) time complexity.
Its structure is as follows:

insert image description here


1. Implementation ideas

1. Node structure (ListNode)

Since the linked list to be implemented is a two-way loop, then for the pointer field, we need a pointer to the previous node and a pointer to the next node . As for the two-way loop, we only need the next of the tail node to point to the head node, and the prev of the head node to point to the tail node to realize the two-way loop.
as follows:
insert image description here

typedef int LTDataType;//方便以后修改数据类型

typedef struct ListNode
{
    
    
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

2. Create a new node (BuyListNode)

Dynamically open up a space, make the prev and next of the node point to itself (to facilitate the creation of the header structure), then assign a value to data, and return the address of the space.

insert image description here

//创建新节点
ListNode* BuyListNode(LTDataType x)
{
    
    
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
    
    
		perror("malloc");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = newnode;
	newnode->prev = newnode;

	return newnode;
}

3. The creation of the head node (ListCreate)

Reuse the BuyListNode function to make the data of the head node invalid (here -1).

//创建返回链表的头结点
ListNode* ListCreate()
{
    
    
	return BuyListNode(-1);
}

4. Destruction of the doubly linked list (ListDestroy)

To destroy the linked list, we have to traverse the linked list, so how to traverse the linked list?

  • The end condition is to traverse to the head node
  • Start traversing from the next node of the head node

As above, we can loop through the linked list.
To destroy a node, we need two pointer variables, one to record the node to be free (cur), and one to record the next node of the node to be free (curNext). Every time free(cur), make cur = curNext.
insert image description here

//双向链表的销毁
void ListDestroy(ListNode* pHead)
{
    
    
	assert(pHead);

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

5. Printing of doubly linked list (ListPrint)

We only need to traverse and print the linked list.

  • The end condition is to traverse to the head node
  • Start traversing from the next node of the head node
//双向链表的打印
void ListPrint(ListNode* pHead)
{
    
    
	assert(pHead);

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

6. Tail insertion of doubly linked list (ListPushBack)

We only need to let the next of the tail node (tail) point to newnode, the prev of newnode point to the tail node (tail), the next of newnode point to the head node, and the prev of the head node point to newnode. We know that the linked list is a two-way cycle,
then The previous node of the head node is the tail node. (Compared with the single linked list, the linked list does not need to traverse to find the tail, and the time complexity is O(1)).
insert image description here

//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
    
    
	assert(pHead);

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

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

7. Tail deletion of doubly linked list (ListPopBack)

We only need two pointers tail (pointing to the tail node), tailPrev (pointing to the previous node of the tail node).
Free the tail, make the next of tailPrev point to the head node, and the prev of the head node point to tailPrev.

  • Note: when head->next == head, the linked list has no valid data, and the data cannot be deleted at the end.

insert image description here

//双向链表的尾删
void ListPopBack(ListNode* pHead)
{
    
    
	assert(pHead);
	//pHead->next == pHead时,链表没有元素
	assert(pHead->next != pHead);

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

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

8. Head insertion of the doubly linked list (ListPushFront)

We only need a pointer first (the next node of the head node), so that the prev of first points to newnode, the next of newnode points to first, the next of head points to newnode, and the prev of newnode points to head.

insert image description here
The head node is a sentinel bit and does not store valid data.

//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
    
    
	assert(pHead);

	ListNode* newnode = BuyListNode(x);
	ListNode* first = pHead->next;

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

9. Delete the head of the doubly linked list (ListPopFront)

We need two pointers first (pointing to the next node of the head node), second (pointing to the next node of the head node).
Free off the first, so that the prev of the second points to the head node, and the next of the head node points to the second.

  • Note: If head->next == head, it means that the linked list is empty and the head cannot be deleted.

insert image description here

//双向链表的头删
void ListPopFront(ListNode* pHead)
{
    
    
	assert(pHead);
	assert(pHead->next != pHead);

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

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

10. Doubly linked list lookup (ListFind)

We need to traverse the linked list and compare whether the linked list element is the object to be found. If found, return the node's address. If not found, return NULL.

//双向链表的查找
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;
}

11. Doubly linked list is inserted before pos (ListInsert)

We need a pointer posPrev (pointing to the node before pos).
The prev of pos points to newnode, the next of newnode points to pos; the next of posPrev points to newnode, and the prev of newnode points to posPrev.

  • If pos points to the head node (sentry position), then ListInsert is equivalent to completing tail insertion.
  • If pos points to the next node of the head node (sentry position), then ListInsert is equivalent to head insertion.

insert image description here

//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
    
    
	assert(pos);

	ListNode* newnode = BuyListNode(x);
	ListNode* posPrev = pos->prev;

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

12. Doubly linked list deletes the node at position pos (ListErase)

We need two pointers posPrev (record the address of the previous node of pos), posNext (record the address of the next node of pos). Free drop pos, make the prev of posNext point to posPrev, and the next of posPrev point to posNext.

  • If pos points to the next one of the head node, then ListErase is equivalent to head deletion.
  • If pos points to the previous node of the head node (that is, the tail node), then ListErase is equivalent to tail deletion.

insert image description here


//双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
    
    
	assert(pos);

	ListNode* posNext = pos->next;
	ListNode* posPrev = pos->prev;

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

2. Code implementation

For the head insertion and tail insertion functions, I reused the ListInsert function.
For the head delete and tail delete functions, I reused the ListErase function.

//Lish.h 文件

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


typedef int LTDataType;

typedef struct ListNode
{
    
    
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;


//创建返回链表的头结点
ListNode* ListCreate();

//创建新节点
ListNode* BuyListNode(LTDataType x);

//双向链表的销毁
void ListDestroy(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);


//Lish.c  文件

#include "List.h"


//创建返回链表的头结点
ListNode* ListCreate()
{
    
    
	return BuyListNode(-1);
}


//创建新节点
ListNode* BuyListNode(LTDataType x)
{
    
    
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
    
    
		perror("malloc");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = newnode;
	newnode->prev = newnode;

	return newnode;
}


//双向链表的打印
void ListPrint(ListNode* pHead)
{
    
    
	assert(pHead);

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

//双向链表的尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
    
    
	assert(pHead);

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

	tail->next = newnode;
	newnode->prev = tail;
	pHead->prev = newnode;
	newnode->next = pHead;*/
	
	ListInsert(pHead, x);
}


//双向链表的尾删
void ListPopBack(ListNode* pHead)
{
    
    
	assert(pHead);
	//pHead->next == pHead时,链表没有元素
	assert(pHead->next != pHead);

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

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

	ListErase(pHead->prev);
}


//双向链表的头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
    
    
	assert(pHead);

	/*ListNode* newnode = BuyListNode(x);
	ListNode* first = pHead->next;

	newnode->next = first;
	first->prev = newnode;
	newnode->prev = pHead;
	pHead->next = newnode;*/

	ListInsert(pHead->next, x);
}


//双向链表的头删
void ListPopFront(ListNode* pHead)
{
    
    
	assert(pHead);
	assert(pHead->next != pHead);

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

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

	ListErase(pHead->next);
}



//双向链表的查找
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;
}



//双向链表在pos前进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
    
    
	assert(pos);

	ListNode* newnode = BuyListNode(x);
	ListNode* posPrev = pos->prev;

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



//双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
    
    
	assert(pos);

	ListNode* posNext = pos->next;
	ListNode* posPrev = pos->prev;

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


//双向链表的销毁
void ListDestroy(ListNode* pHead)
{
    
    
	assert(pHead);

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

Summarize

The above is the realization of the doubly linked list! ! !
insert image description here

Guess you like

Origin blog.csdn.net/li209779/article/details/132068457