C 数据结构 之 链表

链表

  • 1. 概念
    • 用来管理 线性链式物理结构 功能的数据结构,以后在程序中使用线性链式物理结构就不需要写代码操作存储区了,直接使用链表提供的各种功能就可以啦。
  • 2. 实现方法
typedef struct node
{
    int val;             // 记录数字的成员变量
    struct node *p_prev; // 指向前一个节点的指针成员变量
    struct node *p_next; // 指向后一个节点的指针成员变量
} node;

typedef struct
{
    node head;   // 头结点
    node tail;   // 尾节点
    node *p_cur;  // 记录遍历过程中上一个操作的节点 
} link;
	

link_1.h

#ifndef            __LINK_1_H__
#define            __LINK_1_H__
typedef struct node
{
    int val;             // 记录数字的成员变量
    struct node *p_prev; // 指向前一个节点的指针成员变量
    struct node *p_next; // 指向后一个节点的指针成员变量
} node;

typedef struct
{
    node head;   // 头结点
    node tail;   // 尾节点
    node *p_cur;  // 记录遍历过程中上一个操作的节点 
} link;

// 链表初始化函数
void link_init(link *);

// 链表清理函数
void link_deinit(link *);

// 获得数字个数的函数
int link_size(const link *);

// 判断链表是否为空的函数
int link_empty(const link *);

// 在链表的最前面插入数字
int link_add_head(link *, int);

//在链表末尾追加新数字的函数
int link_append(link *, int);

// 按照从小到大的顺序在链表中间插入新的数字的函数
int link_insert(link *, int);

//删除最前面的数字的函数
int link_remove_head(link *);

// 删除最后一个有效数字的函数
int link_remove_tail(link *);

// 删除链表中间指定数字的节点
int link_remove(link *, int);

// 获得最前面的数字
int link_get_head(const link *, int *);

// 获得链表里最后一个数字的函数
int link_get_tail(const link *, int *);

// 根据编号获得数字的函数
int link_get(const link *, int *, int);

// 开始从前向后遍历链表的函数
void link_begin(link *);

// 从前向后遍历的过程中获得下一个数字的函数
int link_next(link *, int *);

// 开始从后向前遍历链表的函数
void link_rbegin(link *);

// 从后向前遍历过程中获得下一个数字的函数
int link_prev(link *, int *);

#endif      //__ LINK_1_H__

link_1.c

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

// 链表初始化函数
void link_init(link *p_link) {
    p_link->head.p_next = &p_link->tail; // 把头结点和尾节点连接起来形成一个空的链式物理结构
    p_link->tail.p_prev = &p_link->head; // 尾节点向前指向头节点
    p_link->tail.p_next = NULL; // 把尾节点里的指针设置成空指针
    p_link->head.p_prev = NULL; // 把头节点里指向前一个节点的指针设置成空指针
    p_link->p_cur = NULL;
    // 遍历过程是对节点处理顺序是有要求的, 因为p_cur是记录遍历过程中上一个操作的节点, 修改链表的函数都有可能破坏链表的遍历过程
    // 所以规定在遍历过程中不可以有修改链表结构的操作, 这里规定了两个状态, 遍历状态和非遍历状态
    // 刚初始化完一个链表后, 它应该处于非遍历状态, 即 p_cur 是空指针的时候,表示这个链表没有处于遍历过程. 想让链表结束遍历过程的时候就把 p_cur 设置为空指针
    // 所以规定只要做了修改链表结构的操作, 就立刻结束遍历过程, 通过这样方式,可以保证在遍历过程中链表的结构是不会被修改的
}

// 链表清理函数
void link_deinit(link *p_link) {
    node *p_first = NULL, *p_mid = NULL, *p_last = NULL;
    p_link->p_cur = NULL;

    // 每次循环把最前面的有效节点删除,并释放
    while (p_link->head.p_next != &p_link->tail) { // 头结点后面不是尾节点,就说明存在有效节点,循环继续
        p_first = &p_link->head;
        p_mid = p_first->p_next;
        p_last = p_mid->p_next;
        // 把p_mid指向的节点从链式物理结构中摘除
        p_first->p_next = p_last;
        p_last->p_prev = p_first;
        // 释放p_mid指针指向的节点
        free(p_mid);
        p_mid = NULL;
    }
}

