Data Structure 02: Linear List [Sequential List + Linked List]

foreword

Reference book: Wang Dao PubMed "2024 Data Structure PubMed Review Guide"

Supporting video: 2.1_Definition and basic operations of linear table_哔哩哔哩_bilibili

Reference blog post: Easy-to-understand explanation of linked list-Knowledge

This is a lonely and brilliant blog post (→_→), I will update it secretly while no one is watching~

PubMed note arrangement: the content includes the basic definition and code of linear sequence table, single linked list, double linked list, circular linked list and static linked list~

Come on for the postgraduate entrance examination~ψ(._. )>!

Currently version 2 content:

Version 1: check information, test code, add comments~

Version 2: Merge the content of the linear linked list of blog posts, adjust the wording and layout, delete ambiguous content, and add mind maps~

 Screenshot source: "The Lonely and Brilliant God-Ghosts"


Definition and basic operation of linear table

Definition of linear table

A finite sequence with n (n≥0) data elements of the same data type, where n is the table length, and when n=0, the linear table is an empty table.

Features of linear tables

  1. The number of elements in the table is limited;
  2. The elements in the table have a logical sequence, and the elements in the table have their sequence; //Elements are sequences with front and rear relationships;
  3. The elements in the table are all data elements, and each element is a single element;
  4. The data types of the elements in the table are all the same, which means that each element occupies the same size of storage space; //It can be a basic data type, or other types of data types supported by C: C Data Type | Novice Tutorial (runoob . com)
  5. The elements in the table are abstract, that is, only the logical relationship between the elements is discussed, regardless of what the elements represent.

Basic operations of linear tables

Basically, it can be divided into four types: (1) create and destroy, (2) view table content, (3) look up table elements, (4) insert and delete these four types of operations~

  • (1-1) Initialist(&L): initialization table . Build an empty linear table;
  • (1-2) DestoryList(&L): destroy operation . Destroy the linear table and release the memory space occupied by the linear table L.
  • (2-1) Length(L): Find the length of the table . Returns the length of the linear list L, that is, the number of data elements in L.
  • (2-2) Empty(L): Empty operation is judged . Returns true if L is an empty list, otherwise returns false.
  • (2-3) PrintList(L): Output operation . Output all element values ​​in the linear list L in sequence.
  • (3-1) LocateElem(L,e): Find operation by value . Finds an element with the given key in the list L.
  • (3-2) GetElem(L,i): Bitwise search operation . Get the value of the element at position i in the list L.
  • (4-1) ListInsert(&L,i,e): insert operation . Inserts the specified element e at the i-th position in the list L.
  • (4-2) ListDelete(&L,i,&e): delete operation . Delete the element at the i-th position in the list L, and return the value of the deleted element with e.

Notice:

  • The symbol "&" represents a reference call in the C++ language, and the same effect can be achieved by using a pointer in the C language.
  • The implementation of basic operations depends on which storage structure is used, and the implementation of algorithms is also different for different storage structures.

Sequential representation of linear tables

Definition of sequence table

The sequential storage of linear tables is also called sequential tables. It uses a group of storage units with continuous addresses to sequentially store the data elements in the linear table, so that two logically adjacent elements are also physically adjacent.

Source of screenshot: Sequential storage structure (vector) of linear table (1) Coco's blog - CSDN blog

    Remarks on the sequence table:

  • The starting position is LOC (A), the starting bit order of the linear table element is 1, and the starting subscript of the array element is 0;
  • The size of the storage space occupied by each element is Sizeof(ElemType).

Features of the sequence table

  • The main feature of the sequence table is random access , that is, the specified element can be found within time O(1) through the first address and element number;
  • The storage density on the sequence table is high, and each node only stores data elements;
  • The logically adjacent elements of the sequential table are also physically adjacent, so insertion and deletion operations need to move a large number of elements;
  • It is inconvenient to expand the capacity of the sequence table .

    Notes on random access:

  • The storage position of each data element is different from the starting position of the linear table by a constant proportional to the bit sequence of the data element, therefore, any data element in the sequential table can be randomly accessed.

    Remarks on capacity expansion:

  • One-dimensional arrays can be statically allocated or dynamically allocated~
  • Static allocation, once the table space is full, the capacity cannot be expanded, and the entire table is useless~ To avoid this problem, you can have sufficient storage space at the beginning, but there is no way to reclaim the idle storage space, which will waste memory space~
  • Dynamic allocation, once the table space is full, the capacity can be expanded, but it is not very convenient ~ first open up a larger continuous storage space in the memory (sometimes a large storage space is not easy to find), and then migrate the data ~

Basic Operations of Sequence Tables

(1) Initialize Initialist (&L)

(1-1) Static allocation

The following are the key statements for static allocation to achieve initialization~

#define Maxsize 10          //定义线性表的最大长度,在内存中占用的连续空间大小为:Maxsize*sizeof(Elemtype)
typedef struct{
	ElemType data[Maxsize]; //顺序表的元素,此处假设元素类型名称为Elemtype(可替换为需要的类型,例如int),并以静态数组的方式实现此表,最多存放的数据个数为Maxsize
	int length;             //顺序表的当前长度
}SqList;                    //以上为顺序表的类型定义,并用SqList命名

The following are the steps to implement static allocation~

Note: The reference call function of "&" can be implemented in C++, but it will report an error in C~

#include <stdio.h>
#define Maxsize 10       //定义最大长度为10

typedef struct{
	int data[Maxsize];   //使用静态的“数组”存放数据
	int length;          //顺序表的当前长度
}SqList;                 //顺序表的类型定义

