2.3.1 Operation and implementation of singly linked list (data structure-pure C language)

2.3.1 Single linked list

In addition to storing data elements, each node also stores a pointer to the next node.

Advantages: Does not require large continuous space, easy to change capacity

Disadvantages: No random access, it takes a certain amount of space to store pointers

Leading node

Detailed operation explanation

image-20231130233644701

  • Define the creation of a singly linked list

    typedef struct LNode {
          
                //定义单链表结点类型
        int data;               //每个节点存放一个数据元素
        struct LNode* next;     //指针指向下一个节点 指向结构体类型为LNode的指针变量next
    }LNode, *LinkList;
    
    //struct LNode* next这意味着每个Node结构体的next成员可以指向另一个Node结构体,从而形成链表或其他递归结构。
    
    
    int main(){
          
          
        
     	LinkList L;
        
        return 0;
    }
    
    • About LNode and *LinkList

      //上述代码相当于
      struct LNode
      {
              
              
          int data;
          struct LNode* next;
      };
      typedef struct LNode LNode;
      typedef struct LNode* LinkList;   //这是一个指向struct LNode类型的指针
      
      //注意LNode* LinkList 这样写更严谨一些;将LNode* 重新命名为LinkList
      
      
      /* 创建单链表的头指针L(L就是一个),然后指向头结点(俗称第0个结点)。第0个结点不保存数据,但是头结点的next指针域指向下一个节点。然后一个个链接起来
      */
      
    • Question 1: Why rename LNode* to LinkList?

      • LNode* is equivalent to LinkList
      • LinkList emphasizes that this is a singly linked list or head node; LinkList L;
        LNode* emphasizes that this is a node; LNode *p
    • Question 2: Why useLinkList L instead of when creating a singly linked list?LNode L

      • When creating a singly linked list, LinkList L is used to facilitate the operation of the linked list; LinkList is an alias of the pointer type pointing to the node, not the specific node type.

      • If LNode L is used to represent a linked list, L is a variable of the specific node type, not a pointer to the node.

        Using LinkList L, you can change the head pointer of the linked list by modifying the value of L, making it easier to operate the entire linked list.

    • memory status

      image-20231006150236031

  • Initialize singly linked list

    // 初始化一个单链表
    bool InitList(LinkList *L) {
          
          		
        *L = (LNode*)malloc(sizeof(LNode));     
        /*    L是指向指针的指针,表示传入的参数是一个指向链表头指针的指针
        当分配内存失败时,L的值为NULL,表示没有足够的内存来创建链表头结点*/
        if (*L == NULL)  // 内存不足,分配失败
            return false;
        (*L)->next = NULL;  // 头结点下一个指向NULL 防止脏数据。每次初始化,都设置,养成好习惯
        return true;
    }
    
    //对于这个地方bool InitList(LinkList* L),这样写bool InitList(LinkList *L)要好一些,便于理解
    //相当于LNode* *L,所以可以直接用(*L)->data,来指向结构体里面内容,注意.和->的用法。复杂写法(**L).data
    
    
    • Question 1: How to understand*L = (LNode*)malloc(sizeof(LNode));

      • It is equivalent to applying for a head node first (the 0th node)

      • The head pointer L points to the 0th node

        image-20231130235925945

      //等同于
      bool InitList(LinkList* L) {
              
              
          LNode* p = (LNode*)malloc(sizeof(LNode));   //申请的头结点为p
          if (L == NULL)  // 内存不足,分配失败
              return false;
          *L = p;                                     //头指针指向头结点p
          (*L)->next = NULL;  // 将结点中的next指针域设置为NULL
          return true;
      }
      
    • Question 2: Why are the parameters passed inLinkList *L

      • There is no reference in C language. To initialize a singly linked list in the main function, the address of the head pointer L must be passed in.LinkList(&L)
  • bitwise insert

    Insert operation, insert the specified element e at the i-th position in table L

    image-20231201003048316

    //按位序插入
    
    /*在第i个位置插入元素e
        1.循环找到i的前一个结点,此时p为目标位置的前一个结点
        2.申请一个节点s,依次修改指针
    */
    
    bool ListInsert(LinkList *L, int i, int e){
          
          
        if(i<1)
            return false;
        LNode* p;       //指针p指向当前扫描的结点
        int j = 0;      //当前p指向的是第几个结点
        p = *L;         //L指向头结点,头结点是第0个结点;p指向第0个结点
    //   	LNode* p = *L;   熟练了可以直接这么写,让p直接指向头结点
        while(p!=NULL&& j<i-1){
          
          
            p = p->next;
            j++;
        }
        if(p==NULL)
            return false;
        LNode* s = (LNode*)malloc(sizeof(LNode));
        s->data = e;
        s->next = p->next;	//s的next指针域   指向  p的next指针域所指向的结点
        p->next = s;
        return true;
    }
    
    
    • Question 1: Why should we find the previous node?

      In order to modify the position pointed by the next pointer of the previous node so that it can point to the place where I inserted it

    • Question 2: What will happen if steps 1 and 2 are reversed?

      Since the next pointer field of p points to s first, s->next = p->next; s points to itself

      image-20231201004104521

  • Insert operation after specifying node

    After node p, insert new element e (actually splitting the bitwise insertion)

    // 后插操作:在p结点之后插入元素e
    bool InsertNextNode(LNode* p, int e) {
          
          
        if (p == NULL)
            return false;
        LNode* s = (LNode*)malloc(sizeof(LNode));
        if (s == NULL)
            return false;
        s->next = p->next;
        p->next = s;
        s->data = e;
        return true;
    }
    
    
    bool ListInsert(LinkList* L, int i, int e) {
          
          
        if (i < 1)
            return false;
        //首先还是先找到结点p
        LNode* p;   
        int j = 0; 
        p = *L;     
        while (p != NULL && j < i - 1) {
          
          
            p = p->next;
            j++;
        }
        return InsertNextNode(p, e);
    }
    
    
    
    • Question 1: Why use LNode *p instead of LNode **p when passing parameters?

      This is because the insertion operation of the linked list needs to modify the pointing of the pointer. In the first way of writing, the next pointer field of the node pointed to by the p pointer is accessed through §->next, and the p pointer itself is not modified; while in the In the second way of writing, the next pointer field of the node pointed to by the p pointer is modified by passing the address of the pointer p.

    • Question 2: Why do you need to write this judgment if (p == NULL) return false;

      If the incoming p points to the empty linked list L, L is empty, p is also empty, and cannot be inserted.

  • Pointed node forward insertion operation

    Insert element e before node p

    Use the orientation as post-interpolation and then exchange the data

    image-20231201162643917

    //指定结点前插操作
    //由于是在p结点之前插入,找不到前驱结点(当然也可以传入单链表来找前驱结点,那么时间复杂度明显较高)
    //采用方式为,正常的后插操作,然后将p和s的元素进行互换,即完成前插操作
    
    bool InsertPriorNode(LNode* p,int e){
          
          
        if(p==NULL)
            return false;
        LNode* s = (LNode*)malloc(sizeof(LNode));
        if(s==NULL)
            return false;
        s->next = p->next;
        p->next = s;
        s->data = p->data;
        p->data = e;
        return true;
    }
    
  • bitwise delete

    Delete the node at the i-th position and return the deleted data to e

    image-20231201170500692

    //按位删除
    bool ListDelete(LinkList *L,int i,int *e){
          
          
        if(i<1)
            return false;
        //还是需要找到前一个结点
        LNode* p = *L;
        int j = 0;
        while(p!=NULL&&j<i-1){
          
          
            p = p->next;
            j++;
        }
        if(p==NULL)
            return false;
        if(p->next = NULL)
            return false;   //如果p后面一个结点为NULL,那么就删除失败
        LNode* q = p->next; //q指向要被删除的结点
        *e = q->data;
        p->next = q->next;
        free(q);
        return true;
    }
    //最坏、平均时间复杂度:O(n)
    //最好时间复杂度:O(1)
    
  • Deletion operation of specified node

    Given node p, delete the data in the node

    Since the node is given and there is no predecessor node, the method is: change p into the predecessor node, copy the subsequent data to p, the next pointer field of p points to the node pointed to by the next pointer field of q, and release q

    To put it simply, it means exchanging data and then deleting the subsequent nodes.

    image-20231202144521563

    //指定结点删除
    bool DeleteNode(LNode *p){
          
          
        if(p==NULL||p->next==NULL)
            return false;
        
        LNode* q = p->next;     //令q指向p的后继结点
        p->data = q->data;    //将q结点中的数据给p
        p->next = q->next;          //将q结点从链中断开
        free(q);                //释放q
        return true;
    }
    
    //此处删除操作仍存在小问题,对于最后一个结点,采用的方式是直接返回false,也就是说,未能实现删除最后一个结点,如果需要准确删除,需要传入单链表的头结点来一次遍历进行删除,时间复杂度也会相应提高
    
  • Bitwise search

    Returns the node p at the i-th position

    // 单链表按位查找
    LNode* GetNode(LinkList* L, int i) {
          
          
        if (i < 0)
            return NULL;
        LNode* p = *L;
        int j = 0;
        while (p != NULL && j < i) {
          
          
            p = p->next;
            j++;
        }
        return p;
    }
    
    • Question 1: Why is i<0, not i<1
      • This is a singly linked list of the head node, i=0, which can be considered as the head node;
      • The bitwise insertion written before starts with finding the previous node of the i-th node, then you can call this method directlyLNode *p = GetNode(*L, i-1);When the inserted node is At the first position, it is reflected that i-1 is 0 at this time
  • Find by value

    //按值查找,找到数据域==e 的结点
    LNode *LocationElem(LinkList L,int e){
          
          
        LNode* p = L->next;
        //从第一个节点开始查找数据域为e的结点
        while(p!=NULL&&p->data!=e)
            p = p->next;
        return p;       //找到后返回该结点指针,否则返回NULL
    }
    
  • Find the length of the table

    //求表长度
    int Length(LinkList L){
          
          
        int len = 0;
        LNode* p = L;
        while(p!=NULL){
          
          
            p = p->next;
            len++;
        }
        return len;
    }
    
    • Question 1: Why is itLNode* p = L; but notLNode* p = L->next; counting from the first node

      In the following while loop, first let the p node move once, and then movelen++, so start from the 0th node

  • Create a singly linked list

    Tail insertion: similar to bitwise insertion, inserting a new node after each node

    image-20231202203249765

    // 用尾插法建立单链表
    LinkList List_tailInsert(LinkList* L) {
          
          
        int x;                               // 设element为整形
        *L = (LNode*)malloc(sizeof(LNode));  // 建立头结点
        LNode *s, *r = *L;
        scanf("%d", &x);
        while (x != -1) {
          
          
            s = (LNode*)malloc(sizeof(LNode));
            s->data = x;  // 申请的新结点s存储数据x
            r->next = s;  // r的next指针域指向s
            r = s;        // r指向s,相当于移到s这边
            scanf("%d", &x);
        }
        r->next = NULL;     //尾结点指针置空
        return *L;           // 注意:返回的是*L
    }
    

    Head insertion method: Each time an insertion operation is performed at the head of the linked list to create a singly linked list

    image-20231203002256870

    // 用头插法建立单链表
    LinkList List_HeadInsert(LinkList *L){
          
          
        LNode* s;
        int x;
        *L = (LNode*)malloc(sizeof(LNode));
        if (*L == NULL)
            return NULL;
        (*L)->next = NULL;      //头插法建立,这里需要将L的next指针域指向NULL 
    
        scanf("%d", &x);
        while(x!=-1){
          
          
            s = (LNode*)malloc(sizeof(LNode));
            if(s==NULL)
                break;	//申请内存失败
            s->data = x;
            s->next = (*L)->next;
            (*L)->next = s;
            scanf("%d", &x);
        }
        return *L;
    }
    
    

