一.双向循环链表
1.概念
单向的链表有一个缺点,就是只能沿着一个方向访问,往回访问及其不方便。双向循环链表可以解决这个问题
双向循环链表在单链表的基础上,每个节点添加一个指针域,这个指针域指向前一个节点。
对于双向循环链表的任何一个节点,都可以往前/往后访问,往前和往后访问的效率相同
双向循环链表付出多维护一个指针域的代价,带来了访问效率的提高和平稳
2.实现和使用
定义dlist.h头文件
#ifndef _DLIST_H_
#define _DLIST_H_
typedef int T;
typedef struct node{
T date;
struct node *prve;
struct node *next;
}dnode_t,*dlist_t;
dlist_t create_emptydlist();//初始化双向循环链表
dlist_t insert(dlist_t p,T dt);//指定位置后插入节点
dlist_t insert_by_tail(dlist_t head,T dt);//从尾部插入
dlist_t insert_by_head(dlist_t head,T dt);//从头部插入
dlist_t search_by_index(dlist_t head,int index);//按位置查找
dlist_t search_by_date(dlist_t head,T dt);//按值查找
int delete_by_index(dlist_t head,int index);//按位置删除
int delete_by_date(dlist_t head,T dt);//按值删除
void look(dlist_t head);//遍历链表
void clean(dlist_t head);//清空链表
void destroy(dlist_t *phead);//销毁链表
#endif
初始化双向循环链表
dlist_t create_emptydlist()
{
dlist_t head = (dlist_t)malloc(sizeof(dnode_t));
if(head)
{
//head->head = -1;
//空链表头节点的前置和后置指针都指向自己
head->prve = head;
head->next = head;
}
return head;
}
指定位置后插入节点
dlist_t insert(dlist_t p,T dt)
{
dlist_t newnode = (dlist_t)malloc(sizeof(dnode_t));
if(newnode)
{
newnode->date = dt;
newnode->next = p->next;//新节点的后置指针指向插入位置的下一个节点
newnode->prve = p;//新节点的前置指针指向插入的位置
p->next = newnode;//插入位置节点的后置指针指向新节点
newnode->next->prve = newnode;//插入位置的下一个节点的前置指针指向新节点
}
return newnode;
}
从头部插入
dlist_t insert_by_head(dlist_t head,T dt)
{
return insert(head,dt);
}
从尾部插入
dlist_t insert_by_tail(dlist_t head,T dt)
{
return insert(head->prve,dt);
}
按位置查找
dlist_t search_by_index(dlist_t head,int index)
{
int i;
dlist_t p = head->next;
while(p!=head)
{
if(i==index)
return p;
p = p->next;
i++;
}
return NULL;
}
按值查找
dlist_t search_by_date(dlist_t head,T dt)
{
dlist_t p = head->next;
while(p!=head)
{
if(p->date==dt)
return p;
p = p->next;
}
return NULL;
}
按位置删除
int delete_by_index(dlist_t head,int index)
{
dlist_t del_node = search_by_index(head,index);
if(!del_node)
return -1;
del_node->prve->next = del_node->next;
del_node->next->prve = del_node->prve;
free(del_node);
del_node = NULL;
return 0;
}
按值删除
int delete_by_date(dlist_t head,T dt)
{
dlist_t del_node = search_by_date(head,dt);
if(!del_node)
return -1;
del_node->prve->next = del_node->next;
del_node->next->prve = del_node->prve;
free(del_node);
del_node = NULL;
return 0;
}
遍历链表
void look(dlist_t head)
{
dlist_t p;
p = head->next;
while(p!=head)
{
printf("%d ",p->date);
p = p->next;
}
printf("\n");
}
清空链表
void clean(dlist_t head)
{
dlist_t p = head->next;
head->next = head;
head->prve = head;
dlist_t q = NULL;
while(p!=head)
{
q = p;
p = p->next;
free(q);
}
p = q = NULL;
}
销毁链表
void destroy(dlist_t *phead)
{
clean(*phead);
free(*phead);
*phead = NULL;
}
二.内核链表
1.概念和原理
内核链表是一种双向循环链表,在Linux内核中使用
内核中大量使用链表,如果用之前的双向链表来实现,存储每一种数据的链表都必须重新设计
但是链表中如果仅仅是数据域不同,链表操作大部分是相同的,为了减少链表中的重复冗余的代码,内核中设计了内核链表来实现链表功能
内核链表的节点中只有指针域,没有数据域,这些节点构成一个双向循环链表
内核链表在使用时将链表的节点作为一个结构体的成员
当我们找到任意一个节点的地址时,就可以计算出它外部结构体的地址,从而来访问结构体中数据成员
内核链表实现了将链表中指针域相关操作提取出来,避免代码的重复编写,降低了代码的冗余度
2.语法
内核链表是内核中的一个工具,内核中已经将内核链表的代码实现,我们只需要使用即可
(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)删除节点
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = (void *) 0;//(void *) 0相当于空地址
entry->prev = (void *) 0;
}
(5)合并链表
static inline void list_splice(struct list_head *list, struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head);
}
(6)结构体成员的地址计算结构体的地址
ptr:结构体成员地址
type:结构体类型
member:结构体成员名
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
(7)遍历
#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)
(8)遍历并求出外部结构体的地址
#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))
#define list_for_each_entry_safe(pos, n, head, member)
for (pos = list_entry((head)->next, typeof(*pos), member), n = list_entry(pos->member.next, typeof(*pos), member) ;
&pos->member != (head) ;
pos = n, n = list_entry(n->member.next, typeof(*n), member))
3.实现和使用
初始化内核链表
kernel list_init()
{
kernel p = (kernel)malloc(sizeof(nodelist));
if(p)
{
INIT_LIST_HEAD(&p->list);
}
return p;
}
插入
void insert(kernel head,T dt)
{
kernel newnode = list_init();
if(newnode)
{
newnode->date = dt;
list_add(&newnode->list,&head->list);
}
}
按值修改
void change_date(kernel head,T value,T dt)
{
struct list_head *pos = NULL,*n = NULL;
kernel tmp = NULL;
list_for_each_safe(pos,n,&head->list)
{
tmp = list_entry(pos,nodelist,list);
if(tmp->date == value)
tmp->date = dt;
}
}
按值删除
void delete_date(kernel head,T dt)
{
kernel pos = NULL,n = NULL;
list_for_each_entry_safe(pos,n,&head->list,list)
{
if(pos->date == dt)
{
list_del(&pos->list);
free(pos);
break;
}
}
}
遍历
void look(kernel head)
{
struct list_head *pos = NULL;
kernel tmp = NULL;
list_for_each(pos,&head->list)
{
tmp = list_entry(pos,nodelist,list);
printf("%d ",tmp->date);
}
printf("\n");
}