// 获得数字个数的函数
int link_size(const link *p_link) {
    int cnt = 0;
    // 不写const 说明可以通过p_tmp改变链表,会与形参矛盾,编译警告
    const node *p_tmp = NULL;
    for (p_tmp = &p_link->head; p_tmp != &p_link->tail; p_tmp = p_tmp->p_next) {
        const node *p_first = p_tmp;
        const node *p_mid = p_first->p_next;
        const node *p_last = p_mid->p_next;
        if (p_mid != &p_link->tail) { // 当p_mid指针不是指向尾节点就一定指向一个有效节点
            cnt++;
        }
    }
    return cnt;
}

// 判断链表是否为空的函数
int link_empty(const link *p_link) {
    return p_link->head.p_next == &p_link->tail;
}

// 在链表的最前面插入数字
int link_add_head(link *p_link, int val) {
    node *p_first = NULL, *p_mid = NULL, *p_last = NULL;
    node *p_node = NULL;
    p_link->p_cur = NULL; // 结束遍历过程
    p_node = (node *)malloc(sizeof(node)); // 动态分配内存用来记录新数字
    if (!p_node) {
        // 动态内存分配失败
        return 0;
    }
    p_node->val = val;
    p_node->p_next = NULL; // 不写也行,好习惯
    p_node->p_prev = NULL; 
    // 把三个节点指针指向链表最前面的三个节点
    p_first = &p_link->head;
    p_mid = p_first->p_next;
    p_last = p_mid->p_next;
    // 把新节点插入到p_first 和 p_mid中间
    p_first->p_next = p_node;
    p_node->p_next = p_mid;
    p_mid->p_prev = p_node;
    p_node->p_prev = p_first;
    return 1;
}

// //在链表末尾追加新数字的函数
// int link_append(link *p_link, int val) {
//     node *p_tmp = NULL;
//     node *p_node = NULL;
//     p_link->p_cur = NULL;    // 结束遍历过程
//     p_node = (node *)malloc(sizeof(node)); // 动态分配内存用来记录新数字
//     if (!p_node) {
//         // 动态内存分配失败
//         return 0;
//     }
//     p_node->val = val;
//     p_node->p_next = NULL; // 不写也行,好习惯
//     p_node->p_prev = NULL;
//     // 编写for循环找到链表末尾的位置
//     for (p_tmp = &p_link->head; p_tmp != &p_link->tail; p_tmp = p_tmp->p_next) {
//         node *p_first = p_tmp;
//         node *p_mid = p_first->p_next;
//         node *p_last = p_mid->p_next;
//         if (p_mid == &p_link->tail) { // 当p_mid 指向尾节点,p_first 和 p_mid 指针中间的位置就是要插的位置
//             p_first->p_next = p_node;
//             p_node->p_next = p_mid;
//             p_mid->p_prev = p_node;
//             p_node->p_prev = p_first;
//             break;
//         }                       
//     }
//     return 1;
// }

//在链表末尾追加新数字的函数
int link_append(link *p_link, int val) {
    node *p_first = NULL, *p_mid = NULL, *p_last = NULL;
    node *p_node = NULL;
    p_link->p_cur = NULL;   // 结束遍历过程
    p_node = (node *)malloc(sizeof(node)); 
    if (!p_node) {
        return 0;
    }
    p_node->val = val;
    p_node->p_next = NULL;
    p_node->p_prev = NULL;

    p_first = p_link->tail.p_prev;  // p_first 指针指向尾节点前面的节点
    p_mid = p_first->p_next;        // p_mid 指针指向尾节点
    p_last = p_mid->p_next;
    // 把新节点插入到p_first 和 p_mid 之间
    p_first->p_next = p_node;
    p_node->p_next = p_mid;
    p_mid->p_prev = p_node;
    p_node->p_prev = p_first;
    return 1;
}

// 按照从小到大的顺序在链表中间插入新的数字的函数
int link_insert(link *p_link, int val) {
    node *p_first = NULL, *p_mid = NULL, *p_last = NULL;
    node *p_tmp = NULL;
    node *p_node = NULL;
    p_link->p_cur = NULL;
    p_node = (node *)malloc(sizeof(node));
    if (!p_node) {
        return 0;
    }
    p_node->val = val;
    p_node->p_next = NULL;
    p_node->p_prev = NULL;
    // for循环寻找插入位置
    for (p_tmp = &p_link->head; p_tmp != &p_link->tail; p_tmp = p_tmp->p_next) {
        node *p_first = p_tmp;
        node *p_mid = p_first->p_next;
        node *p_last = p_mid->p_next;
    // 两种情况
    // 1. p_mid 指针指向节点里的数字比要插入的数字大, 2. 所有节点的数字都没有要插入的大,直至for循环到p_mid 指向尾节点
        if (p_mid->val > val || p_mid == &p_link->tail) {
            p_first->p_next = p_node;
            p_node->p_next = p_mid;
            p_mid->p_prev = p_node;
            p_node->p_prev = p_first;
            break;
        }
    }
    return 1;
}