void InitList(SqList &L){         //初始化顺序表
	for(int i=0; i<Maxsize; i++)
		L.data[i]=0;              //将所有元素设置为默认初始值
	L.length=0;                   //顺序表初始长度为0
}
	
int main(){
	SqList L;   //声明一个顺序表
	InitList(L);//初始化顺序表
	return 0;
}

The whole program is actually divided into two parts:

  • typedef struct{} defines the length, data type, initial length, and sequence name of the sequence table in the form of an array;
  • void InitList(SqList &L) assigns initial values ​​to all elements of the array, which is not a necessary statement, but it can avoid dirty data pollution sequences that already exist in memory~

 (1-2) Dynamic allocation

The following are the key statements for dynamic allocation to realize initialization~

#define Initsize 10          //定义线性表的初始长度,在内存中占用的连续空间大小为:Initsize*sizeof(Elemtype)
typedef struct{
	ElemType data[Maxsize]; //顺序表的元素,此处假设元素类型名称为Elemtype(可替换为需要的类型,例如int),并以静态数组的方式实现此表,最多存放的数据个数为Maxsize
	int length;             //顺序表的当前长度
}SeqList;                    //以上为顺序表的类型定义,并用SeqList命名

 The following are the key statements for C to implement dynamic application of memory space~

  • malloc(): Open up a whole piece of storage space in the memory space, and return the pointer data of the initial address of the storage space; the size of the space requested by malloc = the storage length sizeof(Elemtype) x the initial storage capacity of the sequence table Number of data elements (InitSize)~
  • (Elemtype*): Forcibly convert the data type of the returned pointer data to the defined data type (can be replaced with Int, etc.) ~
L.data=(Elemtype*)malloc(sizeof(Elemtype)*InitSize);

 The following are the key statements for C++ to dynamically apply for memory space~

  • C++ new     is equivalent to c# malloc , applying for memory space~
  • C++ delete is equivalent to c# free , releasing memory space~
L.data=new Elemtype[InitSize];

In summary, the following steps to achieve dynamic allocation~

#include <stdlib.h>  //malloc、free的函数头文件
#define Initsize 10  //定义初始长度(首次设置的最大长度)为10

typedef struct{
	int *data;     //使用动态分配数组的指针
	int Maxsize;   //顺序表的最大容量
	int length;    //顺序表的当前长度
}SeqList;          //顺序表的类型定义

void InitList(SeqList &L){ 
	//用malloc函数申请一片连续的函数空间,将空间初始位置指针变为int类型并赋给了*L.data
    L.data=(int*)malloc(Initsize*sizeof(int));
	L.length=0;          //顺序表的当前长度为0
	L.Maxsize=Initsize;  //顺序表的当前最大容量为10
}
	
void IncreaseSize(SeqList &L,int len){         
	//增加动态数组的长度
	int *p=L.data;            //将data的值赋予P,即*P现在指向原内存空间的头地址
	L.data=(int*)malloc((L.Maxsize+len)*sizeof(int));
	for(int i=0; i<L.length; i++){ 
		L.data[i]=p[i];       //将数据复制到新区域
    }
	L.Maxsize=L.Maxsize+len;  //顺序表的最大长度增加len(5)
	free(p);                  //释放原来的内存空间
}

int main(){
	SeqList L;         //声明顺序表
	InitList(L);       //初始化顺序表
	IncreaseSize(L,5); //在顺序表中增加5个元素的空间
	return 0;
}

 (2) Bitwise search operation GetElem(L,i) and value search operation LocateElem(L,e)

 (2-1) Bitwise search operation GetElem(L,i)

The following is the key code for statically allocated bitwise search. In actual use, it is necessary to increase the "i" input out-of-bounds judgment to ensure the robustness of the program~ 

typedef struct{
	ElemType data[Maxsize];  //使用静态的“数组”存放数据
	int length;              //顺序表的当前长度
}SqList;                     //顺序表的类型定义

ElemType GetElem(SqList L, int i) {
    if (i < 1 || i > L.length) {
        // 处理越界情况,例如返回一个特定的错误值或抛出异常
        // 这里假设返回一个默认的错误值 -1
        return -1;
    }
    return L.data[i - 1];
}

The following is the key code for dynamic allocation bitwise search~

#define Initsize 10  //定义初始长度(首次设置的最大长度)为10

typedef struct{
	ElemType *data;  //使用动态分配数组的指针
	int Maxsize;     //顺序表的最大容量
	int length;      //顺序表的当前长度
}SeqList;            //顺序表的类型定义

ElemType GetElem(SeqList L, int i) {
    if (i < 1 || i > L.length) {
        // 处理越界情况,例如返回一个特定的错误值或抛出异常
        // 这里假设返回一个默认的错误值 -1
        return -1;
    }
    return L.data[i - 1];
}

Note: The data type will affect the bytes of the data. The pointer type Elemtype here needs to be consistent with the queried data type to ensure that the bytes read in the memory meet the requirements. You can refer to C data types | rookie tutorial ( runoob .com) ~

 The worst and average time complexity of bitwise search is O(1), and the data can be found immediately without executing loop statements~

 (2-2) Lookup operation by value LocateElem(L,e)

The following is the key code for dynamic allocation lookup by value~

#define Initsize 10  //定义初始长度(首次设置的最大长度)为10

typedef struct{
	ElemType *data;  //使用动态分配数组的指针
	int Maxsize;     //顺序表的最大容量
	int length;      //顺序表的当前长度
}SeqList;            //顺序表的类型定义

