数据结构双向循环链表和内核链表(数据结构二)

一.双向循环链表

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");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java.L

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值