Data structure budget method--linked list (singly linked list, doubly linked list)

1. Linked list


Table of contents

1. Linked list

1.1 Concept and structure of linked list

1.2 Classification of linked lists

2. Implementation of singly linked list (without sentinel bit)

2.1 Interface functions

2.2 Function implementation

3. Implementation of doubly linked list (with sentinel bit)

3.1 Interface functions

3.2 Function implementation


1.1 Concept and structure of linked list

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

In reality, in data structure


1.2 Classification of linked lists

In practice, the structures of linked lists are very diverse. There are 8 types of linked list structures when combined with the following situations:
1. One-way or two-way

 2. To take the lead or not to take the lead

3. Cyclic or non-cyclical

1. Headless one-way non-cyclic linked list: The structure is simple and is generally not used to store data alone. In practice, it is more often used as a substructure of other data structures, such as hash buckets, adjacency lists of graphs, etc. In addition, this structure appears a lot in written interviews.
2. Headed two-way circular linked list: the most complex structure, generally used to store data separately. The linked list data structures used in practice are all headed bidirectional circular linked lists. In addition, although this structure is complex, after using the code to implement it, you will find that the structure will bring many advantages, and the implementation will be simple. We will know it later when we implement the code.


2. Implementation of singly linked list (without sentinel bit)


2.1 Interface functions

// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;
// 动态申请一个结点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
//销毁单链表
void SLTDestroy(SLNode** pphead);

2.2 Function implementation

1. Dynamically apply for a node

SListNode* BuySListNode(SLTDateType x)
{
    SListNode* new = (SListNode*)malloc(sizeof(SListNode));
    if (new == NULL)
    {
        perror(malloc);
        exit(-1);
    }
    new->data =x;
    new->next = NULL;
    return new;
}

Dynamically apply the linked list and pass the value to the new malloc space.


2. Single linked list tail insertion

void SListPushBack(SListNode** pplist, SLTDateType x)
{
    assert(pphead);
    SListNode* newlist = BuySListNode(x);
    if (*pplist == NULL)
    {
        *pplist = newlist;
    }
    else {
        SListNode* tail = NULL;
        tail = *pplist;
        while (tail->next != NULL)
        {
            tail = tail->next;
        }
        tail->next = newlist;
    }
}

1. Create a new node and allocate memory space for it. 2. Assign the data of the new node to the data to be inserted. 3. Set the pointer field (next) of the new node to NULL, indicating that it is the last node of the linked list. 4. If the linked list is empty, point the head pointer to the new node; otherwise, find the last node of the linked list and point its pointer field to the new node.


3. Tail deletion of singly linked list

void SListPopBack(SListNode** pplist)
{
    assert(pphead);
    assert(*pplist);
    if ((*pplist)->next == NULL)
    {
        free(*pplist);
        *pplist = NULL;
    }
    else {
        SListNode* tail = *pplist;
        SListNode* prve = NULL;
        while (tail->next != NULL)
        {    
            prve = tail;
            tail = tail->next;
        }
        free(tail);
        tail = NULL;//不置空也没问题,出作用域自动销毁
        prve->next = NULL;
    }
    
}

1. If the linked list is empty, return the empty linked list directly. 2. If the linked list has only one node, release the memory space of the node and point the head pointer to NULL. 3. Traverse the linked list and find the penultimate node. 4. Set the pointer field of the penultimate node to NULL, indicating that it is the last node of the linked list. 5. Release the memory space of the last node.


3. Head plug of singly linked list

void SListPushFront(SListNode** pplist, SLTDateType x)
{
    assert(pphead);
    SListNode* newlist = BuySListNode(x);
    newlist->next = *pplist;
    *pplist = newlist; 
}

1. Create a new node and allocate memory space for it. 2. Assign the data of the new node to the data to be inserted. 3. Point the pointer field of the new node to the current head node. 4. Point the head pointer to the new node.


4.Delete the header of single linked list

void SListPopFront(SListNode** pplist)
{
    assert(pphead);
    assert(*pplist);
    SListNode* tmp = (*pplist)->next;
    free(*pplist);
    *pplist = tmp;
}

1. If the linked list is empty, return the empty linked list directly. 2. Point the head pointer to the second node. 3. Release the memory space of the first node.


5.Singly linked list search

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
    SListNode* find = plist;
    while (find != NULL)
    {
        if (find->data == x)
        {
            return find;
            break;
        }
        find = find->next;
    }
    return NULL;
}

1. If the linked list is empty, return NULL. 2. Traverse the linked list and compare the data of the nodes with the target value one by one. 3. If a matching node is found, return the node pointer; otherwise, return NULL.


6. Insert after pos node

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
    assert(pphead);
	if (pos == NULL) {
		return;
	}

	SListNode* newlist = BuySListNode(x);
	newlist->next = pos->next;
	pos->next = newlist;
}