int LocateElem(SeqList L,ElemType e){
	for(int i=0;i<L.length;i++)  
		if(L.data[i]==e)
			return i+1;  //数组下标为i的元素值等于e,返回其位序i+1
	return 0;            //退出循环,说明查找失败
}

The above code needs to pay attention to:

  • Basic data types: int, char, double, float, etc. can replace Elemtype and directly use the operator "==" to compare~
  • But two identical structures cannot be directly compared - "struct a == struct b" cannot be compiled; if you want to compare, you can compare the components in the structure one by one, for example "a.data1 == b.data1 && a.data2 == b.data2”~

The worst and average time complexity of searching by value is O(n) , depending on the number of executions of the deepest loop L.data[i]==e, that is, the found element is at the last position (n) or in the middle position((n+1)/2) ~

(3) Insert ListInsert(&L,i,e) and delete ListDelete(&L,i,&e)

(3-1) Insert ListInsert(&L,i,e)

Note: (1) The following programs are also implemented in C++~(2) Take static allocation as an example~

#include <stdio.h>
#define Maxsize 10       //定义最大长度为10

typedef struct{
	int data[Maxsize];   //使用静态的“数组”存放数据
	int length;          //顺序表的当前长度
}SqList;                 //顺序表的类型定义

void InitList(SqList &L){         //初始化顺序表
	for(int i=0; i<Maxsize; i++)
		L.data[i]=0;              //将所有元素设置为默认初始值
	L.length=0;                   //顺序表初始长度为0
}

bool ListInsert(SqList &L,int i,int e){ 
	if(i<1||i>L.length+1)         //判断i的取值范围:i<1或i>现有表长+1,都不是合法输入
		return false;
	if(L.length>=Maxsize)         //判断现有表长是否超过储存空间,满足条件则不能插入
		return false;
	for(int j=L.length; j>=i; j++)
		L.data[j]=L.data[j-1];    //将第i=3个位置之后的数据后移一位
	L.data[i-1]=e;                //将第i=3个位置(data[2])赋予数据e=3
	L.length++;                   //顺序表长度+1
	return true;
}
	
int main(){
	SqList L;           //声明一个顺序表
	InitList(L);        //初始化顺序表
	ListInsert(L,3,3);  //在表L的第i=3个位置上插入数据e=3
	return 0;
}

The whole program is divided into three parts:

  • typedef struct{} defines the length, data type, initial length, and sequence name of the sequence table in the form of an array;
  • void InitList(SqList &L) assigns initial values ​​to all elements of the array, which is not a necessary statement, but it can avoid dirty data pollution sequences that already exist in memory~
  • Bool ListInsert() implements the data insertion function, the target bit sequence i and subsequent data are moved backwards (elements move from back to front in a loop), and e is placed in position i; and before inserting, it will judge whether the input data is legal, Whether it meets the definition of a linear table (logically adjacent and physically adjacent), and whether there is an insertable vacancy in the table~

The worst and average time complexity of the insertion operation is O(n) , which depends on the execution times of the deepest loop L.data[j]=L.data[j-1], that is, the original n elements (the worst ) or n/2 elements (average) are all moved back~

(3-2) Delete ListDelete(&L,i,&e)

#include <stdio.h>
#define Maxsize 10       //定义最大长度为10

typedef struct{
	int data[Maxsize];   //使用静态的“数组”存放数据
	int length;          //顺序表的当前长度
}SqList;                 //顺序表的类型定义

void InitList(SqList &L){         //初始化顺序表
	for(int i=0; i<Maxsize; i++)
		L.data[i]=0;              //将所有元素设置为默认初始值
	L.length=0;                   //顺序表初始长度为0
}

bool ListDelete(SqList &L,int i,int &e){ 
	if(i<1||i>L.length)           //判断i的取值范围:i<1或i>现有表长,都不是合法输入
		return false;
	e=L.data[i-1];                //将被删除的元素第i=3个位置(data[2])赋予数据e
	for(int j=i.length; j<L; j++)
		L.data[j-1]=L.data[j];    //将第i=3个位置之后的数据前移一位
	L.length--;                   //顺序表长度-1
	return true;
}
	
int main(){
	SqList L;              //声明一个顺序表
	InitList(L);           //初始化顺序表
	//省略代码:表中插入元素
	int e=-1;              //引用型参数,返回被删除的值
	if(ListDelete(L,3,e))  //删除表L的第i=3个位置的元素,并返回该元素值
		print("已删除第3个元素,删除元素值为=%d\n",e);
	else
		print("位序不合法,删除失败\n");
	return 0;
}

The whole program is divided into four parts:

  • typedef struct{} defines the length, data type, initial length, and sequence name of the sequence table in the form of an array;
  • void InitList(SqList &L) assigns initial values ​​to all elements of the array, which is not a necessary statement, but it can avoid dirty data pollution sequences that already exist in memory~
  • Bool ListDelete() implements the data deletion function, puts the data at position i into e, and the data after the target bit sequence i moves forward in sequence (elements move from front to back in a loop); and before inserting, it will judge whether the input data is legal or not. Satisfy the definition of a linear table (logically adjacent and physically adjacent) ~
  • ListDelete(&L,i,&e) remember to add the reference symbol '&', indicating that the value is assigned to e in this code, not to other variables with the same name in memory e~

 The worst and average time complexity of the deletion operation is O(n) , which depends on the execution times of the deepest loop L.data[j-1]=L.data[j], that is, the original n elements (the worst ) or n/2 elements (average) are all moved forward~


Single list

Definition of singly linked list