Complete code

// 带头结点的单链表
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>


typedef struct LNode {
    
    
    int data;
    struct LNode* next;
} LNode, *LinkList;

// 初始化一个单链表
bool InitList(LinkList *L) {
    
    
    *L = (LNode*)malloc(sizeof(LNode));
    /* L是指向指针的指针,表示传入的参数是一个指向链表头指针的指针
    当分配内存失败时,L的值为NULL,表示没有足够的内存来创建链表头结点*/
    if (*L == NULL)  // 内存不足,分配失败
        return false;
    (*L)->next = NULL;  // 头结点之后暂时还没有结点
    return true;
}

bool Empty(LinkList* L) {
    
    
    return (*L)->next == NULL;
}

// 后插操作:在p结点之后插入元素e
bool InsertNextNode(LNode* p, int e) {
    
    
    if (p == NULL)
        return false;
    LNode* s = (LNode*)malloc(sizeof(LNode));
    if (s == NULL)
        return false;
    s->next = p->next;
    p->next = s;
    s->data = e;
    return true;
}

/*在第i个位置插入元素e(带头结点)
    1.循环找到i的前一个结点,p指向他的next指针域,p=p->next
    2.申请一个节点s,依次修改指针
*/
// 按位序插入(带头结点)
bool ListInsert(LinkList* L, int i, int e) {
    
    
    if (i < 1)
        return false;
    LNode* p;   // 指针p指向当前扫描的结点
    int j = 0;  // 当前p指向的是第几个结点
    p = *L;     // L指向头结点,头结点是第0个结点;
    while (p != NULL && j < i - 1) {
    
    
        p = p->next;
        j++;
    }
    /*     if(p==NULL)
            return false;
        LNode* s = (LNode*)malloc(sizeof(LNode));
        s->data = e;
        s->next = p->next;
        p->next = s;
        return true; */
    return InsertNextNode(p, e);
}