1. Determine whether the specified position is NULL. If it is NULL, it will be returned directly without inserting. 2. We create a new node and assign the data of the new node to the data to be inserted. 3. We point the pointer field of the new node to the original next node of the node at the specified position, and then point the pointer field of the node at the specified position to the new node to complete the insertion operation.


7. Delete the value after pos node

void SListEraseAfter(SListNode* pos)
{
    assert(pphead);
	if (pos == NULL || pos->next == NULL) {
		return;
	}

	SListNode* temp = pos->next;
	pos->next = temp->next;
	free(temp);
}

1. Determine whether the specified position is NULL or whether the next node at the specified position is NULL. If so, return directly without deletion. 2. We create a temporary pointer temp, pointing to the next node of the node at the specified position. 3. We point the pointer field of the specified location node to the next node of the temp node, then release the memory space of the temp node to complete the deletion operation.


8. Destroy singly linked list

void SLTDestroy(SLNode** pphead)
{
	assert(pphead);

	SLNode* cur = *pphead;
	while (cur)
	{
		SLNode* next = cur->next;
		free(cur);
		cur = next;
	}

	*pphead = NULL;
}
Save the next address first, and then destroy the current node.

9. Analyze and think about why not insert before pos position? Why not delete the pos position?

        This is because the nodes in the singly linked list only have one pointer pointing to the next node and no pointer to the previous node. So how to solve it? Please see the implementation of the doubly linked list later.        

3. Implementation of doubly linked list (with sentinel bit)


3.1 Interface functions

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

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

LTNode* BuyLTNode(LTDataType x);
LTNode* LTInit();
void LTPrint(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);

void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);

int LTSize(LTNode* phead);

LTNode* LTFind(LTNode* phead, LTDataType x);

// pos֮ǰx
void LTInsert(LTNode* pos, LTDataType x);
// ɾposλ
void LTErase(LTNode* pos);

3.2 Function implementation

        We can notice that the difference between the parameters of single linked list and double linked list is that the single linked list transfers the second level pointer, while the doubly linked list transfers the first level pointer. In fact, this is caused by whether there is a sentinel bit. When there is no sentinel bit, we need to use a secondary pointer to save the address of the first node of the linked list. At this time, changes the structure. Pointer , so we need to use the secondary pointer of the structure , and with the sentinel bit, we only need to change the sentinel bit For the following nodes (structures), what changes at this time is the structure , so you only need to use of the structure Level 1 pointer.

         Compared with a singly linked list, a doubly linked list actually has an additional head pointer field, so that the previous node of the current node can be found, which means that it can easily be done at any point. Insert before the node. The doubly linked list achieves "end-to-end correspondence", so naturally the nodes do not need to point to NULL.

In this way, many operations become simple, fast and efficient.


1. Dynamically apply for a node

LTNode* BuyLTNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;
	node->next = NULL;
	node->prev = NULL;

	return node;
}

2. Initialization of the head node (sentinel bit)

LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

      

         ​​​​This involves the operation of changing the value of the structure. Of course, it can also be written in the form of receiving a secondary pointer. The current method of scheduling is of course also feasible. The operation here is to initialize the sentinel bit. We can see that we point the front and back pointer fields of the head node to ourselves, thus maintaining the loop effect.


  3.Tail insert in two-way linked list 

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* tail = phead->prev;
	LTNode* newnode = BuyLTNode(x);

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

	newnode->next = phead;
	phead->prev = newnode;
}

       

        It is very convenient to implement tail deletion in a doubly linked list. There is no need to loop to find the tail node, because the front pointer field of the head node points to the tail node, so we only need one step to find the tail. Then we only need to point the tail node to the new node, then let the new node point to the tail node, then let the new node point to the head node, and finally let the head node point to the new node.


4.Tail deletion

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	free(tail);

	tailPrev->next = phead;
	phead->prev = tailPrev;

	
}

        To delete the tail node, first find the tail node and the previous node of the tail node, then release the tail node, let the new tail node point to the head, and then let the head point to the tail.


5. Print doubly linked list

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

       

        When we print a doubly linked list, we don’t need to print as long as the head node exists, so we can add a second assertion.

When printing, we start printing from the node next to the head node, and stop printing when the last circle is reached.


        Here I will omit the head insertion, head deletion, node number and search, because the operations are similar and very simple, and I will provide the complete code in the end.


6.Insert before pos node

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* posPrev = pos->prev;
	LTNode* newnode = BuyLTNode(x);

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

        If you want to insert in front of the pos node, you only need to find the pos node and the node in front of the pos node. To find the pos node, we can use it with the search function to find the desired pos node.


7. Delete pos node

void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}

If you want to delete the pos node, you only need to find the previous pos node and the next pos node, free the pos node, and then connect the previous pos node and the next pos node.

        This is the whole content of the linked list. I hope it will be helpful to you all. Next, I will update the OJ topic of the linked list. I hope you can support me! ! !​ 

Guess you like

Origin blog.csdn.net/2301_76618602/article/details/134209785