The linked storage of the linear list is also called the singly linked list, which refers to storing the data elements in the linear list through a group of arbitrary storage units. In order to establish a linear relationship between data elements, for each linked list node, in addition to storing the information of the element itself, it is also necessary to store a pointer to its successor.

Features of Singly Linked List

(1) To solve the shortcoming that the sequential table needs a large number of continuous storage units, but the additional pointer field of the singly linked list also has the shortcoming of wasting space;

(2) Since the elements of the singly linked list are discretely distributed in the storage unit, the singly linked list is a non random access storage structure, that is, a specific node in the list cannot be directly found. When searching for a node, it is necessary to traverse from the header of the table and search in sequence.

  • Maybe because the single-linked list is not stored continuously, it means that it is not convenient to search by bits~

(3) Usually, a head pointer can be used to identify a singly linked list, such as singly linked list L, when the head pointer is NULL, it means an empty list. In addition, for the convenience of operation, a node is added before the first node of the singly linked list, which is called the head node .

  • The single linked list of the leading node is determined to be empty: head→next==null;
  • The cyclic singly linked list of the leading node is judged to be empty: head→next==head;
  • The headless node singly linked list is determined to be empty: head==null.

Basic operation of singly linked list

(1) Initialize InitList(&L)

(1-1) Single linked list node type description

1 The first 4 lines define the node structure: data field + pointer field, and the last 2 lines of typedef are to simplify the variable name and increase the readability of the program~

struct LNode{           //定义单链表中结点的类型
	ElemType data;      //数据域:每个结点存放一个数据元素
	struct LNode *next; //指针域:指针指向下一个结点
};                       

typedef struct LNode LNode;     //typedef类型定义,将结点命名为LNode
typedef struct LNode *Linklist; //typedef类型定义,指针命名为*LinkList

   Equivalent to the following code~

typedef struct LNode{   //定义单链表中结点的类型
	ElemType data;      //数据域:每个结点存放一个数据元素
	struct LNode *next; //指针域:指针指向下一个结点
}LNode,*LinkList;       //typedef类型定义,将结点命名为LNode 指针命名为*LinkList

Remarks: Here, LNode *L or LinkList L can both represent the creation of a pointer to the head node of a singly linked list, with the same effect; the former is emphasized as a node, and the latter is emphasized as a linked list~

2 Memory application space, adding new node operations~

#include <stdlib.h> 
LNode *p = (LNode *)malloc(sizeof(LNode));
  • malloc(): In the header file <stdlib.h>, the purpose is to open up a whole piece of storage space in the memory space, and return the pointer LNode *p of the initial address of the storage space; the space size requested by malloc = occupied by a single node The storage length sizeof(LNode)~
  • (LNode *): Forcibly convert the data type of the returned pointer data to the defined data type~

(1-2) Initialize the singly linked list of headless nodes

Basic idea: build a singly linked list without data elements, clear the content, the same below~

#include <stdio.h>

typedef struct LNode{        //定义单个结点的结构
	ElemType data;      
	struct LNode *next; 
}LNode,*LinkList;       

bool InitList(LinkList &L){
	L = NULL;                //空表,暂时没有结点,清空脏数据;需要头文件<stdio.h>
	return true;
}

void test(){
	LinkList L;              //声明一个指向单链表的指针(不创建结点)
	InitList(L);             //初始化一个空表
}

(1-3) Initialize the singly linked list of the leading node

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

typedef struct LNode{        //定义单个结点的结构
	ElemType data;      
	struct LNode *next; 
}LNode,*LinkList;       

bool InitList(LinkList &L){
	L = (LNode *)malloc(sizeof(LNode)); //分配头节点;需要头文件<stdlib.h> 
	if(L==NULL)                //内存不足,分配失败;需要头文件<stdio.h>
		return false;
	L->next = NULL;            //空表,仅有头节点,暂时没有数据结点
	return true;
}

void test(){
	LinkList L;              //声明一个指向单链表的指针(不创建结点)
	InitList(L);             //初始化一个空表,带头节点
}

(2) Insert ListInsert(&L,i,e) and delete ListDelete(&L,i,&e)

(2-1) Insert ListInsert(&L,i,e)

(2-1-1) Take the lead node pointer, insert in bit order

Basic idea: Traverse from the beginning to the desired position, and then perform the insertion operation. It is necessary to modify the two chains before and after the insertion node~

Source of screenshot:  Easy to understand linked list - Zhihu (zhihu.com)

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

typedef struct LNode{        //定义单个结点的结构
	ElemType data;      
	struct LNode *next; 
}LNode,*LinkList;       

bool InitList(LinkList &L){
	L = (LNode *)malloc(sizeof(LNode)); //分配头节点;需要头文件<stdlib.h> 
	if(L==NULL)                //内存不足,分配失败;需要头文件<stdio.h>
		return false;
	L->next = NULL;            //空表,仅有头节点,暂时没有数据结点
	return true;
}

bool ListInsert(LinkList &L,int i,ElemType e){
	if(i<1)                    //输入的位置如果<1,为非法输入
		return false;
	LNode *p;                  //创建指针p,任务为寻找插入的位置
	int j=0;
	p = L;                     //首先将p的位置定到为头节点L的位置
	while(p!=NULL && j<i-1){   //循环找到第i-1个结点
		p=p->next;
		j++;
	}
	if(p==NULL)                //i值不合法
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));  //申请空间,创建结点s
	s->data = e;               //将e存到空间内,结点为s
	s->next = p->next;         //结点s的next指针与结点p的next指针指向同一个位置(也就是i+1的位置)
	p->next = s;               //结点p的next指针指向s
	return true;
}

