链表
- 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