C语言数据结构解析与实例-链表

链表是C语言中的一种基本数据结构。

对于C程序员来说,了解链表是必要的。

链表是一种动态数据结构,其长度可以在运行时增加或减少。

链表与数组有什么不同?考虑以下几点:

  • 数组是一种静态数据结构。这意味着数组的长度在运行时无法改变,而链表是一种动态数据结构。
  • 在数组中,所有元素都保持在连续的内存位置上,而在链表中,元素(或节点)可以位于任何位置,但仍然相互连接。

何时优先选择链表而不是数组?在不知道要存储的数据量时,通常首选链表。例如,在员工管理系统中,无法使用数组,因为数组长度是固定的,而任意数量的新员工可以加入。在这种情况下,使用链表(或其他动态数据结构),因为它们的容量可以在运行时根据需要增加(或减少)。

链表在内存中是如何排列的?

链表基本上由位于随机内存位置的内存块组成。现在,有人可能会问它们是如何连接或如何遍历的?嗯,它们是通过指针连接起来的。通常,链表中的一个块通过以下结构表示:

struct test_struct
{
    int val;
    struct test_struct *next;
};

正如你在这里所看到的,这个结构体包含一个值 val 和一个指向相同类型结构体的指针。值 val 可以是任意值(取决于链表所存储的数据),而指针 next 包含了链表中下一个块的地址。因此,通过这些包含下一个节点地址的 next 指针,实现了链表的遍历。最后一个节点(或者只有一个节点的链表)的 next 指针将包含一个 NULL 值。

节点是如何创建的?

节点是通过以下方式将内存分配给一个结构(如上文所示)来创建的:

struct test_struct *ptr = (struct test_struct*)malloc(sizeof(struct test_struct));

这个结构体包含一个值 val 和一个指向相同类型结构体的指针。值 val 可以是任意值(取决于链表所存储的数据),而指针 next 包含了链表中下一个块的地址。因此,通过这些包含下一个节点地址的 next 指针,实现了链表的遍历。最后一个节点(或者只有一个节点的链表)的 next 指针将包含一个 NULL 值。

...
...
ptr->val = val;
ptr->next = NULL;
...
...

搜索节点

搜索一个节点意味着查找包含要搜索的值的节点。实际上,如果我们谈论线性搜索的话,这是一个非常简单的任务(请注意,还有许多搜索算法)。只需从第一个节点开始,然后将要搜索的值与该节点中包含的值进行比较。如果值不匹配,则通过“next”指针(它包含下一个节点的地址)访问下一个节点,并在那里进行相同的值比较。搜索会继续进行,直到访问到最后一个节点,或者找到了值等于要搜索的值的节点。以下是一个代码片段示例:

...
...
...
    while(ptr != NULL)
    {
        if(ptr->val == val)
        {
            found = true;
            break;
        }
        else
        {
            ptr = ptr->next;
        }
    }
...
...
...

删除节点

节点的删除通过首先在链表中找到该节点,然后在包含其地址的指针上调用free()函数来实现。如果被删除的节点不是第一个或最后一个节点,那么需要将被删除节点之前的节点的“next”指针指向被删除节点之后的节点的地址。就像如果一个人从人链中脱离出来,那么这个人之前的两个人需要重新握手来保持链条的完整。

一个实际的C链表示例

这里有一个实际的示例,它创建一个链表,向其添加一些节点,然后在链表中搜索和删除节点

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

struct test_struct
{
    int val;
    struct test_struct *next;
};

typedef struct test_struct myLinklist;

myLinklist *head = NULL; //头结点
myLinklist *curr = NULL; //当前节点(尾节点)


//创建链表并初始化头节点
myLinklist* create_list(int val){
    printf("\n creating list with headnode as [%d]\n", val);
    //分配空间
    myLinklist * ptr = (myLinklist*)malloc(sizeof(myLinklist));

    if(NULL == ptr){
        printf("\n Node creation failed \n");
        return NULL;
    }

    ptr->val = val;
    ptr->next = NULL;

    head = curr = ptr;
    return ptr;
}

//向链表中添加节点
myLinklist* add_to_list(int val, bool add_to_end){
    //链表为空
    if(NULL == head){
        return (create_list(val));
    }

    if(add_to_end)
        printf("\n Adding node to end of list value [%d]", val);
    else
        printf("\n Adding node to beginning of list with value [%d]", val);
    
    myLinklist * ptr = (myLinklist*)malloc(sizeof(myLinklist));
    if(NULL == ptr){
        printf("\n Node creation failed \n");
        return NULL;
    }

    ptr->val = val;
    ptr->next = NULL;

    if(add_to_end){
        curr->next = ptr;
        curr = ptr;
    }else{
        ptr->next = head;
        head = ptr;
    }

    return ptr;
}

//在链表中搜索指定值的节点
myLinklist* search_in_list(int val, myLinklist* *prev){
    myLinklist* ptr = head;
    myLinklist* tmp = NULL;
    bool found = false;

    printf("\n Searching the list for value [%d] \n", val);

    while(ptr != NULL){
        if(ptr->val == val){
            found = true;
            break;
        }else{
            //更新前一个节点
            tmp = ptr;
            ptr = ptr->next;
        }
    }

    if(true == found){
        if(prev) *prev = tmp;
        return ptr;
    }else{
        return NULL;
    }
}

//删除指定值的节点
int delete_from_list(int val){
    myLinklist* prev = NULL;
    myLinklist* del = NULL;

    printf("\n Deleting value [%d] from list\n", val);

    del = search_in_list(val, &prev);

    if(del == NULL)
        return -1;
    else{
        //有前节点
        if(prev != NULL)
            prev->next = del->next;
        
        //尾节点
        if(del == curr)
            curr=prev;
        //头节点
        else if(del == head)
            head = del->next;
    }

    free(del);
    del = NULL;

    return 0;
}

//打印链表中的节点值
void print_list(void){
    myLinklist* ptr = head;

    printf("\n ------printing list start------ \n");
    while(ptr != NULL){
        printf("\n [%d] \n", ptr->val);
        ptr=ptr->next;
    }
    printf("\n ------printing list end------\n");

    return;
}

int main(){
    int i = 0;
    int ret = 0;
    myLinklist* ptr = NULL;

    print_list();

    for(i=5; i<10; i++)
        add_to_list(i, true);
    
    print_list();

    for(i=4; i>0; i--)
        add_to_list(i, false);
    
    print_list();

    for(i=1; i<10; i+=4){
        ptr = search_in_list(i, NULL);
        if(NULL == ptr){
            printf("\n search [val=%d] failed, no such element found\n", i);
        }else{
            printf("\n search passed [val=%d]\n", ptr->val);
        }

        print_list();

        ret = delete_from_list(i);
        if(ret != 0){
            printf("\n delete [val=%d] failed, no such element found\n", i);
        }else{
            printf("\n delete [val=%d] passed \n",i);
        }

        print_list();
    }
    
    return 0;
}

在上面的代码中:

第一个节点始终通过一个全局的“head”指针来访问。当第一个节点被删除时,该指针会被调整。
类似地,还有一个“curr”指针,它包含链表中的最后一个节点。当最后一个节点被删除时,该指针也会被调整。
每当向链表中添加一个节点时,都会检查链表是否为空,如果为空,则将其作为第一个节点添加。

posted @ 2023-06-26 15:38  言叶以上  阅读(24)  评论(0编辑  收藏  举报