To understand the "linked list" in one breath, rely on these 20+ pictures

 

Seriously, anyone talking about how to get started with embedded software? What do I need to learn? My almost unanimous answer is: C language and data structures in software plus some simple and commonly used algorithms, these need to be learned well.

Through my own review and learning, I also write some basic data structure knowledge, draw more pictures, less BB, and learn data structure with everyone

Sequential storage and chain storage

Array-sequential storage

Array is a data structure of sequential storage, but it has a lot to do. Its flexible use brings a lot of convenience to our program design;

However, the biggest disadvantage of the array is that we need to move a large number of elements when inserting and deleting, so a lot of time is consumed, and the redundancy is unacceptable.

Take the example of inserting an element into a C language array. When we need to {1,2,3,4}insert an'A' at the position after the first element of an array , what we need to do is:

  1. Move the entire element after the first element back to form a new array{1,2,2,3,4}

  2. Then replace the element at the second element position with the element'A' we need

  3. In the end, our expectations are formed, which requires a lot of operations.

As can be seen from the figure above, the use of arrays has two major disadvantages:

  1. There are many elements that need to be moved to insert and delete operations, which wastes computing power.

  2. Enough space must be provided for the array, otherwise there is a risk of overflow.

Linked list-chain storage

Because of these shortcomings of arrays, the idea of ​​linked lists naturally arises.

The linked list cleverly simplifies the above content through the discontinuous storage method, adaptive memory size, and flexible use of pointers.

The basic thinking of the linked list is to use the setting of the structure to open up an extra memory space as a pointer. It always points to the next node. The nodes are connected in series through the NEXT pointer to form a linked list.

Among them, DATA is a custom data type, NEXT is a pointer to the next linked list node, and by accessing NEXT, we can guide us to visit the next node of the linked list.

For a series of nodes, a linked list is formed as shown below:

The above-mentioned insert and delete operation only needs to modify the area pointed to by the pointer, and does not require a lot of data movement operations. As shown below:

Compared with arrays, linked lists solve the disadvantages of arrays that are inconvenient to move, insert, and delete elements. However, correspondingly, linked lists pay a greater memory sacrifice in exchange for the realization of these functions.

Linked list overview

Including singly linked lists, double linked lists, and circular singly linked lists, the functions in practical applications are different, but the implementation methods are similar.

  • The single-linked watch is like the American men’s basketball team, passed down from generation to generation;

  • The double-linked watch is like Chinese men’s football. You pass the ball to me, I pass it to you, and finally to the goalkeeper;

  • The circular linked list is like the Chinese men’s basketball team. The torch was passed from Yao Ming to Wang Zhizhi, Wang Zhizhi passed to Yi Jianlian, and now Yi Jianlian was injured and passed to Yao Ming.

Single list

Single linked list concept and simple design

A singly linked list is a chained access data structure. The data in the linked list is represented by nodes, and each node is composed of elements and pointers.

The element represents the image of the data element, which is the storage unit for storing data; the pointer indicates the storage location of the subsequent element, which is the address data that connects each node.

In 结点的序列the linear form is referred to as linear list expressed, i.e. single chain, single list is accessed chain structure.

For each node of the linked list, we use the structure to design, the main contents are:

Among them, the DATA data element can be any data format you want to store, which can be an array, an int, or even a structure (this is the legendary structure sleeve structure)

NEXT is a pointer, which represents an area that can be pointed to, usually used to point to the next node. The end of the linked list NEXT points to NULL (empty), because there is no space to point to at the end

Therefore, for the node definition of a singly linked list, the code can be described as:

//定义结点类型
typedef struct Node {
    int data;       //数据类型,你可以把int型的data换成任意数据类型,包括结构体struct等复合类型
    struct Node *next;          //单链表的指针域
} Node,*LinkedList;  
//Node表示结点的类型,LinkedList表示指向Node结点类型的指针类型

Initialization of the linked list

Initialization mainly completes the following tasks: create a pre-node of a singly linked list and gradually add nodes later, generally refers to the space for applying for a node, and assigning a null value (NULL) to a node at the same time, the code can be expressed as:

LinkedList listinit(){
    Node *L;
    L=(Node*)malloc(sizeof(Node));      //开辟空间 
    if(L==NULL){                     //判断是否开辟空间失败,这一步很有必要
        printf("申请空间失败");
        //exit(0);                  //开辟空间失败可以考虑直接结束程序
    }
    L->next=NULL;       //指针指向空
}

