C语言---单链表

单链表,双链表,静态链表,循环链表... ...

链表:链式存储结构,用于存储逻辑关系为"一对一"的数据.

与顺序表不同在于:链表的物理地址是不一定连续的

节点

  1. 结构体 指针

  2. 节点类型一般都是自定义的

头节点,尾节点,首元节点

首元节点: 第一个真正存储数据的节点

头指针,尾指针

 

创建单链表

首先,定义一个存放结点相关信息的结构体,结构体有两个元素,分别是键值和一个指向下一节点的指针

// 方便灵活改变类型 
typedef int DataType; 
// 节点结构体 
struct node 
{ 
DataType key; 0/
struct node * pnext; 
};
​
typedef struct node Node; 
//想要创建一个单链表,可以先创建一个表头结点(哑结点),然后在表头结点后不断插入新的结点即 可,需要注意的是,每新建一个结点都要为该结点分配一段内存空间
// **创建链表并给初始值 
// 参数:长度 
// 返回值:头指针 
Node* CreateList(int Length) 
{ 
    // 判断长度 
    if (Length<=0) 
    { 
        printf("Length error!\n"); 
        exit(EXIT_FAILURE); 
    }
    // 1 创建头尾两个指针 
    Node *phead, *pear; 
    phead = pear = NULL; 
    // 2 申请内存,做头节点(哑节点) 
    phead = (Node*)malloc(sizeof(Node)); 
    // 3 处理异常情况 
    if (phead == NULL) 
    { 
        perror("malloc failed!\n"); 
        exit(EXIT_FAILURE); 
    }
    /*
    首先介绍一下: 
    exit(0): 正常执行程序并退出程序 
    exit(1): 非正常执行导致退出程序 
    其次介绍: 
    stdlib.h头文件中 定义了两个变量: 
    #define EXIT_SUCCESS 0 
    #define EXIT_FAILURE 1 
    最后介绍: 
    exit(EXIT_SUCCESS) : 代表安全退出 
    exit(EXIT_FAILURE) : 代表异常退出 
    C 库函数 void perror(const char *str) 
    把一个描述性错误消息输出到标准错误 stderr。 
    首先输出字符串 str,后跟一个冒号,然后是一个空格。 
    */
    // 4 初始化头节点
    phead->pnext = NULL; 
    phead->key = 0; 
    // 5 初始化尾节点(一个节点,头即是尾) 
    pear = phead; 
    // 6 通过循环添加节点 
    Node* pNewNode = NULL; 
    for (size_t i = 0; i < Length; i++) 
    { 
        // 1 申请一个节点,检测,给值 
        pNewNode = (Node*)malloc(sizeof(Node)); 
        if (pNewNode == NULL) 
        { 
            perror("malloc failed!\n"); 
            exit(EXIT_FAILURE); 
        }
        int num = 0; 
        printf("输入节点数据: "); 
        scanf("%d", &num); 
        pNewNode->key = num; 
        pNewNode->pnext = NULL; 
        // 2 将新节点添加到链表中 
        // 添加过程中,头指针不动,尾指针改变 
        pear->pnext = pNewNode; 
        pear = pNewNode; 
    }
    return phead; 
}

遍历单链表

// **遍历链表 
// 参数:链表头指针 
// 返回值:无 
void TraverseList(Node* const pList) 
{ 
    // pList->pnext 避开哑节点 
    Node* ptemp = pList->pnext; 
    if (ptemp == NULL) 
    { 
        printf("链表为空!"); 
    }
    while (ptemp) 
    { 
        printf("%d ", ptemp->key); 
        ptemp = ptemp->pnext; 
    }
    printf("\n"); 
}

这段代码根据链表表尾结点的 next 指针指向 NULL 来遍历整个链表。

插入一个元素