// 前插操作:在p结点之前插入元素e
bool InsertPriorNode(LNode* p, int e) {
    
    
    if (p == NULL)
        return false;
    LNode* s = (LNode*)malloc(sizeof(LNode));
    if (s == NULL)
        return false;  // 内存分配失败
    s->next = p->next;
    p->next = s;        // 将新结点s连到p之后
    s->data = p->data;  // 将p中元素复制到s中
    p->data = e;        // p中元素覆盖为e
    return true;
}

// 指定结点删除
// 因为没有前一个结点,采用方式:申明指针q指向p的后继结点,然后夺舍p,释放q
bool DeleteNode(LNode* p) {
    
    
    if (p == NULL||p->next==NULL)
        return false;
    LNode* q = p->next;       // 令q指向*p的后继结点
    p->data = p->next->data;  // 将后继结点夺舍*p结点
    p->next = q->next;        // 将*q结点从链中断开
    free(q);                  // 释放q
    return true;
}

// 按位序删除(带头结点)
bool ListDelete(LinkList* L, int i, int* e) {
    
    
    if (i < 1)
        return false;
    LNode* p;                         // 当前p指向当前扫描的结点
    int j = 0;                        // 当前p指向第几个结点
    p = *L;                           // p指向第0个结点
    while (p != NULL && j < i - 1) {
    
      // 循环找到第i-1个结点
        p = p->next;
        j++;
    }
    if (p == NULL)
        return false;  // i值不合法(超了)
    if (p->next == NULL)
        return false;
    LNode* q = p->next;  // q指向要被删除的结点,即p的下一个结点
    *e = q->data;        // 返回e的数值
    p->next = q->next;   // q指向的结点直接断开
    free(q);
    return true;
}