Note: It is necessary to judge whether the space opening fails, otherwise the space opening fails due to unknown circumstances in the production, and the code is still being executed, and the consequences will be unimaginable. Therefore, it is necessary to develop such a judgment.

Head insertion method to create a singly linked list

Use the pointer to point to the next node element to create one by one, and the final result obtained using the header insertion method is in reverse order. as the picture shows:

Starting from an empty list, a new node is generated, and the read data is stored in the data field of the new node, and then the new node is inserted into the head of the current linked list, that is, after the head node.

//头插法建立单链表
LinkedList LinkedListCreatH() {
    Node *L;
    L = (Node *)malloc(sizeof(Node));   //申请头结点空间
    L->next = NULL;                      //初始化一个空链表
  
    int x;                         //x为链表数据域中的数据
    while(scanf("%d",&x) != EOF) {
        Node *p;
        p = (Node *)malloc(sizeof(Node));   //申请新的结点
        p->data = x;                     //结点数据域赋值
        p->next = L->next;     //将结点插入到表头L-->|2|-->|1|-->NULL
        L->next = p;
    }
    return L;
}

Tail insertion method to create a singly linked list

The figure shows the creation process of the tail insertion method.

In the linked list generated by the header interpolation, the order of the nodes is inconsistent with the order of the input data. If you want the order of the two to be consistent, you need the tail interpolation.

This method is to insert new nodes one by one into the end of the current linked list. For this reason, a tail pointer r must be added to make it always point to the end node of the current linked list. The code is as follows:

//尾插法建立单链表
  
LinkedList LinkedListCreatT() {
    Node *L;
    L = (Node *)malloc(sizeof(Node));   //申请头结点空间
    L->next = NULL;                  //初始化一个空链表
    Node *r;
    r = L;                          //r始终指向终端结点,开始时指向头结点
    int x;                         //x为链表数据域中的数据
    while(scanf("%d",&x) != EOF) {
        Node *p;
        p = (Node *)malloc(sizeof(Node));   //申请新的结点
        p->data = x;                     //结点数据域赋值
        r->next = p;            //将结点插入到表头L-->|1|-->|2|-->NULL
        r = p;
    }
    r->next = NULL;
    return L;
}

Traverse singly linked lists such as printing and modifying

Starting from the head of the linked list, each element is accessed gradually backward, which is called traversal.

For traversal operations, we can derive many commonly used data operations, such as querying elements, modifying elements, obtaining the number of elements, printing the entire linked list data, and so on.

The idea of ​​traversal is extremely simple. You only need to create a node pointing to the linked list L, and then search backwards along the linked list L one by one. The code is as follows:

//便利输出单链表
void printList(LinkedList L){
    Node *p=L->next;
    int i=0;
    while(p){
        printf("第%d个元素的值为:%d\n",++i,p->data);
        p=p->next;
    }
}

For element modification operations, the following is the code implementation:

//链表内容的修改,在链表中修改值为x的元素变为为k。
LinkedList LinkedListReplace(LinkedList L,int x,int k) {
    Node *p=L->next;
    int i=0;
    while(p){
        if(p->data==x){
            p->data=k;
        }
        p=p->next;
    }
    return L;
}

A simple traversal design function only needs void and no parameters, and when it comes to element operations, you can design a LinkedList type function to return a new linked list after the operation.

Insert operation

The insertion operation of the linked list is mainly divided into finding the i-th position, and modifying the next pointer of this position to point to our newly inserted node, and the newly inserted node next pointer points to the node at our i+1 position.

Its operation mode can set a predecessor node, find the i-th position by loop, and then insert it.

As shown in the figure, insert a NEW_DATA data node between DATA1 and DATA2 data nodes:

From the original linked list state

To the new linked list state:

The code is implemented as follows:

//单链表的插入,在链表的第i个位置插入x的元素
  
LinkedList LinkedListInsert(LinkedList L,int i,int x) {
    Node *pre;                      //pre为前驱结点
    pre = L;
    int tempi = 0;
    for (tempi = 1; tempi < i; tempi++) {
        pre = pre->next;                 //查找第i个位置的前驱结点
    }
    Node *p;                                //插入的结点为p
    p = (Node *)malloc(sizeof(Node));
    p->data = x;
    p->next = pre->next;
    pre->next = p;
  
    return L;
}