// **插入元素(在位置后插入元素) 
// 参数:头指针,插入位置指针,值 
// 返回值:头指针 
void InsertElement(Node* List, Node* Position, int val) 
{ 
    Node* tmpNode = (Node*)malloc(sizeof(Node)); 
    if (tmpNode == NULL) 
    { 
        perror("malloc failed!\n"); 
        exit(EXIT_FAILURE); 
    }
    tmpNode->key = val; 
    tmpNode->pnext = Position->pnext; 
    Position->pnext = tmpNode; 
}

这段代码将元素 val 插入到链表中指定结点的后面

删除全部元素

// **删除全部 
// 参数:头指针 
// 返回值:无 
void DeleteList(Node* List) 
{ 
    Node* position, *tmpNode; 
    position = List->pnext; 
    List->pnext = NULL; // 头节点还在 
    while (position != NULL) 
    { 
        tmpNode = position->pnext; // 先将当前结点的 next 指针赋给临时结点保存 
        free(position); // 然后释放当前结点 
        position = tmpNode; // 再将以保存的 next 指针赋给 position, 即为下一个要删除的结点 
    } 
}

删除整个链表时,需要注意一点,要提前将要删除结点的 next 指针保存下来,再释放该结点。而 不能在释放了一个结点后再去利用已释放结点的 next 指针去释放下一个结点,因为此时上一个结点已 经被释放了,故而找不到 next 指针。此外,由于在创建链表时,每插入一个新的结点都会用 malloc 来 给结点分配一块内存,故而在删除链表时,每释放一个结点也应该使用 free 来释放一次内存

删除指定元素

// **删除指定元素(第一个特定值的元素) 
// 参数:头指针,值
// 返回值:头指针 
void DeleteElement(Node* List, int val) 
{ 
    Node* tmpNode; 
    Node* ptemp = List; 
    while (ptemp->pnext != NULL && ptemp->pnext->key != val) 
    { 
        ptemp = ptemp->pnext; 
    }
    if (ptemp->pnext == NULL) 
    { 
        printf("要删除的元素不存在!\n"); 
    }
    else 
    { 
        tmpNode = ptemp->pnext; 
        ptemp->pnext = tmpNode->pnext; 
        free(tmpNode); 
    } 
}

删除一个元素时,需要先找到该元素的前驱结点

查找一个元素

// **查找元素(按照值查找) 
// 参数:头指针,值 
// 返回值:目标节点指针或NULL 
Node* FindElement(Node* List, int val) 
{ 
    Node* ptr = List->pnext; 
    if (ptr == NULL) 
    { 
        printf("链表为空\n"); 
        return NULL; 
    }
    // 循环查找 
    while (ptr != NULL && ptr->key != val) 
    { 
        ptr = ptr->pnext; 
    }
    if (ptr != NULL) 
    { 
        printf("找到 %d 了\n", val); 
    }
    else 
    { 
        printf("没有找到 %d\n", val); 
    }
    return ptr; 
}

这段代码查找元素 val 是否在链表中,如果在,则打印元素已找到的信息,并返回该元素在链表中所在的结点;如果不在链表中,则打印没找到的信息,并返回一个空指针

注意