void test(){
	LinkList L;              //声明一个指向单链表的指针(不创建结点)
	InitList(L);             //初始化一个空表(带头节点)
	ListInsert(L,i,e);       //在第i个位置插入元素e(带头节点)
}

Well, speaking of it, the function realized is as shown in the figure below, insert e into the position of the specified position i (shown as 1)~

Screenshot Source: Wangdao Entrance Examination Supporting Courseware 2.3.2 

Notice:

  • For the insertion operation, first, s needs to point to the next data (a1 in the figure), and then p points to s (while disconnecting the pointer to a1), as shown in the above figure~
  • If the order is reversed, first let p point to s (and disconnect the pointer of a1 at the same time), then e will point to itself because it cannot find the location of a1 in the memory, as shown below~

Screenshot Source: Wangdao Entrance Examination Supporting Courseware 2.3.2 

Worst time complexity/average time complexity O(n) , for example, inserting data at the end of the table, the complexity of traversing to the last node is O(n), and the complexity of inserting itself is O(1)~

(2-1-2) Headless node pointer, inserted in bit order

Basic idea: Compared with the leading node, the difference is that the operation of the first node needs to be specially processed (because the forward node of the first data element cannot be found~)

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

typedef struct LNode{        //定义单个结点的结构
	ElemType data;      
	struct LNode *next; 
}LNode,*LinkList;       

//
bool InitList(LinkList &L){
	L = NULL;                //空表,暂时没有结点,清空脏数据;需要头文件<stdio.h>
	return true;
}

bool ListInsert(LinkList &L,int i,ElemType e){
	if(i<1)                    //输入的位置如果<1,为非法输入
		return false;
	if(i==1){                  //输入的位置如果=1,因为找不到前向结点需要特殊处理
		LNode *s = (LNode *)malloc(sizeof(LNode));  //申请空间,创建结点s
		s->data = e;               //将e存到空间内,结点为s
		s->next = L;               //结点s的next指针与原头结点L的指针指向同一个位置(也就是原表的第1个数据的位置)
		L = s;                     //将头节点L指向结点s
		return true;
	}
	LNode *p;                  //创建指针p,任务为寻找插入的位置
	int j=0;
	p = L;                     //首先将p的位置定到为头节点L的位置
	while(p!=NULL && j<i-1){   //循环找到第i-1个结点
		p=p->next;
		j++;
	}
	if(p==NULL)                //i值不合法
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));  //申请空间,创建结点s
	s->data = e;               //将e存到空间内,结点为s
	s->next = p->next;         //结点s的next指针与结点p的next指针指向同一个位置(也就是i+1的位置)
	p->next = s;               //结点p的next指针指向s
	return true;
}

void test(){
	LinkList L;              //声明一个指向单链表的指针(不创建结点)
	InitList(L);             //初始化一个空表(无头节点)
	ListInsert(L,i,e);       //在第i个位置插入元素e(无头节点)
}

The processing of the first node is more troublesome, so the subsequent linked list, unless otherwise specified, is regarded as a linked list with a leading node pointer~

(2-1-3) After the specified node is inserted

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

bool InsertNextNode(LNode *p,ElemType e){
	if(p==NULL)                //p值不合法
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));  //申请空间,创建结点s
	if(s==NULL)                //内存分配失败
		return false;
	s->data = e;               //将数据e存到结点s中
	s->next = p->next;         //结点s的next指针与结点p的next指针指向同一个位置(也就是i+1的位置)
	p->next = s;               //结点p的next指针指向s
	return true;
}

The steps here are repeated with the bitwise operation: the bitwise operation can directly perform the encapsulated post-insertion operation "return InsertNextNode(p,e);"~

Worst time complexity/average time complexity O(1)

(2-1-4) Specify node forward insertion

Basic idea: first perform the operation of inserting node s after node p, and then exchange the data of nodes p and s (specifically, transfer the data of p to the backward node s of p, and then transfer the data of e Input to the forward node p of s) such a big move in the dark~

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

bool InsertPriorNode(LNode *p,ElemType e){
	if(p==NULL)                //p值不合法
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));  //申请空间,创建结点s
	if(s==NULL)                //内存分配失败
		return false;
	s->next = p->next;         //结点s的next指针与结点p的next指针指向同一个位置(也就是i+1的位置)
	p->next = s;               //结点p的next指针指向结点s
	s->data = p->data;         //结点p的data搬运到结点s
	p->data = e;               //将数据e存到结点p中
	return true;
}

Worst time complexity/average time complexity O(1) 

(2-2) Delete ListDelete(&L,i,&e)

(2-2-1) Delete in bit order

Basic idea: first point the forward node pointer of p to the backward node of p, and then delete p~

 Source of screenshot:  Easy to understand linked list - Zhihu (zhihu.com)

#include <stdio.h>

bool ListDelete(LinkList &L,int i,ElemType &e){
	if(i<1)                    //输入的位置如果<1,为非法输入
		return false;
	LNode *p;                  //创建指针p,任务为寻找插入的位置
	int j=0;
	p = L;                     //首先将p的位置定到为头节点L的位置
	while(p!=NULL && j<i-1){   //循环找到第i-1个结点
		p=p->next;
		j++;
	}
	if(p==NULL)                //i值不合法
		return false;
	LNode *q=p->next;          //令q指向被删除结点
	e = q->data;               //用e返回被删除元素的值
	p->next = q->next;         //将*q结点从链中“断开”
	free(q);                   //释放结点存储空间
	return true;
}