//删除最前面的数字的函数
int link_remove_head(link *p_link) {
    node *p_first = NULL, *p_mid = NULL, *p_last = NULL;
    p_link->p_cur = NULL;   // 结束遍历过程
    if (p_link->head.p_next == &p_link->tail) {
        // 链表是空的情况下无法删除任何节点
        return 0;
    }
    // 让三个指针指向链表最前面的三个节点
    p_first = &p_link->head;
    p_mid = p_first->p_next;
    p_last = p_mid->p_next;
    // 把p_mid指向的节点从链式物理结构中摘出来
    p_first->p_next = p_last;
    p_last->p_prev = p_first;
    free(p_mid);
    p_mid = NULL;
    return 1;
}

// // 删除最后一个有效数字的函数
// int link_remove_tail(link *p_link) {
//     node *p_tmp = NULL;
//     p_link->p_cur = NULL; //结束遍历过程
//     //for循环找到最后一个有效数字所在的节点
//     for (p_tmp = &p_link->head; p_tmp != &p_link->tail; p_tmp = p_tmp->p_next) {
//         node *p_first = p_tmp;
//         node *p_mid = p_first->p_next;
//         node *p_last = p_mid->p_next;
//         // 当p_last指针指向尾节点,p_mid指针一定指向最后一个有效节点
//         if (p_last == &p_link->tail) {
//             // 把p_mid指针指向的节点从链式物理结构中摘出来
//             p_first->p_next = p_last;
//             p_last->p_prev = p_first;
//             free(p_mid);
//             p_mid = NULL;
//             return 1;
//         }
//     }
//     return 0;
// }

// 删除最后一个有效数字的函数
int link_remove_tail(link *p_link) {
    node *p_first = NULL, *p_mid = NULL, *p_last = NULL;
    p_link->p_cur = NULL;
    if (p_link->head.p_next == &p_link->tail) {
        // 链表为空
        return 0;
    }
    p_last = &p_link->tail;    // p_last只想最后一个有效节点
    p_mid = p_last->p_prev;    //p_mid 指针只想最后一个有效节点
    p_first = p_mid->p_prev;
    // 把p_mid从最后一个有效节点里摘除掉
    p_first->p_next = p_last;
    p_last->p_prev = p_first;
    //释放p_mid 指针指向的节点
    free(p_mid);
    p_mid = NULL;
    return 1;
}

// 删除链表中间指定数字的节点
int link_remove(link *p_link, int val) {
    node *p_tmp = NULL;
    p_link->p_cur = NULL;
    // 编写for循环找到要删除的数字所在节点
    for (p_tmp = &p_link->head; p_tmp != &p_link->tail; p_tmp = p_tmp->p_next) {
        node *p_first = p_tmp;
        node *p_mid = p_first->p_next;
        node *p_last = p_mid->p_next;
        // 这里要防止有效节点里没有指定数字而尾节点里有的情况
        if (p_mid->val == val && p_mid != &p_link->tail)
        {
            p_first->p_next = p_last;
            p_last->p_prev = p_first;
            free(p_mid);
            p_mid = NULL;
            return 1;
        }
    }
    return 0;
}

// 获得最前面的数字
int link_get_head(const link *p_link, int *p_val) {
    const node *p_first = NULL, *p_mid = NULL, *p_last = NULL;
    if (p_link->head.p_next ==  &p_link->tail) {
        return 0;
    }
    p_first = &p_link->head;
    p_mid = p_first->p_next;
    p_last = p_mid->p_next;
    // 把p_mid指针指向节点里的数字传递给调用函数
    *p_val = p_mid->val;
}

// // 获得链表里最后一个数字的函数
// int link_get_tail(const link *p_link, int *p_val) {
//     const node *p_tmp =NULL;
//     for (p_tmp = &p_link->head; p_tmp != &p_link->tail; p_tmp = p_tmp->p_next) {
//         const node *p_first = p_tmp;
//         const node *p_mid = p_first->p_next;
//         const node *p_last = p_mid->p_next;
//         if (p_last == &p_link->tail) {
//             *p_val = p_mid->val;
//             return 1;
//         }
//     }
//     return 0;
// }