// 按位查找,返回第i个结点,带头结点
LNode* GetElem(LinkList L, int i) {
    
    
    if (i < 0)
        return NULL;
    LNode* p;
    int j = 0;
    p = L;
    while (p != NULL && j < i) {
    
      // 循环找到第i个结点
        p = p->next;
        j++;
    }
    return p;
}

// 按值查找,找到数据域==e 的结点
LNode* LocationElem(LinkList L, int e) {
    
    
    LNode* p = L->next;
    // 从第一个节点开始查找数据域为e的结点
    while (p != NULL && p->data != e)
        p = p->next;
    return p;  // 找到后返回该结点指针,否则返回NULL
}

// 求表的长度
int Length(LinkList L) {
    
    
    int len = 0;
    LNode* p = L;
    while (p != NULL) {
    
    
        p = p->next;
        len++;
    }
    return len;
}

// 用尾插法建立单链表
LinkList List_tailInsert(LinkList* L) {
    
    
    int x;                               // 设element为整形
    *L = (LNode*)malloc(sizeof(LNode));  // 建立头结点
    LNode *s, *r = *L;
    scanf("%d", &x);
    while (x != -1) {
    
    
        s = (LNode*)malloc(sizeof(LNode));
        s->data = x;  // 申请的新结点s存储数据x
        r->next = s;  // r的next指针域指向s
        r = s;        // r指向s,相当于移到s这边
        scanf("%d", &x);
    }
    r->next = NULL;     //尾结点指针置空
    return *L;           // 注意:返回的是*L
}

