单链表
概念与理解
- 链表指的是一种链式结构,就像是一个一个人一样手拉着手,那么它的作用就是将数据一个一个的使用链子(地址)连接起来,相对于人手拉手如下图所示 ,单链表有几个概念如下所示:
- 头指针:声明结构体的首地址,如下我们声明了一个head结构体变量,也就是链表的头节点,并添加了五个节点
- 节点:不同的节点连接起来形成了链表,一个节点包含一个数据域和指针域
- 数据域:用于存放数据
- 指针域:用于存放指向下一个链表节点的地址
- NULL(空指针):单链表结束的标志,而且指针如果不存储数据最好还是要指向空,防止出现野指针、或则内存异常之类的情况
typedef struct LinkNode
{
Datatype data;
struct LinkNode* next;
}Node,*Link;
// 上述定义下 struct LinkNode knode 等价于 Node knode
struct LinkNode head;
... // 对链表进行初始化
注意:很多人可能对链表似乎很难理解,依本人愚见需要理解三点就能掌握好它。
第一点:需要知道指针用法,指针就是地址,地址就是指针。比如下面的代码就是整数型的指针变量p指向了整数型变量a的地址,当然指针也是有自己的地址的。这样就可以随意使用(改变,运算)变量a的值了
第二点:需要知道不管什么语言,赋值语句比如a=b,那么只会改变a的值(这里a和b可以是基础类型的变量、指针变量、数组名、结构体变量、联合体和枚举类型变量)
第三点:学习使用调试功能,学习链表时,学会使用debug 功能可以看到地址的变化更加有利于深入理解概念,对于那些追求知其然和知其所以然的有很大帮助
int *p = NULL;
int a = 10;
p = &a;
示例
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int Datatype;
typedef struct LinkNode
{
Datatype data;
struct LinkNode *next;
} Node, *Link;
struct LinkNode *insertSequence(struct LinkNode *head, int data);
struct LinkNode *heaDinsert(struct LinkNode *head, int data);
struct LinkNode *taiLinsert(struct LinkNode *head, int data);
int LinklistNumber(Link head);
bool Linklistlook(Link head, int data);
Link ascending_order(Link head);
struct LinkNode *reverseLinklist(struct LinkNode *head);
Link reLinklist(Link head);
struct LinkNode *deleteNode(struct LinkNode *head, int data);
struct LinkNode *deleteRepeatNode(struct LinkNode *head);
void printLink(struct LinkNode *pt);
int main(void)
{
system("chcp 65001");
int n, num[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, numCount = 5;
char ch;
struct LinkNode *head = NULL;
printf("********************头插法创建链表************************\n");
for (int i = 0; i < numCount; i++)
{
// head = taiLinsert(head,num[i]);
head = heaDinsert(head, num[i]);
}
printf("********************打印链表************************\n");
printLink(head);
printf("********************计算链表节点的个数************************\n");
int LinkNum = LinklistNumber(head);
printf("链表节点的个数:%d\n", LinkNum);
printf("********************寻找链表节点************************\n");
Linklistlook(head, 3);
printf("********************链表排序************************\n");
Link sx = ascending_order(head);
printLink(sx);
printf("********************反转链表************************\n");
Link st = reLinklist(head);
printLink(st);
printf("********************删除带顺序的重复节点************************\n");
Link sp = deleteRepeatNode(st);
printLink(sp);
printf("********************删除链表中的指定节点num[3]************************\n");
struct LinkNode *sd = deleteNode(sp, 4);
printLink(sd);
// windows下先不释放内存
printf("finish!\n");
return 0;
}
// 创建一个顺序 (依赖于n) 链表 头插法
struct LinkNode *insertSequence(struct LinkNode *head, int data)
{
// 新建链表头指针 难道不是指向是直接赋值 也就是NULL
struct LinkNode *new_ = head;
// 为这个新指针申请一个结构体空间
new_ = (struct LinkNode *)malloc(sizeof(struct LinkNode));
// 空指针的数据域 等于 输入的数据
new_->data = data;
// 如果 输入的头指针是空 那就更新 new
// 不为空
if (head == NULL)
{
head = new_;
// printf("head:%d\n",head->data);
}
else
{
new_->next = head;
head = new_;
}
// printf("head:%p\n",head);
return head;
}
// 创建一个链表依赖于用户的输入 头插法
struct LinkNode *heaDinsert(struct LinkNode *head, int data)
{
// 新建节点
struct LinkNode *L_new;
// 为这个新节点申请一个结构体空间
L_new = (struct LinkNode *)malloc(sizeof(struct LinkNode));
// 必须把第一次申请的节点的next域赋值为NULL 作为打印链表的判断条件
L_new->next = NULL;
// 空指针的数据域 等于 输入的数据
L_new->data = data;
printf("%p\n", &L_new->data);
// 如果 输入的头指针是空 那就更新 new
// 不为空
if (head == NULL)
{
head = L_new;
// printf("head:%d\n",head->data);
}
else
{
L_new->next = head;
head = L_new;
}
// printf("head:%p\n",head);
return head;
}
// 创建一个链表依赖于用户的输入 尾插法
struct LinkNode *taiLinsert(struct LinkNode *head, int data)
{
// 新建节点
struct LinkNode *L_new;
struct LinkNode *L_new_prec;
// 为这个新节点申请一个结构体空间
L_new = (struct LinkNode *)malloc(sizeof(struct LinkNode));
// 必须把第一次申请的节点的next域赋值为NULL 作为打印链表的判断条件
L_new->next = NULL;
// 空指针的数据域 等于 输入的数据
L_new->data = data;
// 如果 输入的头指针是空 那就更新 new
if (head == NULL)
{
printf("head is NULL !\n");
head = L_new; // 让head指向头结点 只有第一次循环是有作用的
}
else // 不为空
{
printf("head no NULL !\n");
L_new_prec->next = L_new;
}
L_new_prec = L_new; // L_new_prec链表的尾结点 L_new_prec和head已经是同时指向了同一块内存地址
return head;
}
// 打印链表
void printLink(struct LinkNode *head)
{
struct LinkNode *p = head;
while (p != NULL) // 这里必须非常注意尾节点的next域是不是NULL 否则会发生死循环
{
printf("%d", p->data);
p = p->next; // 不能改为P++ 因为地址不连续
}
printf("\n");
}
// 计算链表的节点个数
int LinklistNumber(Link head)
{
int n;
Link p = head;
while (p != NULL)
{
n++;
p = p->next;
}
return n;
}
// 链表的查找元素
bool Linklistlook(Link head, int data)
{
int n = 1;
Link p = head;
while (p != NULL)
{
if (p->data == data)
{
printf("找到了,%d在第%d节点\n", data, n);
return true;
}
p = p->next;
n++;
}
printf("未找到\n");
return false;
}
// 链表排序 升序
Link ascending_order(Link head)
{
Link p = head;
Link cur = NULL;
int i = 0;
while (p != NULL)
{
if (p->next == NULL)
break; // 需要两层for循环
if ((p->data) < (p->next->data))
{
int temp = p->data;
p->data = p->next->data;
p->next->data = temp;
}
p = p->next;
printf("i:%d\n", i++);
}
return head;
}
// 反转链表 头插法
struct LinkNode *reverseLinklist(struct LinkNode *head)
{
// 新链表的头指针
struct LinkNode *newhead = NULL;
// 需要头插的结点
struct LinkNode *current = head;
while (current)
{
// 保存需要头插结点的下一个结点
struct LinkNode *next = current->next;
// 将curren头插到新链表
current->next = newhead;
// 将current地址 给 新链表
newhead = current;
// 将next的地址 给 current
current = next;
}
return newhead;
}
Link reLinklist(Link head)
{
Link new = NULL;
Link cur = head;
while (cur != NULL)
{
Link nextLink = cur->next; // 用新的节点保存旧链表当前节点的下一个节点
cur->next = new; // 头插法
new = cur;
cur = nextLink; // cur用完之后将cur移到旧链表下一个节点
}
return new;
}
// 删除链表中指定结点
struct LinkNode *deleteNode(struct LinkNode *head, int data)
{
// 删除头结点
struct LinkNode *current = head;
if (current->data == data)
{
current = current->next;
printf("rojek\n");
return current;
}
printf("rojek1\n");
while (current != NULL)
{
if (current->next->data == data)
{
current->next = current->next->next;
return head;
}
current = current->next;
}
printf("没有需要删除的节点\n");
return head;
}
// 删除带有顺序的链表重复结点 快慢指针
// current 与 循环p对比 current指向下一个节点
struct LinkNode *deleteRepeatNode(struct LinkNode *head)
{
struct LinkNode *current = head;
while (current != NULL)
{
struct LinkNode *p = current;
while (p->next != NULL)
{
if (p->next->data == current->data)
{
p->next = p->next->next;
}
else
{
p = p->next;
}
}
current = current->next;
}
return head;
}
双向链表
概念与理解
- 双链表相对于单链表每个节点多了一个指针域,因此可以将每个节点的指针域分别叫做前驱和后继,它还可以进行双向的遍历。简图如下所示:
从上述双链表的简图中可以看出双链表的一个节点有两个指针域和一个数据域,它的几个概念主要如下所示:
节点:包含两个指针域和一个数据域,其中一个指针域也就是prev叫做前驱,另外一个指针域next叫做后继
前驱prev:节点中的其中一个指针域,用于指向前一个节点的数据域
前驱prev:节点中的其中一个指针域,用于指向后 一个节点的数据域
示例
/*
* time : 20240127
* author : 二飞
* description: 双向链表
*/
#include <stdio.h>
#include <stdlib.h>
typedef struct Twolist
{
struct Twolist * prev;
int data;
struct Twolist * next;
}twolist;
twolist * headsert(twolist *mylist, int mybuff);
void printLink(twolist *mylist);
void freelist(twolist *mylist);
int main(void)
{
int buff[10] = {0,1,2,3,4,5,6,7,8,9};
twolist *listhead = NULL;
for(int i = 0; i < 10; i++)
{
listhead = headsert(listhead, buff[i]);
}
printLink(listhead);
freelist(listhead);
return 0;
}
// 头插法
twolist * headsert(twolist *mylist, int mybuff)
{
twolist *tmplist = NULL;
tmplist = (twolist *)malloc(sizeof(twolist));
tmplist->prev = NULL;
tmplist->next = NULL;
tmplist->data = mybuff;
if (mylist == NULL)
{
mylist = tmplist;
}
else
{
tmplist->next = mylist;
mylist->prev = tmplist;
mylist = tmplist;
}
return mylist;
}
// 打印双向循环链表
void printLink(twolist *mylist)
{
twolist *pritlist = NULL;
pritlist = mylist;
while(pritlist->next != NULL)
{
printf("data = %d\n",pritlist->data);
pritlist = pritlist->next;
}
}
void freelist(twolist *mylist)
{
twolist *freelist, *tmplist = NULL;
tmplist = mylist;
while(tmplist != NULL)
{
freelist = tmplist;
tmplist = tmplist->next;
free(freelist);
}
}
linux内核双向循环链表
概念与理解
linux双向循环链表相对于普通的双向链表来说有两点区别
- 每个节点没有数据域
- 尾节点的指针域next指向头节点的指针域next,头节点的指针域prev指向尾节点的prev
使用例子
#include "mylist.h"
#include <stdio.h>
#include <stdlib.h>
struct student
{
struct list_head stu_list;
int ID;
int math;
};
int main()
{
struct student *p;
struct student *q;
struct student stu1;
struct student stu2;
struct list_head *pos;
struct student *pos1;
// 链表的初始化
INIT_LIST_HEAD(&stu1.stu_list);
INIT_LIST_HEAD(&stu2.stu_list);
// 头插法创建stu stu1链表
for (int i = 0; i < 6; i++)
{
p = (struct student *)malloc(sizeof(struct student));
p->ID = i;
p->math = i + 80;
// 头插法
list_add(&p->stu_list, &stu1.stu_list);
// 尾插法
// list_add_tail(&p->list,&stu.list);
}
printf("list_add: \r\n");
list_for_each(pos, &stu1.stu_list)
{
printf("ID = %d,math = %d,pos = %x\n", ((struct student *)pos)->ID, ((struct student *)pos)->math, pos);
}
// 尾插法创建stu stu1链表
for (int i = 0; i < 6; i++)
{
p = (struct student *)malloc(sizeof(struct student));
p->ID = i;
p->math = i + 80;
// 头插法
// list_add(&p->stu_list,&stu1.stu_list);
// 尾插法
list_add_tail(&p->stu_list, &stu2.stu_list);
}
printf("list_add_tail: \r\n");
list_for_each(pos, &stu2.stu_list)
{
printf("ID = %d,math = %d,pos = %x\n", ((struct student *)pos)->ID, ((struct student *)pos)->math,pos);
}
printf("list_for_each_entry: \r\n");
list_for_each_entry(pos1, &stu1.stu_list, stu_list)
{
printf("ID = %d,math = %d,pos1 = %x\n", pos1->ID, pos1->math,pos1);
}
return 0;
}
代码详细解释
- list_for_each
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
将pos作为临时的链表指针,指向新建的链表,进行遍历。指向某个节点后,必须强制将其转化为对应结构体的指针才能够找到对应节点的值。使用该遍历的方式必须将struct list_head xxx放在结构体的首位,当临时的链表指针指向该结构体时,同时也是指向该结构体的第一个成员,如果struct list_head xxx 未放在结构体的首尾,那么该宏定义将不能使用。
struct student
{
struct list_head stu_list;
int ID;
int math;
};
- list_for_each_entry
// 计算member在type中的位置
#define offsetof(type, member) (size_t)(&((type *)0)->member)
//依据member的地址获取type的起始地址
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member)*__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
// 简单的宏定义代替
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
1. list_for_each_entry - iterate over list of given type
2. @pos: the type * to use as a loop cursor.
3. @head: the head for your list.
4. @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
先看**pos = list_entry((head)->next, typeof(*pos), member)**
对应的是container_of((head)->next, typeof(*pos), member)
,其中typeof用于返回括号中的变量类型
接着对应,说明首节点是没有存内容的
const typeof(((typeof(*pos) *)0)->member)* __mptr = (ptr);
(typeof(*pos))((char *)__mptr - offsetof(typeof(*pos),member));
结合上述例子list_for_each_entry(pos1, &stu1.stu_list, stu_list)
,
1、typeof(*pos)等价于typeof(*pos1) 为 struct student 结构体类型
2、typeof(((typeof(*pos) *)0)->member) 等价于 typeof(((struct student *)0)->stu_list) 为 struct list_head
3、const typeof(((typeof(*pos) )0)->member) __mptr = (ptr) 等价于 struct list_head * __mptr = &stu1.stu_list->next 去到首节点的下一个节点的地址
4、(typeof(*pos))((char *)__mptr - offsetof(typeof(*pos),member)) 等价于
(struct list_head)((char )__mptr)-(size_t)(&(struct list_head)0)->stu_list)) 为 找到首节点的下一个节点的地址减去对应结构体成员的地址 得到结构体的首地址,相当于是结构体成员的地址减去结构体成员的偏移量 得到结构体的首地址
5、&pos->member != (head) 循环结束判断条件
注意在实际使用中 内核链表的声明
struct student stu1;
struct student stu2;
内核链表的使用
struct student *p;
struct student *q;
一般不会使用同一个结构体
比如可以这样:
#include "mylist.h"
#include <stdio.h>
#include <stdlib.h>
struct student
{
struct list_head stu_list;
int ID;
int math;
};
struct company
{
struct list_head com_list;
};
int main()
{
struct student *p;
struct student *q;
struct company stu1;
struct company stu2;
struct list_head *pos;
struct student *pos1;
// 链表的初始化
INIT_LIST_HEAD(&stu1.com_list);
INIT_LIST_HEAD(&stu2.com_list);
// 头插法创建stu stu1链表
for (int i = 0; i < 6; i++)
{
p = (struct student *)malloc(sizeof(struct student));
p->ID = i;
p->math = i + 80;
// 头插法
list_add(&p->stu_list, &stu1.com_list);
// 尾插法
// list_add_tail(&p->list,&stu.list);
}
printf("list_add: \r\n");
list_for_each(pos, &stu1.com_list)
{
printf("ID = %d,math = %d,pos = %x\n", ((struct student *)pos)->ID, ((struct student *)pos)->math, pos);
}
// 尾插法创建stu stu1链表
for (int i = 0; i < 6; i++)
{
p = (struct student *)malloc(sizeof(struct student));
p->ID = i;
p->math = i + 80;
// 头插法
// list_add(&p->stu_list,&stu1.stu_list);
// 尾插法
list_add_tail(&p->stu_list, &stu2.com_list);
}
printf("list_add_tail: \r\n");
list_for_each(pos, &stu2.com_list)
{
printf("ID = %d,math = %d,pos = %x\n", ((struct student *)pos)->ID, ((struct student *)pos)->math,pos);
}
printf("list_for_each_entry: \r\n");
list_for_each_entry(pos1, &stu1.com_list, stu_list)
{
printf("ID = %d,math = %d,pos1 = %x\n", pos1->ID, pos1->math,pos1);
}
return 0;
}