// 获得链表里最后一个数字的函数
int link_get_tail(const link *p_link, int *p_val) {
    const node *p_first = NULL, *p_mid = NULL, *p_last = NULL;
    if (p_link->head.p_next == &p_link->tail) {
        // 链表为空
        return 0;
    }
    p_last = &p_link->tail;
    p_mid = p_last->p_prev;
    p_first = p_mid->p_prev;

    *p_val = p_mid->val; //把p_mid指针指向的节点里的数字传递给调用函数
    return 1;
}

// 根据编号获得数字的函数
int link_get(const link *p_link, int *p_val, int sn) {
    int cnt = 0;
    const node *p_tmp = NULL;
    //for 循环找指定节点
    for (p_tmp = &p_link->head; p_tmp != &p_link->tail; p_tmp = p_tmp->p_next) {
        
        const node *p_first = p_tmp;
        const node *p_mid = p_first->p_next;
        const node *p_last = p_mid->p_next;
        // 确保p_mid指针是有效节点而不是指向尾节点
        if (p_mid != &p_link->tail && cnt == sn) {
            *p_val = p_mid->val;
            return 1;
        }
        cnt++;
    }
    return 0;
}

// 开始从前向后遍历链表的函数
void link_begin(link *p_link) {
    p_link->p_cur = &p_link->head; // 把头节点设置为上一个操作的节点
}

// 从前向后遍历的过程中获得下一个数字的函数
int link_next(link *p_link, int *p_val) {
    // 如果没有调用 link_begin 函数, 是没有办法开始遍历过程的
    if (!p_link->p_cur) {
        // 链表没有处于遍历过程中
        return 0;
    }
    p_link->p_cur = p_link->p_cur->p_next; //找到这次要操作的节点并记录下来
    if (p_link->p_cur == &p_link->tail) {
        // 这次要操作的节点是尾节点就说明遍历过程已经结束了
        p_link->p_cur = NULL; // 结束遍历
        return 0;
    }
    else {
        *p_val = p_link->p_cur->val; //把这次要操作的节点内容传递给调用函数
        return 1; 
    }
    
}

// 开始从后向前遍历链表的函数
void link_rbegin(link *p_link) {
    p_link->p_cur = &p_link->tail; //把尾节点节点设置为上一个操作的节点
}

// 从后向前遍历过程中获得下一个数字的函数
int link_prev(link *p_link, int *p_val) {
    // 如果没有调用 link_rbegin 函数, 是没有办法开始遍历过程的
    if (!p_link->p_cur) {
        return 0;
    }
    p_link->p_cur = p_link->p_cur->p_prev;
    if (p_link->p_cur == &p_link->head) {
        // 这次要操作的节点是头节点就说明遍历已经结束
        p_link->p_cur = NULL;
        return 0;
    }
    else {
        *p_val = p_link->p_cur->val;
        return 1;
    }
}

link_main.c

#include <stdio.h>
#include "link_1.h"
int main() {
    int val = 0, size = 0;
    link lnk = {0};
    link_init(&lnk);
    link_add_head(&lnk, 50);
    link_add_head(&lnk, 10);
    link_append(&lnk, 70);
    link_append(&lnk, 100);
    link_insert(&lnk, 30);
    link_insert(&lnk, 20);
    link_insert(&lnk, 40);
    link_insert(&lnk, 80);
    link_insert(&lnk, 60);
    link_insert(&lnk, 90);
    link_remove_head(&lnk);
    link_remove_tail(&lnk);
    link_remove(&lnk, 40);

    link_get_head(&lnk, &val);
    printf("最前面的数字是%d\n", val);
    link_get_tail(&lnk, &val);
    printf("最后面的数字是%d\n", val);
    
    size = link_size(&lnk);
    for (int num = 0; num < size; num++) {
        link_get(&lnk, &val, num);
        printf("%d ", val);
    }
    printf("\n");

    // 正向遍历
    link_begin(&lnk);
    while(1) {
        if (!link_next(&lnk, &val)) {
            break;
        } 
        printf("%d ", val);
    }
    printf("\n");

    // 反向遍历
    link_rbegin(&lnk);
    while(1){
        if (!link_prev(&lnk, &val)) {
            break;
        }
        printf("%d ", val);
    }
    printf("\n");

    link_deinit(&lnk);
    return 0;
}
moonx@moonx: $ gcc link_1.c link_main.c; ./a.out 
最前面的数字是20
最后面的数字是90
20 30 50 60 70 80 90 
20 30 50 60 70 80 90 
90 80 70 60 50 30 20
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值