Worst time complexity/average time complexity O(n) , for example, inserting data at the end of the table, the complexity of traversing to the last node is O(n), and the complexity of inserting itself is O(1).

(2-2-2) Delete the specified node

Basic idea: execute the operation of inserting node q after node p, exchange the data of p and q, p points to the next node of q, and finally delete node q, which is such a big move in the dark~

#include <stdio.h>

bool DeleteNote(LNode *p){
	if(p==NULL)                //p值不合法
		return false;
	LNode *q=p->next;          //令*q指向*p的后向结点
	p->data = p->next->data;   //和后继结点交换数据域
	p->next = q->next;         //将*q结点从链中“断开”
	free(q);                   //释放后继结点存储空间
	return true;
}

But this idea cannot solve the problem of deleting the last node (q will point to null, and there will be a null pointer error), the safest way is to traverse the previous pointer of q from the beginning~

(3) Bitwise search operation GetElem(L,i) and value search operation LocateElem(L,e)

(3-1) Bitwise search operation GetElem(L,i)

Basic idea: traverse from the beginning to the position that needs to be found~

#include <stdio.h>

bool GetElem(LinkList L,int i){
	if(i<1)                    //输入的位置如果<1,为非法输入
		return false;
	LNode *p;                  //创建指针p,任务为寻找插入的位置
	int j=0;
	p = L;                     //首先将p的位置定到为头节点L的位置
	while(p!=NULL && j<i){     //循环找到第i个结点
		p=p->next;
		j++;
	}
	return p;
}

This code can also be encapsulated, directly calling "LNode *p = GetElem(L,i-1);"~

Worst time complexity/average time complexity O(n) 

(3-2) Lookup operation by value LocateElem(L,e)

Basic idea: traverse from the beginning to the position that needs to be found~

#include <stdio.h>

bool LocateElem(LinkList L,ElemType e){
	LNode *p = L->next;            //创建指针p,任务为寻找插入的位置,起始位置在第1个结点后
	while(p!=NULL && p->data !=e)  //循环找到数据域为e的结点
		p=p->next;
	return p;
}

illustrate:

  • Basic data types: int, char, double, float, etc. can replace Elemtype and directly use the operator "==" to compare~
  • But two identical structures cannot be directly compared - "struct a == struct b" cannot be compiled; if you want to compare, you can compare the components in the structure one by one, for example "a.data1 == b.data1 && a.data2 == b.data2”~

Worst time complexity/average time complexity O(n) 

The following is the derivative operation of the search: Find the length of the table~

(3-3) Find the table length Length(L)

#include <stdio.h>

int Length(LinkList L){
	int len = 0;             //统计表长
	LNode *p = L;            //创建指针p,任务为寻找插入的位置,起始位置在第1个结点后
	while(p->next !=NULL){   //循环遍历结点
		p=p->next;
		len++;
	}
	return len;
}

Worst time complexity/average time complexity O(n) 

(4) Create a singly linked list

(4-1) Tail insertion method List_TailInsert(L)

Basic idea: initialization → establishment of node pointer, tail pointer → perform post-insertion data operation at the tail pointer → move the tail pointer backward~

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

typedef struct LNode{        //定义单个结点的结构
	ElemType data;      
	struct LNode *next; 
}LNode,*LinkList;    

LinkList List_TailInsert(LinkList &L){
	int x;                              //设置链表的数据类型(ElemType)为整型
	L = (LNode *)malloc(sizeof(LNode)); //建立头节点;需要头文件<stdlib.h> 
	LNode *s,*r=L;                      //声明两个指针s、r均指向头节点,r为表尾指针
	scanf("%d",&x);                     //用户键入结点的值(整型)
	while(x!=9999){                     //用户键入结点的值为9999时,退出插入操作
		s = (LNode *)malloc(sizeof(LNode));   //建立s结点
		s->data=x;        //s结点的数据域填入x
		r->next=s;        //r指针指向s结点(建立s结点与前结点的链)
		r=s;              //r指针后移到s结点的位置(更新尾节点位置)
		scanf("%d",&x);
	}
	r->next=NULL;         //尾结点指针置空
	return L;
}

Worst time complexity/average time complexity O(n)  

(4-2) Head insertion method List_HeadInsert(L)

Basic idea: initialization → establishment of head pointer, tail pointer → perform post-insertion data operation at the head pointer ~

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

typedef struct LNode{        //定义单个结点的结构
	ElemType data;      
	struct LNode *next; 
}LNode,*LinkList;    

LinkList List_HeadInsert(LinkList &L){
	LNode *s,*r=L;                      //声明指针s
	int x;                              //设置链表的数据类型(ElemType)为整型
	L = (LNode *)malloc(sizeof(LNode)); //建立头节点;需要头文件<stdlib.h> 
	L->next=NULL;                       //尾结点数据置空
	scanf("%d",&x);                     //用户键入结点的值(整型)
	while(x!=9999){                     //用户键入结点的值为9999时,退出插入操作
		s = (LNode *)malloc(sizeof(LNode));   //建立s结点
		s->data=x;        //s结点的数据域填入x
		s->next=L->next;  //s结点的指针指向L结点的指针(同时指向原表第1个数据)
		L->next=s;        //L结点指向s结点(建立单链)
		scanf("%d",&x);
	}
	return L;
}

Worst time complexity/average time complexity O(n)  

Note: The data stored in the head insertion method is opposite to the actual input order, and the reverse effect will be achieved, which is the test point~ 


double linked list

Definition of double linked list

There are two pointers prior and next in the double-linked list node (a prior pointer pointing to its predecessor is added to the node of the single-linked list), pointing to its predecessor node and successor node respectively.