C库函数:
void perror(const char* str)
​
exit(EXIT_FAILURE);
exit(0);    正常执行   退出程序
exit(1);    非正常执行 退出程序
​
#define EXIT_SUCCESS    0
#define EXIT_FAILURE    1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
二.内核链表 内核链表是一种链表,Linux内核中的链表都是用这种形式实现的 1.特性 内核链表是一种双向循环链表,内核链表的节点节点结构中只有指针域 使用内核链表的时候,将内核链表作为一个成员放入到一个结构体中使用 我们在链表中找到内核链表结构的地址,通过这个地址就可以找到外部大结构体的地址,通过大结构体就可以访问其中的成员 优势: 内核链表突破了保存数据的限制,可以用内核链表来保存任何数据(使用一种链表表示各种类型的数据,通用性很强) 内核链表中只有指针域,维护起来更加方便,效率更高 2.使用 内核链表在内核中已经被实现,我们只需要调用其接口直接使用即可 内核链表的实现代码在内核源代码的list.h文件中 3.源代码分析 (1)节点结构: struct list_head { struct list_head *next, *prev;//前置指针 后置指针 }; (2)初始化 #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) (3)插入 //从头部插入 static inline void list_add(struct list_head *new, struct list_head *head)//传入要插入的节点和要插入的链表 { __list_add(new, head, head->next); } //从尾部插入 static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } (4)通过节点找到外部结构体的地址 //返回外部结构体的地址,第一个参数是节点地址,第二个参数是外部结构体的类型名,第三个参数是节点在外部结构体中的成员名 #define list_entry(ptr, type, member) ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) (5)遍历内核链表 //遍历内核链表 #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) //安全遍历内核链表 #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) 二.内核链表 内核链表是一种链表,Linux内核中的链表都是用这种形式实现的 1.特性 内核链表是一种双向循环链表,内核链表的节点节点结构中只有指针域 使用内核链表的时候,将内核链表作为一个成员放入到一个结构体中使用 我们在链表中找到内核链表结构的地址,通过这个地址就可以找到外部大结构体的地址,通过大结构体就可以访问其中的成员 优势: 内核链表突破了保存数据的限制,可以用内核链表来保存任何数据(使用一种链表表示各种类型的数据,通用性很强) 内核链表中只有指针域,维护起来更加方便,效率更高 2.使用 内核链表在内核中已经被实现,我们只需要调用其接口直接使用即可 内核链表的实现代码在内核源代码的list.h文件中 3.源代码分析 (1)节点结构: struct list_head { struct list_head *next, *prev;//前置指针 后置指针 }; (2)初始化 #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) (3)插入 //从头部插入 static inline void list_add(struct list_head *new, struct list_head *head)//传入要插入的节点和要插入的链表 { __list_add(new, head, head->next); } //从尾部插入 static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } (4)通过节点找到外部结构体的地址 //返回外部结构体的地址,第一个参数是节点地址,第二个参数是外部结构体的类型名,第三个参数是节点在外部结构体中的成员名 #define list_entry(ptr, type, member) ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) (5)遍历内核链表 //遍历内核链表 #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) //安全遍历内核链表 #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) 二.内核链表 内核链表是一种链表,Linux内核中的链表都是用这种形式实现的 1.特性 内核链表是一种双向循环链表,内核链表的节点节点结构中只有指针域 使用内核链表的时候,将内核链表作为一个成员放入到一个结构体中使用 我们在链表中找到内核链表结构的地址,通过这个地址就可以找到外部大结构体的地址,通过大结构体就可以访问其中的成员 优势: 内核链表突破了保存数据的限制,可以用内核链表来保存任何数据(使用一种链表表示各种类型的数据,通用性很强) 内核链表中只有指针域,维护起来更加方便,效率更高 2.使用 内核链表在内核中已经被实现,我们只需要调用其接口直接使用即可 内核链表的实现代码在内核源代码的list.h文件中 3.源代码分析 (1)节点结构: struct list_head { struct list_head *next, *prev;//前置指针 后置指针 }; (2)初始化 #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ } while (0) (3)插入 //从头部插入 static inline void list_add(struct list_head *new, struct list_head *head)//传入要插入的节点和要插入的链表 { __list_add(new, head, head->next); } //从尾部插入 static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } (4)通过节点找到外部结构体的地址 //返回外部结构体的地址,第一个参数是节点地址,第二个参数是外部结构体的类型名,第三个参数是节点在外部结构体中的成员名 #define list_entry(ptr, type, member) ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) (5)遍历内核链表 //遍历内核链表 #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); \ pos = pos->next) //安全遍历内核链表 #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) C语言下的单链表,可以增加,删除,查找,销毁节点

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值