Data structure (implemented in C language) - introduction to linked list and realization of basic operations (single linked list + leading bidirectional circular linked list)

1 Introduction

We learned about sequence tables a few days ago, but sequence tables also have many disadvantages, such as: insertion and deletion in the middle and head, the time complexity is O(N), it is easy to cause memory waste, and it takes time to repeatedly apply for space. So today we will learn another structure in the linear list-linked list.

2. Linked list

2.1 The concept and structure of linked list

The linked list is a non-continuous and non-sequential storage structure in the physical storage structure. The logical order of the data elements is realized through the link order of the pointers in the linked list.

2.2 Classification of linked lists

In practice, linked lists have many different structures, such as: 1. Leading or not leading, 2. One-way or two-way, 3. Cyclic or non-cyclic. There are 8 different structures in combination of these situations.
Although there are so many structures, we have 2 most common ones:

1. Headless one-way non-circular linked list: the structure is simple, and it is generally not used to store data alone. In practice, it is more of a substructure of other data structures, such as hash buckets, adjacency lists of graphs, and so on.

2. Leading two-way circular linked list: the most complex structure, generally used to store data separately. The linked list data structure used in practice is a two-way circular linked list with the lead.

We will tell you below how the singly linked list and the leading bidirectional circular linked list are implemented.

3. Singly linked list

3.1 Structure type

A data field and a pointer field to the next node.

typedef int SLTDateType;

typedef struct SListNode
{
    
    
	SLTDateType date;
	struct SListNode* next;
}SLTNode;

3.2 Create nodes

Every time a new node is created, a piece of space is requested, and the data is initialized, and the pointer points to NULL.

SLTNode* CreatSListNode(SLTDateType x)
{
    
    
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);
	newnode->date = x;
	newnode->next = NULL;
	return newnode;
}

3.3 Print linked list

Traversing the linked list, when the pointer of the head node is NULL, the linked list traversal ends and printing stops.

void SListPrint(SLTNode* phead)
{
    
    
	while (phead)
	{
    
    
		printf("%d ", phead->date);
		phead = phead->next;
	}
	printf("NULL\n");
}

3.4 Tail insertion and deletion

Pay attention when inserting, if the head pointer is NULL, it means that the first node of the linked list should be inserted at this time, so the head pointer should point to the inserted node, otherwise, traverse to find the last node of the linked list, and then Just link the new node to the back.

insert:

void SListPushBack(SLTNode** pphead, SLTDateType x)
{
    
    
	assert(pphead);
	SLTNode* newnode = CreatSListNode(x);
	if (*pphead == NULL)
	{
    
    
		*pphead = newnode;
	}
	else
	{
    
    
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
    
    
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

When deleting, first check whether the next node of the head node is NULL. If it is, it means that there is only one node in the linked list at this time, just delete it directly. If not, traverse the linked list to find the last node and the last node in the linked list. The previous node, delete the last node and use the previous node as the new end node, and the next pointer points to NULL.

delete:

void SListPopBack(SLTNode** pphead)
{
    
    
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
    
    
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
    
    
		SLTNode* tail = *pphead;
		SLTNode* tailprev = NULL;
		while (tail->next != NULL)
		{
    
    
			tailprev = tail;
			tail = tail->next;
		}
		free(tail);
		tailprev->next = NULL;
	}
}

3.5 Head insertion and deletion

Head insertion is relatively simple, just point the next pointer of the new node to the current head node, and then use the new node as the new head node.

insert:

void SListPushFront(SLTNode** pphead, SLTDateType x)
{
    
    
	assert(pphead);
	SLTNode* newnode = CreatSListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

Deleting the head is also relatively simple, just use the next node of the current head node as the new head node, and then delete the current head node.

delete:

void SListPopFront(SLTNode** pphead)
{
    
    
	assert(pphead);
	assert(*pphead);
	SLTNode* cur = (*pphead)->next;
	free(*pphead);
	(*pphead) = cur;
}

3.6 Finding Nodes

To find a node is to directly traverse the linked list. If the data of a certain node is equal to the data to be searched, the node will be returned. If it has not been found, NULL will be returned.
Setting the return type of the search to this is for the convenience of matching with the insertion and deletion of the determined position later.

SLTNode* SListFind(SLTNode* phead, SLTDateType x)
{
    
    
	SLTNode* cur = phead;
	while (cur)
	{
    
    
		if (cur->date == x)
		{
    
    
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

3.7 Insertion and deletion at a certain position

When inserting, it should be noted that if the insertion position is the first node in the linked list, it means head insertion, and the head insertion function can be directly called here. If it is not the first node, traverse the linked list, record the position of the previous node at this position, and then insert it behind the previous node.

insert:

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
    
    
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
    
    
		SListPushFront(pphead, x);
	}
	else
	{
    
    
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
    
    
			prev = prev->next;
		}
		SLTNode* newnode = CreatSListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

Also pay attention when deleting, if the deleted position is the first node in the linked list, directly call the head delete function. If not, continue to traverse the linked list, record the position of the previous node of the node, then let the next pointer of the previous node point to the next node of the node, and finally delete the node.

delete:

void SListErase(SLTNode** pphead, SLTNode* pos)
{
    
    
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
    
    
		SListPopFront(pphead);
	}
	else
	{
    
    
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
    
    
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

The main functions of the singly linked list have been introduced, but the singly linked list also has some disadvantages, such as: finding the tail node requires traversing the linked list, which is time-consuming, and finding the previous node of a certain node also requires traversing. Next, let's learn the leading bidirectional circular linked list. The leading bidirectional circular linked list can solve these problems very well, and it is very convenient to use.

4. Take the lead in doubly linked list

4.1 Structure type

A data field, a successor pointer to the next node, and a predecessor pointer to the previous node.

typedef int LTDataType;

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

4.2 Create nodes

Each time a node is created, a piece of space is applied for, and the predecessor pointer and successor pointer of the new node are both NULL.

LTNode* CreatListNode(LTDataType x)
{
    
    
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
    
    
		perror("malloc");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

4.3 Initialize the linked list

The initialization of the leading bidirectional circular linked list is to create a head node. The head node does not store any data, and the predecessor pointer and successor pointer of the initial head node point to itself, because there are no other nodes in the linked list at this time.

LTNode* ListInit()
{
    
    
	LTNode* phead = CreatListNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

4.4 Print linked list

When printing, pay attention to traversing and printing from the next position of the head node, and the traversal ends when traversing to the head node.

void ListPrint(LTNode* phead)
{
    
    
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
    
    
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

4.5 Tail insertions and deletions

At this time, the advantage of the leading bidirectional circular linked list is reflected. There is no need to traverse to find the tail node, because the precursor pointer of the head node must point to the tail node, which is much more convenient than the single linked list, but pay attention to the order of the pointers when inserting. Finally, point the predecessor pointer of the head node to the new tail node.

insert:

void ListPushBack(LTNode* phead, LTDataType x)
{
    
    
	assert(phead);
	LTNode* newnode = CreatListNode(x);
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

When deleting, make sure that there are other nodes in the linked list besides the head node, that is, make sure that the successor pointer of the head node is not pointing to the head node itself, and then directly find the tail node and the previous node of the tail node, and put the tail node The previous node of the point is used as the new end node, and finally the previous end node can be deleted.

delete:

void ListPopBack(LTNode* phead)
{
    
    
	assert(phead);
	assert(phead->next != phead);
	LTNode* tail = phead->prev;
	LTNode* tailprev = tail->prev;
	tailprev->next = phead;
	phead->prev = tailprev;
	free(tail);
}

4.6 Head insertion and deletion

When inserting, pay attention to the order of modifying the pointers, and ensure that the successor pointer of the head node is the last to be modified, otherwise, the next node of the current head node will not be found and cause an error.

insert:

void ListPushFront(LTNode* phead, LTDataType x)
{
    
    
	assert(phead);
	LTNode* newnode = CreatListNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

When deleting, make sure that there are other nodes in the linked list besides the head node, first record the next node of the head node, and then point the next pointer of the head node to the next node of the next node of the head node, the head node Click the prev pointer of the next node to point to the head node, and finally delete the next node of the previously recorded head node, which is the head deletion.

delete:

void ListPopFront(LTNode* phead)
{
    
    
	assert(phead);
	assert(phead->next != phead);
	LTNode* pheadnext = phead->next;
	phead->next = pheadnext->next;
	pheadnext->next->prev = phead;
	free(pheadnext);
}

4.7 Finding Nodes

When searching, it is also necessary to ensure that there are other nodes in the linked list besides the head node, traverse the linked list to find the node, and finally return the pointer of the node, so that it can be used with the insertion and deletion of the determined position to be introduced below.

LTNode* ListFind(LTNode* phead, LTDataType x)
{
    
    
	assert(phead);
	assert(phead->next != phead);
	LTNode* cur = phead;
	while (cur)
	{
    
    
		if (cur->data == x)
		{
    
    
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

4.8 Insertion and deletion at a certain position

When inserting, it is the same as the previous function, just pay attention to the order of pointer modification.

insert:

void ListInsert(LTNode* phead, LTNode* pos, LTDataType x)
{
    
    
	assert(phead);
	assert(pos);
	LTNode* newnode = CreatListNode(x);
	pos->prev->next = newnode;
	newnode->prev = pos->prev;
	newnode->next = pos;
	pos->prev = newnode;
}

Also pay attention to the order of pointer modification when deleting.

delete:

void ListErase(LTNode* phead, LTNode* pos)
{
    
    
	assert(phead);
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
}

4.9 Destroy the linked list

When destroying, first ensure that there are other nodes in the linked list besides the head node, then call the delete function, traverse the linked list and delete them one by one, and finally delete the head node.

void ListDestory(LTNode* phead)
{
    
    
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
    
    
		LTNode* next = cur->next;
		ListErase(phead, cur);
		cur = next;
	}
	free(phead);
}

5. The difference between sequence list and linked list

The linked list is not a panacea, and it also has disadvantages. The linked list is actually complementary to the sequence list. Both have their own advantages and disadvantages, and they should be used flexibly.

difference sequence table linked list
storage space must be physically continuous Logically continuous, but not necessarily physically continuous
random access supports O(1) Does not support O(N)
Insert or delete elements at any position May need to move elements, low efficiency O(N) Just modify the pointer
insert Dynamic sequence table, when the space is not enough, it needs to be expanded no concept of capacity
Applicable scene Efficient storage of elements + frequent access Frequent insertion and deletion at any position
cache utilization high Low

6. Ending

At this point, the sequence table and our two common linked lists (single linked list and leading bidirectional circular linked list) are all introduced. We can find that the basic operation codes of these structures are not complicated to implement. The main thing is to ensure clear thinking and It can be done by considering special circumstances, and no matter what kind of data structure has its meaning, there is no perfect data structure, and each structure has its own advantages and disadvantages. When we use it, we must determine the most suitable one according to the usage scenario. structure.

Next, I will continue to introduce more data structures, such as stacks and queues, binary trees, etc. You can continue to pay attention.
Finally, I would like to thank you all for your patience and support. Friends who feel that this article is well written can follow and support it three times. If you have any questions or there are mistakes in this article, you can private message me or leave a message in the comment area Discussion, thanks again everyone.

Guess you like

Origin blog.csdn.net/qq_43188955/article/details/130117405