Delete operation

To delete an element, create a predecessor node and a current node. When the data we need to delete is found, we directly use the predecessor node to skip the node to be deleted and point to the next node to be deleted, and then change the original Some nodes are released through the free function. as the picture shows:

code show as below:

//单链表的删除,在链表中删除值为x的元素
  
LinkedList LinkedListDelete(LinkedList L,int x) {
    Node *p,*pre;                   //pre为前驱结点,p为查找的结点。
    p = L->next;
     
    while(p->data != x) {              //查找值为x的元素
        pre = p;
        p = p->next;
    }
    pre->next = p->next;          //删除操作,将其前驱next指向其后继。
    free(p);
     
    return L;
}

Doubly linked list

Introduction and concept of doubly linked list

A singly linked list means that there is only one pointer to its successor in the node, which is one-way, but sometimes when a large amount of data needs to be searched, it is necessary to traverse multiple times from the beginning, and this search is not very efficient.

On the basis of the singly linked list, a predecessor node is designed for each node. The predecessor node and the previous node are connected to each other to form a linked list, and the concept of a doubly linked list is created.

A doubly linked list can be referred to as a double-linked list for short. It is a kind of linked list. Each data node in it has two pointers, which point to the direct successor and the direct predecessor respectively. Therefore, starting from any node in the doubly linked list, you can easily access its predecessor and successor nodes.

Diagram of doubly linked list

A complete doubly linked list should be that the pre pointer of the head node points to null, the next pointer of the end node points to null, and the other nodes are linked back and forth.

Node design of doubly linked list

For each node, there are:

Among them, DATA represents data, which can be a simple type or a complex structure;

pre represents the predecessor pointer, it always points to the previous node of the current node, if the current node is the head node, the pre pointer is empty;

Next represents the successor pointer, which always points to the next node of the current node. If the current node is the end node, the next pointer is empty

The code design is as follows:

typedef struct line{
    int data;           //data
    struct line *pre;   //pre node
    struct line *next;  //next node
}line,*a;
//分别表示该结点的前驱(pre),后继(next),以及当前数据(data)

  1. Creation of doubly linked list

To create a doubly linked list, you need to create the head node first, and then gradually add the head node of the doubly linked list that has data elements, that is, the data field of the head node contains data, which is different from the general singly linked list of.

For gradually adding data, first open up a new memory space as a new node, assign a value to the data of this node, and then point the next pointer of the previous node in the linked list to itself, and its own pre pointer to point up A node.

The code can be designed as:

//创建双链表
line* initLine(line * head){
    int number,pos=1,input_data;
    //三个变量分别代表结点数量,当前位置,输入的数据
    printf("请输入创建结点的大小\n");
    scanf("%d",&number);
    if(number<1){return NULL;} //输入非法直接结束
    //头结点创建///
    head=(line*)malloc(sizeof(line));
    head->pre=NULL;
    head->next=NULL;
    printf("输入第%d个数据\n",pos++);
    scanf("%d",&input_data);
    head->data=input_data;
  
    line * list=head;
    while (pos<=number) {
        line * body=(line*)malloc(sizeof(line));
        body->pre=NULL;
        body->next=NULL;
        printf("输入第%d个数据\n",pos++);
        scanf("%d",&input_data);
        body->data=input_data;
        
        list->next=body;
        body->pre=list;
        list=list->next;
    }
    return head;
}

The process of creating a doubly linked list can be divided into: creating a head node -> creating a new node -> linking the head node and the new node to each other -> creating a new node again, which will help understanding.

Insert operation of doubly linked list

as the picture shows:

For each insertion operation of a doubly linked list, you first need to create an independent node, and open up the corresponding space through the malloc operation;

Secondly, we select this newly created independent node and point its pre pointer to the previous node at the desired insertion position;

At the same time, the next pointer of the previous node that needs to be inserted is modified to point to the new node. The next pointer of the new node will point to the original next node, and the pre of the next node is modified. The pointer points to the new node itself, and we call such an operation 双向链表的插入操作.

The code can be expressed as:

//插入数据
line * insertLine(line * head,int data,int add){
    //三个参数分别为:进行此操作的双链表,插入的数据,插入的位置
    //新建数据域为data的结点
    line * temp=(line*)malloc(sizeof(line));
    temp->data=data;
    temp->pre=NULL;
    temp->next=NULL;
    //插入到链表头,要特殊考虑
    if (add==1) {
        temp->next=head;
        head->pre=temp;
        head=temp;
    }else{
        line * body=head;
        //找到要插入位置的前一个结点
        for (int i=1; i<add-1; i++) {
            body=body->next;
        }
        //判断条件为真,说明插入位置为链表尾
        if (body->next==NULL) {
            body->next=temp;
            temp->pre=body;
        }else{
            body->next->pre=temp;
            temp->next=body->next;
            body->next=temp;
            temp->pre=body;
        }
    }
    return head;
}

Delete operation of doubly linked list

As shown:

The process of deletion is: select the node to be deleted -> select the previous node of this node -> point the next pointer of the previous node to its next node -> select the next node of the node -> Modify the pre pointer of the next node to point to its previous node.

When traversing, this node is directly skipped. After that, we release and delete the node and return the space to the memory. We call this operation 双链表的删除操作.

The code can be expressed as:

//删除元素
line * deleteLine(line * head,int data){
    //输入的参数分别为进行此操作的双链表,需要删除的数据
    line * list=head;
    //遍历链表
    while (list) {
        //判断是否与此元素相等
        //删除该点方法为将该结点前一结点的next指向该节点后一结点
        //同时将该结点的后一结点的pre指向该节点的前一结点
        if (list->data==data) {
            list->pre->next=list->next;
            list->next->pre=list->pre;
            free(list);
            printf("--删除成功--\n");
            return head;
        }
        list=list->next;
    }
    printf("Error:没有找到该元素,没有产生删除\n");
    return head;
}

Traversal of doubly linked list

The traversal of the doubly linked list uses the next pointer to gradually index backwards.

Note that in this judgment, we can either use while(list)the operation to directly judge whether the linked list is empty, or use while(list->next)the operation to judge whether the linked list is empty, and the judgment conditions for the next node being empty and whether the current node is empty are the same effect.

Its simple code can be expressed as:

//遍历双链表,同时打印元素数据
void printLine(line *head){
    line *list = head;
    int pos=1;
    while(list){
        printf("第%d个数据是:%d\n",pos++,list->data);
        list=list->next;
    }
}

Circular linked list

Circular linked list concept

For singly linked lists and doubly linked lists, it is like an alley, no matter how you go, you can eventually walk from one end to the other. As the name suggests, a circular linked list is like an alley with a portal. When you think you are at the end, In fact, this is the beginning.

The only difference between the creation process of a circular linked list and a non-cyclic linked list is that the end node of the non-cyclic linked list points to NULL, while the tail pointer of the circular linked list points to the beginning of the linked list.

By pointing the tail node of the singly linked list to the head node, the linked list is called循环单链表(Circular linkedlist)

A complete circular singly linked list is shown in the figure:

Node design of circular linked list (take single circular linked list as an example)

For the nodes of the cyclic singly linked list, you can completely refer to the node design of the singly linked list, as shown in the figure:

One-way circular linked list node

data represents data;

Next represents a pointer, which always points to the next node of itself. For the existence of only one node, this next pointer always points to itself. For the tail node of a linked list, next always points to the beginning.

The code is as follows:


typedef struct list{
    int data;
    struct list *next;
}list;
//data为存储的数据,next指针为指向下一个结点

Circular singly linked list initialization

First create a head node and open up memory space for it. After the memory space is successfully opened, point the next node of the head node to head itself, and create an init function to complete;

In order to repeat creation and insertion, we can point to the empty node next created in the init function, and after the main function call is created, point the next pointer of the head node to itself.

This way of operation can facilitate the creation of a singly linked list later, and the entire creation can be completed by directly using the insert function called multiple times.

The code is as follows:

//初始结点
list *initlist(){
    list *head=(list*)malloc(sizeof(list));
    if(head==NULL){
        printf("创建失败,退出程序");
        exit(0);
    }else{
        head->next=NULL;
        return head;
    }
}

Recall in the main function can be like this

    //初始化头结点//
    list *head=initlist();
    head->next=head;

Creation of circular linked list

as the picture shows:

The creation of one-way circular linked list

Through the gradual insertion operation, create a new node, modify the next pointer of the end node of the original linked list to point to the new node, and then point the next pointer of the new node to the head node again, and then proceed gradually. Insert operation, and finally complete the creation of the entire single-item circular linked list.

The code is as follows:

//创建——插入数据
int insert_list(list *head){
    int data;   //插入的数据类型
    printf("请输入要插入的元素:");
    scanf("%d",&data);
  
    list *node=initlist();
    node->data=data;
    //初始化一个新的结点,准备进行链接
  
    if(head!=NULL){
        list *p=head;
        //找到最后一个数据
        while(p->next!=head){
            p=p->next;
        }
        p->next=node;
        node->next=head;
        return 1;
    }else{
        printf("头结点已无元素\n");
        return 0;
    }
  
}

Insert operation of circular singly linked list

As shown in the figure, for the operation of inserting data, an independent node can be created by pointing the next pointer of the previous node of the node that needs to be inserted to the node, and then pointing to the next node by the next pointer of the node that needs to be inserted Point the way to complete the insert operation.

The code is as follows:

//插入元素
list *insert_list(list *head,int pos,int data){
    //三个参数分别是链表,位置,参数
    list *node=initlist();  //新建结点
    list *p=head;       //p表示新的链表
    list *t;
    t=p;
    node->data=data;
    if(head!=NULL){
        for(int i=1;i<pos;i++){
            t=t->next;  //走到需要插入的位置处
        }
        node->next=t->next;
        t->next=node;
        return p;
    }
    return p;
}

Delete operation of circular singly linked list

As shown in the figure below, the delete operation of a circular singly linked list is to find the node to be deleted first, and point the next pointer of the previous node directly to the next node of the deleted node.

What needs attention is the tail node, because after deleting the tail node, the node before the tail node becomes the new tail node, and this new tail node needs to point to the head node instead of being empty.

The code is as follows:

//删除元素
int delete_list(list *head) {
    if(head == NULL) {
        printf("链表为空!\n");
        return 0;
    }
    //建立临时结点存储头结点信息(目的为了找到退出点)
    //如果不这么建立的化需要使用一个数据进行计数标记,计数达到链表长度时自动退出
    //循环链表当找到最后一个元素的时候会自动指向头元素,这是我们不想让他发生的
    list *temp = head;          
    list *ptr = head->next;
  
    int del;
    printf("请输入你要删除的元素:");
    scanf("%d",&del);
  
    while(ptr != head) {
        if(ptr->data == del) {
            if(ptr->next == head) { 
                temp->next = head;
                free(ptr);
                return 1;
            }
            temp->next = ptr->next;    //核心删除操作代码
            free(ptr);
            //printf("元素删除成功!\n");
            return 1;
        }
        temp = temp->next;
        ptr = ptr->next;
    }
    printf("没有找到要删除的元素\n");
    return 0;
}

Traversal of cyclic singly linked list

Unlike the traversal of ordinary singly-linked lists and doubly-linked lists, circular linked lists require special judgments on nodes.

First find the position of the tail node. Since the next pointer of the tail node points to the head node, 链表本身是否为空(NULL)the method cannot be used to make a simple loop judgment. We need 判断结点的next指针是否等于头结点的方式to judge whether the loop is completed.

In addition, there is a counting method, that is, to establish a counter count=0, each time the next pointer points to the next node, the counter +1is completed when the count number is the same as the number of nodes in the linked list;

However, there is a problem with this, that is, at the same time that the number of nodes in the linked list is obtained, it also needs to complete a traversal to achieve the goal.

The code is as follows:

//遍历元素
int display(list *head) {
    if(head != NULL) {
        list *p  = head;
        //遍历头节点到,最后一个数据
        while(p->next != head ) {
            printf("%d   ",p->next->data);
            p = p->next;
        }
        printf("\n");   //换行
        //把最后一个节点赋新的节点过去
        return 1;
    } else {
        printf("头结点为空!\n");
        return 0;
    }
}

Advanced concept-two-way circular linked list

Cyclic singly linked lists also have a twin brother-doubly cyclic linked lists. The design idea is the same as that of singly linked lists and doubly linked lists, which is to connect the tail node and the head node to each other on the basis of the original doubly linked list. Leave it to everyone.

Summary of linked lists

When inserting and deleting operations in the sequence table, about half of the elements in the table are moved on average, so the efficiency of the sequence table with a larger n is low. And it needs to allocate enough storage space in advance, and the linked list is the essence of it.

Based on storage, computing, and environment considerations, we can better use linked lists in projects.

That's it for the basics of linked lists today. See you in the next issue!

Guess you like

Origin blog.csdn.net/u012846795/article/details/108271721