Screenshot source:  How to design a double linked list in a java project- Programming Language- Yisuyun

Features of double linked list

1. A precursor node is added to facilitate reverse retrieval; the time complexity of insertion and deletion operations is only O(1);

2. Search by value and search by bit are the same as singly linked list.

Basic operation of double linked list

(1) Initialize InitDLinkList(&L)

Basic idea: build a singly linked list without data elements, take the lead node ~ named D is the abbreviation of double ~

#include <stdio.h>
#include <stdlib.h> 
 
typedef struct DNode{          //定义单个结点的结构
	ElemType data;      
	struct DNode *prior; 
	struct DNode *next; 
}DNode,*DLinkList;       

bool InitDLinkList(DLinkList &L){
	L = (DNode *)malloc(sizeof(DNode));   //分配头节点;需要头文件<stdlib.h> 
	if(L==NULL)                //内存不足,分配失败;需要头文件<stdio.h>
		return false;
	L->prior= NULL;            //头节点的prior指向null
	L->next = NULL;            //头节点之后暂时没有数据结点	
	return true;
}
 
void test(){
	DLinkList L;              //声明一个指向双链表的指针(不创建结点)
	InitDLinkList(L);         //初始化一个空表,带头节点
}

(2) Insert ListInsert(&L,i,e) and delete ListDelete(&L,i,&e) 

(2-1) Insert InsertNextNode after the specified node

Basic idea: node s needs to be connected with two chains before and after two elements, and a total of four chains are modified~

Screenshot source: linux kernel linked list: bidirectional circular linked list |

The screenshot itself is a circular double-linked list, and the insertion and deletion operations are the same as those of the non-cyclic double-linked list when it is not at the end of the list (line 9 of the following code, it is judged whether node p has a successor node, and the circular double-linked list does not need to be considered)  ~

Notice:

  1. Modify the order of pointers when inserting; first write the backward chain in each direction, and then break the chain of the original table~
  2. Determine whether the p node has a successor node (whether it is a data element at the end of the table); if so, there is no need for NULL to point to the previous node~
bool InsertNextNode(DNode *p,DNode *s){
	if(p==NULL)                //p值不合法
		return false;
	DNode *s = (DNode *)malloc(sizeof(DNode));  //申请空间,创建结点s
	if(s==NULL)                //内存分配失败
		return false;
	s->data = e;               //将数据e存到结点s中
	s->next = p->next;         //结点s的next指针与结点p的next指针指向同一个位置(也就是i+1的位置)
	if(p->next != NULL)        //判断p结点的下一个结点不为空
		p->next->prior = s;    //结点p的下一个结点的prior指针指向s
	s->prior = p;              //结点s的prior指针指向p
	p->next = s;               //结点p的next指针指向s
	return true;
}

 (2-2) Delete the specified node DeleteNextNode

Basic idea: Deleting node s needs to modify 2 chains~

Note: It is necessary to judge whether the node s itself and the nodes before and after it are empty~

Screenshot source: linux kernel linked list: bidirectional circular linked list |

The screenshot itself is a circular double-linked list, and the insertion and deletion operations are the same as those of the non-cyclic double-linked list when it is not at the end of the list (line 8 of the following code, it is judged whether node p has a successor node, and the circular double-linked list does not need to be considered)  ~

bool DeleteNextNode(DNode *p){
	if(p==NULL)                //p值不合法
		return false;
	DNode *q=p->next;          //令*q指向*p的后向结点
	if(q==NULL)                //q没有后继
		return false;	
	p->next = q->next;         //p的next指针指向后向结点
	if(q->next!=NULL)
		q->next->prior = p;    //将*q结点从链中“断开”
	free(q);                   //释放后继结点存储空间
	return true;
}

 The following is the derivative operation of deletion: Destroy the table~

 (2-3) Destroy the double linked list DestoryList

Basic idea: Encapsulate the operation of deleting nodes, recycle → release the head node, clear the head pointer~

void DestoryList(DLinklist &L){
	while(L->next != NULL)
		DeleteNextNode(L);
	free(L); //释放头节点
	L=null;  //头指针指向null
}

circular linked list

Definition of circular linked list

The difference between a circular singly linked list and a singly linked list is that the pointer of the last node in the list is not NULL, but instead points to the head node, so that the entire linked list forms a ring.

Diagram: Circular Linked List of Linked Storage Structure (Revised Edition) - Brother Wu Learning Algorithm's Blog - CSDN Blog

Screenshot Source: Diagram: Circular Linked List of Linked Storage Structure (Revised Edition)_Brother Wu Learning Algorithm Blog-CSDN Blog

In a circular double linked list, the prior pointer of the head node points to the tail node, and the next pointer of the tail node points to the head node.

The characteristics of circular linked list

1 In a singly linked list, the entire linked list can only be traversed sequentially from the head node, while a circular singly linked list can traverse the entire linked list starting from any node in the list. Sometimes the head pointer is not set for the circular singly linked list but only the tail pointer is set to make the operation more efficient.

  • Many operations are at the head or tail of the linked list. If the head pointer is set, the complexity of O(n) is required to insert elements at the end of the table; if the tail pointer r is set, r->next is the head pointer. Only O(1) complexity is required for inserting elements at the head or tail of the list.

2 In the circular singly linked list, the next field of the tail node *r points to L, so there is no node in the table whose pointer field is NULL. Therefore, the null condition of the circular singly linked list is not whether the pointer of the head node is empty, but is whether it is equal to the head pointer.

  • Circular linked list judgment empty condition: L->next==L;
  • Circular linked list judgment table end node condition: p->next==L

