[Data structure] Linked list of linear list

foreword

The previous article described the sequential list in the linear list. This article describes the definition, category, implementation, advantages and disadvantages of various linked lists, and the advantages and disadvantages of linked lists and sequential lists.
A link to the previous article: Sequence table of linear table

1. The definition 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.

insert image description here

Second, the classification of linked lists

1. One-way and two-way

insert image description here

insert image description here

2. Leading and not leading

insert image description here
insert image description here

3. To loop and not to loop

insert image description here
insert image description here

4. Commonly used (headless one-way non-circular linked list and headed two-way circular linked list)

insert image description here

insert image description here

3. Interface and implementation of headless one-way non-circular linked list

1. Interface of singly linked list

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

// slist.h
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
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode* plist);

2. Implementation of the interface

#include "slist.h"

SListNode* BuySListNode(SLTDateType x)
{
    
    
	//创造新节点
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
    
    
		perror("malloc");
		return NULL;
	}
	//新节点初始化
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

void SListPushBack(SListNode** pplist, SLTDateType x)
{
    
    
	//这里使用二级指针的原因是:
	//若链表为空,需要改变的就是结构体指针,需要结构体指针的地址
	//若传入的是一级指针,这里传入的只是临时拷贝,无法改变函数外的变量
	if (*pplist == NULL)
	{
    
    
		*pplist = BuySListNode(x);
	}
	//若不为空,需要改变的是结构体,只需要结构体的指针
	else
	{
    
    
		SListNode* tail = *pplist;
		SListNode* newnode = BuySListNode(x);
		while (tail->next)
		{
    
    
			tail = tail->next;
		}

		tail->next = newnode;
	}

}

void SListPrint(SListNode* plist)
{
    
    
	while (plist)
	{
    
    
		printf("%d->", plist->data);
		plist = plist->next;
	}
	printf("NULL");
}

void SListPushFront(SListNode** pplist, SLTDateType x)
{
    
    
	//这里使用二级指针的原因是:每次头删都需要改变头节点
	SListNode* newnode = BuySListNode(x);
	
	newnode->next = *pplist;
	*pplist = newnode;
}

//无头单链表尾删删除节点的时候有三种情况
void SListPopBack(SListNode** pplist)
{
    
    
	//没有节点
	assert(*pplist);

	//一个节点
	if ((*pplist)->next == NULL)
	{
    
    
		free(*pplist);
		*pplist = NULL;
	}
	//多个节点
	else
	{
    
    
		//尾删既可以找尾找尾的前一个节点,也可以创造一个变量记录尾节点的前一个节点

		//创造一个变量记录尾节点的前一个节点
		//	SListNode* tail = *pplist;
		//	SListNode* prev = NULL;
		//	while (tail->next)
		//	{
    
    
		//		prev = tail;
		//		tail = tail->next;
		//	}

		//	free(prev->next);
		//	prev->next = NULL;
		
		//找尾找尾的前一个节点
		SListNode* tail = *pplist;
		while (tail->next->next)
		{
    
    
			tail = tail->next;
		}

		free(tail->next);
		tail->next = NULL;
	}
}

//无头单链表头删删除节点的时候有三种情况,但只有一个节点和多个节点的情况可以合并
void SListPopFront(SListNode** pplist)
{
    
    
	//无节点时
	assert(*pplist);
	
	//有节点时
	SListNode* del = *pplist;
	*pplist = del->next;
	free(del);
}

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

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
    
    
	//断言:在pos后面插入一个节点,最差的情况是pos为尾节点,但不能为NULL
	assert(pos);
	SListNode* cur = (SListNode*)malloc(sizeof(SListNode));
	if (cur == NULL)
	{
    
    
		perror("malloc");
		return;
	}
	cur->data = x;
	cur->next = pos->next;
	pos->next = cur;
}

void SListEraseAfter(SListNode* pos)
{
    
    
	//需要删除节点的前一个节点不能为NULL,删除节点也不能为NULL
	assert(pos);
	assert(pos->next);
	
	SListNode* next = pos->next;
	pos->next = next->next;
	free(next);
}

void SListDestroy(SListNode* plist)
{
    
    
	SListNode* del = NULL;
	while (plist)
	{
    
    
		del = plist;
		plist = plist->next;
		free(del);
	}
}

Fourth, take the lead in the implementation of the two-way circular linked list interface

1. Interface of doubly linked list

#pragma once

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

// 带头+双向+循环链表增删查改实现
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);

2. Implementation of the interface

#include "list.h"

// 创建返回链表的头结点.
ListNode* ListCreate()
{
    
    
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	if (phead == NULL)
	{
    
    
		perror("malloc");
		return NULL;
	}

	//让头的 next 和 prev 都指向自己
	//则双向链表为空
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

// 创造新节点
ListNode* BuyTLNode(LTDataType x)
{
    
    
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
    
    
		perror("malloc");
		return NULL;
	}

	newnode->data = x;

	return newnode;
}

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
    
    
	/*ListNode* tail = pHead->prev;
	ListNode* newnode = BuyTLNode(x);
	
	newnode->next = pHead;
	newnode->prev = tail;
	tail->next = newnode;
	pHead->prev = newnode;*/
	ListInsert(pHead, x);   //复用
}