// 用头插法建立单链表
LinkList List_HeadInsert(LinkList *L){
    
    
    LNode* s;
    int x;
    *L = (LNode*)malloc(sizeof(LNode));
    if (*L == NULL)
        return NULL;
    (*L)->next = NULL;      //头插法建立,这里需要将L的next指针域指向NULL 

    scanf("%d", &x);
    while(x!=-1){
    
    
        s = (LNode*)malloc(sizeof(LNode));
        if(s==NULL)
            break;
        s->data = x;
        s->next = (*L)->next;
        (*L)->next = s;
        scanf("%d", &x);
    }
    return *L;
}

//  输出单链表中的元素
void printList(LinkList L) {
    
    
    LNode* p = L->next;  // p此时指向第1个结点(跳过头结点)
    int j = 1;
    if (L == NULL)
        return;  // 如果链表为空,则直接返回

    while (p != NULL) {
    
      // 遍历单链表
        printf("第%d个节点为:%d\n", j, p->data);
        p = p->next;  // 移动到下一个节点
        j++;
    }
}

int main() {
    
    
    LinkList L;
    List_HeadInsert(&L);
    // List_tailInsert(&L);
    ListInsert(&L, 2, 1314);
    ListInsert(&L, 3, 520);
    ListInsert(&L, 4, 0);
    int e;
    ListDelete(&L, 4, &e);
    printf("被删除数据为%d\n", e);
    printList(L);

    system("pause");
}

No leading node

  • Creation and initialization

    typedef struct LNode
    {
          
          
        int data;
        struct LNode* next;
    }LNode,*LinkList;
    
    //初始化
    bool InitList(LinkList *L){
          
          
        *L = NULL;
        return true;
    }
    
  • bitwise insert

    image-20231203003830808

    For nodes without a head, since there is no head node, the first insertion needs to be processed separately.

    //按位插入
    bool ListInsert(LinkList *L,int i,int e){
          
          
        if(i<1)
            return false;
        if(i==1){
          
          
            LNode* s = (LNode*)malloc(sizeof(LNode));
            if(s==NULL)
                return false;
            s->data = e;
            s->next = *L;
            *L = s;
            return true;
        }
        LNode* p = *L;  // p指向当前扫描的结点
        int j = 1;      // 此时指向第一个节点
        while(p!=NULL&&j<i-1){
          
          
            p = p->next;
            j++;
        }
        if(p==NULL)
            return false;
        LNode* s = (LNode*)malloc(sizeof(LNode));
        s->data = e;
        s->next = p->next;
        p->next = s;
        return true;
    }
    
    
    • Question 1: Why do we need to deal with the first insertion position separately?

      From a method point of view, insertion requires p to find the previous node. Since there is no node before the first position, the pointing of the head pointer needs to be modified for insertion; and the head pointer of a singly linked list with a head node always points to the head node, so p can be found directly

  • bitwise delete

    Delete the node according to the position. The first node also needs to be processed separately.

    // 按位删除
    bool ListDelete(LinkList* L, int i, int* e) {
          
          
        if (i < 1)
            return false;
        if (i == 1) {
          
          
            LNode* q = *L;    //q指向头指针所指向的结点,即第一个结点
            *L = q->next;      //头指针指向 q的next指针域所指向的结点,如果没有第二个结点则为NULL 
            *e = q->data;
            free(q);
            return true;
        }
        // 与之前删除相同
        LNode* p = *L;
        int j = 1;
        while (p != NULL && j < i - 1) {
          
          
            p = p->next;
            j++;
        }
        // 检查第 i-1 个结点以及第 i 个节点是否存在
        if (p == NULL || p->next == NULL)
            return false;
        LNode* q = p->next;
        p->next = q->next;
        *e = q->data;
        free(q);
        return true;
    }
    

    Specific operations will be in stacks and queues, and nodes without the head are more commonly used.

Guess you like

Origin blog.csdn.net/weixin_46290752/article/details/134760292