Basic operation of circular linked list

(1) Initialization

(1-1) Loop singly linked list InitList(LinkList &L)

Basic idea: build a circular singly linked list without data elements, take the lead node~

Note: There is no empty element in the circular linked list. When the data element is empty, the pointer of the head node points to itself~

#include <stdio.h>
#include <stdlib.h> 
 
typedef struct LNode{          //定义单个结点的结构
	ElemType data;      
	struct LNode *next; 
}LNode,*LinkList;       

bool InitList(LinkList &L){
	L = (LNode *)malloc(sizeof(LNode));   //分配头节点;需要头文件<stdlib.h> 
	if(L==NULL)                //内存不足,分配失败;需要头文件<stdio.h>
		return false;
	L->next = L;               //头节点之后暂时没有数据结点,注意循环链表此处不是null
	return true;
}

(1-2) Circular double linked list InitDLinkList(DLinklist &L)

Basic idea: build a circular double-linked list without data elements, take the lead node~

Note: The circular linked list has no empty elements. When the data element is empty, the two nodes of the head node point to itself... the actual effect is somewhat similar to...

Screenshot source: Keep Self-Care Shoulder Stretch 

#include <stdio.h>
#include <stdlib.h> 
 
typedef struct DNode{          //定义单个结点的结构
	ElemType data;      
	struct DNode *prior; 
	struct DNode *next; 
}DNode,*DLinkList;       

bool InitDLinkList(DLinkList &L){
	L = (DNode *)malloc(sizeof(DNode));   //分配头节点;需要头文件<stdlib.h> 
	if(L==NULL)                //内存不足,分配失败;需要头文件<stdio.h>
		return false;
	L->prior= L;               //头节点的prior指向自己,构成循环
	L->next = L;               //头节点之后暂时没有数据结点	
	return true;
}
 
void test(){
	DLinkList L;              //声明一个指向双链表的指针(不创建结点)
	InitDLinkList(L);         //初始化一个空表,带头节点
}

static linked list

Definition of static linked list

The static linked list uses an array to describe the linked storage structure of the linear list. The node also has a data field data and a pointer field next. Unlike the pointer in the linked list mentioned above, the pointer here is the relative address of the node (array subscript ), also known as a cursor.

Like the sequential list, the static linked list must also pre-allocate a continuous space.

The static linked list is marked with next==-1 as its end; the idle cursor can be marked as -2 to distinguish the dirty data of the memory~

Screenshot Source: Static Linked List - Sogou Encyclopedia

 Screenshot source: https://www.cnblogs.com/dongry/p/10210609.html

Characteristics of static linked list

1 The insertion and deletion operations of the static linked list are the same as those of the dynamic linked list, only need to modify the pointer, without moving the element;

2 Can not access randomly, can only search backwards from the first node in turn;

3 The capacity is fixed and cannot be changed;

4 It is not as convenient as a single-linked list. It is actually used in low-level languages ​​that do not support pointers in the early days, or in scenarios where the number of data elements is almost constant [the file allocation table FAT of the operating system]. // So I don't take the test very often~

Basic operation of static linked list

(1) Initialize InitList(&L)

(1-1) Static linked list node type description SLinkList

Basic ideas: Lines 3-6 define the node structure: data field + pointer field, lines 8-10 allocate continuous storage space for the static linked list~

#include <stdio.h>
#define MaxSize 10      //预定义静态链表的最大长度
struct Node{            //定义静态链表中结点的类型
	ElemType data;      //数据域:每个结点存放一个数据元素
	int next;           //指针域:下一个元素的数组下标
};  

void test(){
	struct Node a[MaxSize]; //数组a作为静态链表
}

Comparison between sequence list and linked list

Sequence list vs Linked list comparison
project linear sequence table linear linked list Remark
logical structure linear table linear table the same~
storage structure

sequential storage

chain storage

1 Continuous storage, support random access

2 high storage density

3 It is inconvenient to allocate space and change capacity

1 Discrete storage, does not support random access

2 low storage density

3 It is convenient to allocate discrete space and change capacity

basic operation

create:

1 Need to pre-allocate a large continuous space;

2. It is inconvenient to expand (static cannot be changed, dynamic can be changed but not very convenient).

create:

1 Declare the head pointer, add the head node (the head node is not necessary), and you can create it;

2 to facilitate expansion.

Linked list is more flexible~

destroy:

1 length=0 logically marks an empty table;

2 Static allocation: the system automatically reclaims space; dynamic allocation: free(L.data);.

destroy:

1 Loop to delete nodes.

Sequence table recycling is faster~ (Although generally no one is better than this...)

Insertion and deletion (bitwise):

1 bitwise lookup element O(1);

2 All elements move backward and forward O(n);

3 Overall complexity O(n).

Insertion and deletion (bitwise):

1 Traverse to find elements O(n);

2 Insert and delete elements O(1);

3 Overall complexity O(n).

The linked list is faster~

//Although the complexity is equal, it takes less time to find elements than to move them~

Find:

1 bitwise lookup element O(1);

2 Find elements by value O(n), if it is an ordered list, the time is O(log2n)~

Find:

1 Find element by bit O(n);

2 Find element by value O(n)~

The sequence table is faster~

end

If the blog post is vague or wrong, please leave a message for discussion and criticism~

Code words are not easy, if it helps, can you give a thumbs up to support the blogger? Thanks~(●'◡'●)

Guess you like

Origin blog.csdn.net/weixin_42789937/article/details/130831265