// 双向链表打印
void ListPrint(ListNode* pHead)
{
    
    
	printf("header <--> ");

	//打印的时候,由于头内的 data 的值没用
	//则从头的的下一个节点开始打印
	//并且在循环到头的时候打印结束
	ListNode* cur = pHead ->next;
	while (cur != pHead)
	{
    
    
		printf("%d <--> ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

// 判断双向链表是否为空
bool LTEmpty(ListNode* pHead)
{
    
    
	assert(pHead);
	return pHead->next == pHead;
}

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
    
    
	assert(pHead);
	assert(!(LTEmpty(pHead)));

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

	free(first);*/
	ListErase(pHead->next);   //复用
}

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
    
    
	/*ListNode* next = pHead->next;
	ListNode* newnode = BuyTLNode(x);
	
	newnode->next = next;
	newnode->prev = pHead;
	next->prev = newnode;
	pHead->next = newnode;*/
	ListInsert(pHead->next, x);//复用
}

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
    
    
	assert(pHead);               
	assert(!(LTEmpty(pHead)));
	/*ListNode* tail = pHead->prev;
	ListNode* tailPrev = tail->prev;

	tailPrev->next = pHead;
	pHead->prev = tailPrev;

	free(tail);*/

	ListErase(pHead->prev); //复用
}

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
    
    
	ListNode* cur = pHead ->next;

	while (cur)
	{
    
    
		if (cur->data == x)
		{
    
    
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}


// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
    
    
	assert(pos);
	ListNode* newnode = BuyTLNode(x);
	ListNode* posPrev = pos->prev;

	newnode->next = pos;
	newnode->prev = posPrev;
	posPrev->next = newnode;
	pos->prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
    
    
	assert(pos);

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

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

	free(pos);
}

5. Leading two-way circular linked list VS headless one-way non-circular linked list

1. Take the lead in bidirectional circular linked list

1.1 Advantages of the lead bidirectional circular linked list:

  1. can be freely in the linked listany positionInsert and delete nodes, because the doubly linked list can easily find the predecessor and successor nodes .
  2. can supportbidirectional traversal, that is, the linked list can be traversed from front to back or from back to front.
  3. Some special operations can be implemented more efficiently. For example, to delete a specified node in the linked list, it is necessary to modify the pointers of its predecessor and successor nodes at the same time. The two-way linked list can directly complete this operation, while the one-way linked list needs to traverse to the predecessor node. Finish.

1.2 Disadvantages of leading bidirectional circular linked list:

  1. Because each node additionally stores a pointer to the predecessor node, more memory space is required.
  2. Because pointers to predecessor and successor nodes need to be maintained, more operations are required when inserting and deleting nodes, resulting in high time complexity.

2. Headless one-way acyclic linked list

2.1 Advantages of headless one-way non-circular linked list:

  1. Because each node only needs to store a pointer to a successor node, less memory space is required.
  2. When inserting and deleting a node, only the pointer of the previous node needs to be modified, and the pointer of the next node does not need to be modified. The operation is relatively simple, resulting in low time complexity.

2.2 Disadvantages of headless one-way non-circular linked list:

  1. Two-way traversal cannot be achieved, that is, the linked list cannot be traversed from back to front.
  2. When deleting a specified node, it is necessary to traverse to its predecessor node before completing the deletion operation, resulting in low deletion efficiency.

3. Summary

In short, which linked list data structure to choose should be determined according to the specific application scenario and the operations that need to be done. If you need to insert and delete nodes frequently, and you need to support bidirectional traversal, you can choose the headed bidirectional circular linked list; if you need to occupy less memory space and do not need bidirectional traversal, you can choose the headless one-way non-cyclic linked list.


6. Linked list VS sequence table

1. Take the lead in bidirectional circular linked list

1.1 Advantages of linked list:

  1. Dynamic memory allocation : The linked list can dynamically allocate memory at runtime, so the number of nodes can be flexibly increased or decreased according to actual needs.
  2. Efficient insertion and deletion operations : Since the elements in the linked list do not have to be stored in contiguous memory space, the insertion and deletion operations are very efficient, only need to modify the pointer, without moving all subsequent elements.
  3. Unlimited size : The size of the linked list is not limited and can be expanded according to actual needs.

2. Disadvantages of linked list:

  1. Random access is inefficient : Since the elements in the linked list are not stored in order, the efficiency of random access to an element is relatively low, and it needs to be traversed from the beginning O(N).
  2. High storage space overhead : Each node of the linked list needs an additional pointer to point to the next node, which will increase the storage space overhead.
  3. Cache-unfriendly : Since the elements in the linked list are not stored in order, it may cause a cache miss and reduce access efficiency.

2. Sequence table

2.1 Advantages of sequence table

  1. Efficient random access : The random access efficiency of the subscript is high O(1).
  2. Efficient end-deletion and end-insertion : end-insertion and end-deletion of the sequence table do not need to move data, and the efficiency is high.
  3. Cache-friendly : Since the elements in the linked list are stored in order, the cache hit rate is high and the access efficiency is high.

2.2 Disadvantages of sequence table

  1. Partial insertion and deletion operations are inefficient : Since the elements in the sequence table are physically continuous, data needs to be moved when data is inserted into and deleted from the previous sequence table, which is inefficient O(N).
  2. Memory size is limited : When the memory of the sequence table is full, it needs to be expanded, which requires a cost, and the sequence table usually has memory waste.

end

If you have any suggestions and questions, or if there are any mistakes, I hope everyone can mention them.
I hope everyone can make progress with me in the future! !
If this article is useful to you, I hope you can give me a little like!

Guess you like

Origin blog.csdn.net/qq_55401402/article/details/130472263