一、链表的种类。 传统链表: 单向链表、单向循环链表、双向链表、双向循环链表。 非传统链表: 内核链表。
传统链表与非传统链表区别? 传统链表增删改查,都是需要自己实现。 非传统链表增删改查,都是由内核提供,都已经实现好的了,我们只需要直接调用即可。
二、什么是内核链表? 内核链表与传统链表不一样,传统链表数据域与指针域都是用户自己写的,但是内核链表数据域是用户自己写的,但是指针域是内核链表已经实现好的了,用户不能修改指针域。 内核链表本身就是一条双向循环链表,头节点无效,但是与双向循环链表模型不一样。
三、内核链表节点模型? 即使是内核链表,节点依然由两部分组成: struct list_node{ /* 数据域 */ -> 用户自己写
/* 指针域 */ -> 是被定义在内核链表头文件: kernel_list.h中。
};
1、指针域长什么样子的? struct list_head { struct list_head *next; struct list_head *prev; };
2、假设每一个节点都是存放int类型的数据,那么内核链表节点怎么写? struct list_node{ int data; //数据域 struct list_head list; //指针域 };
四、内核链表实现增删改查? 1、 初始化头节点。 #define INIT_LIST_HEAD(ptr) do { (ptr)->next = (ptr); (ptr)->prev = (ptr); }while(0)
2、 尾插。 功能:list_add_tail – add a new entry 原型:void list_add_tail(struct list_head *new, struct list_head *head) 参数:
-
@new: new entry to be added --> 新节点的指针
-
@head: list head to add it before --> 头节点的指针
3、 头插。 功能: list_add – add a new entry 原型: void list_add(struct list_head *new, struct list_head *head) 参数:
-
@new: new entry to be added --> 新节点的指针
-
@head: list head to add it after --> 头节点的指针
4、遍历。 功能: * list_for_each_entry - iterate over list of given type 原型: #define list_for_each_entry(pos, head, member)
-
@pos: the type * to use as a loop counter. ---> 指向大的结构体的指针变量,类似于传统链表中的p。
-
@head: the head for your list. ---> 头节点的指针。
-
@member: the name of the list_struct within the struct. ---> 在大的结构体中,小的结构体变量叫什么名字。
5、删除。 功能: * list_for_each_entry_safe – iterate over list of given type safe against removal of list entry 原型: #define list_for_each_entry_safe(pos, n, head, member)
-
@pos: the type * to use as a loop counter. ---> 指向大的结构体的指针变量,类似于传统链表中的p。
-
@n: another type * to use as temporary storage ---> 另外一个指向大的结构体的指针变量,类似于传统链表的q。
-
@head: the head for your list. ---> 头节点的指针。
-
@member: the name of the list_struct within the struct. ---> 在大的结构体中,小的结构体变量叫什么名字。
功能: * list_del – deletes entry from list. 原型: void list_del(struct list_head *entry)
-
@entry: the element to delete from the list. -> 需要删除的那个节点的指针
练习1: 现在有一些关于人物信息文件,存储在一个目录中。 文件名以人物的名字来命名的,例如: zhangsan.txt zhangsan,M,10086,pengyou -> 依次是姓名,性别,电话号码,关系 (使用逗号来分开) 程序执行后,将全部人物的信息初始化到链表中,将所有人物的信息都打印出来。(一个人物等于一个节点) 要求使用内核链表来做。
详细代码:
#include "kernel_list.h"
#include <stdio.h>
#include <stdlib.h>
struct list_node{
int data;
struct list_head list; //指针域,是被定义在头文件:kernel_list.h
};
struct list_node *init_list_head()
{
//1. 为头节点申请空间。
struct list_node *head = malloc(sizeof(struct list_node));
//2. 数据域无效、指针域有效。
INIT_LIST_HEAD(&(head->list));
return head;
}
void insert_data_to_tail(struct list_node *head,int num)
{
//1. 为新节点申请空间。
struct list_node *new = malloc(sizeof(struct list_node));
//2. 为数据域赋值。
new->data = num;
//3. 为指针域赋值。
list_add_tail(&(new->list),&(head->list));
return;
}
void insert_data_to_head(struct list_node *head,int num)
{
//1. 为新节点申请空间。
struct list_node *new = malloc(sizeof(struct list_node));
//2. 为数据域赋值。
new->data = num;
//3. 为指针域赋值。
list_add(&(new->list),&(head->list));
return;
}
void show_list_node(struct list_node *head)
{
struct list_node *p = NULL;
list_for_each_entry(p,&(head->list),list)
{
printf("data:%d\n",p->data);
}
return;
}
int delete_list_node(struct list_node *head,int num)
{
struct list_node *p = NULL;
struct list_node *q = NULL;
list_for_each_entry_safe(p,q,&(head->list),list)
{
if(p->data == num)
{
//1. 从链表中将节点脱离了
list_del(&(p->list));
//2. 释放空间。
free(p);
return 0;
}
}
return -1;
}
void delete_list(struct list_node *head)
{
struct list_node *p = NULL;
struct list_node *q = NULL;
list_for_each_entry_safe(p,q,&(head->list),list)
{
//1. 从链表中将节点脱离了
list_del(&(p->list));
//2. 释放空间。
free(p);
}
free(head);
return;
}
int main(int argc,char *argv[])
{
//1. 初始化头节点。
struct list_node *head = NULL;
head = init_list_head();
//2. 尾插。
insert_data_to_tail(head,10);
insert_data_to_tail(head,20);
insert_data_to_tail(head,30);
//3. 头插。
insert_data_to_head(head,8);
insert_data_to_head(head,5);
insert_data_to_head(head,3);
//4. 遍历。
show_list_node(head);
//6. 删除节点。
delete_list_node(head,10);
printf("--------------------\n");
show_list_node(head);
//7. 删除整条链表。
delete_list(head);
return 0;
}
五、链式栈。
1、什么是栈? 在一条储存结构链表中,插入节点与删除节点都是在同一端进行,那么这种结构就是栈。 例如:生活上拿碗例子,上次最后一个放进去的碗,下一次一定是第一个被拿出来,这种逻辑就是后进先出。
往链式栈插入数据:压栈。 从链式栈拿出数据:出栈。 第一个元素:栈顶
2、设计栈管理结构体以及栈节点结构体。 1)栈节点结构体 struct node{ int data; struct node *next; };
2)栈管理结构体。 -> 管理栈顶元素以及统计整个栈元素的个数。 struct stack{ struct node *top; //这个指针就是指向栈顶。 int size; //统计栈中元素个数。 };
3、示例代码。
#include <stdio.h>
#include <stdlib.h>
//节点结构体
struct node{
int data;
struct node *next;
};
//管理结构体
struct stack{
struct node *top; //指向栈顶的元素
int size; //统计栈中元素个数
};
struct stack *init_stack()
{
//1. 为管理结构体申请空间。
struct stack *s = malloc(sizeof(struct stack));
if(s == NULL)
printf("malloc s malloc!\n");
//2. 为管理结构体赋值。
s->top = NULL;
s->size = 0;
return s;
}
void push_stack(struct stack *s,int num)
{
//1. 为新节点申请空间。
struct node *new = malloc(sizeof(struct node));
if(new == NULL)
printf("malloc new error!\n");
//2. 为新节点赋值。
new->data = num;
new->next = s->top; //让new指向原本的栈顶。
s->top = new; //让现在栈顶指向new。
s->size++;
return;
}
void show_stack(struct stack *s)
{
struct node *p = NULL;
for(p=s->top;p!=NULL;p=p->next)
{
printf("p->data:%d\n",p->data);
}
return;
}
int pop_stack(struct stack *s,int *a)
{
//1. 在出栈之前,先判断一下栈是否为空。
if(s->top == NULL)
return -1;
//2. 不为NULL,说明有元素,就可以出栈。
struct node *tmp = s->top;
*a = tmp->data;
//3. 更换栈顶。
s->top = s->top->next;
//4. 释放原来的栈顶。
free(tmp);
//5. 节点的个数-1
s->size--;
return 0;
}
int main(int argc,char *argv[])
{
//1. 初始化一条空栈。(这个栈中一个元素都没有,top指向NULL)
struct stack *s = NULL;
s = init_stack();
//2. 压栈。
push_stack(s,5); //最先放进去的,最后打印,最后出栈。
push_stack(s,8);
push_stack(s,10); //最后放进去的,最先打印,最先出栈。
//3. 遍历栈。
show_stack(s);
printf("====================\n");
//4. 出栈。
int a;
while(s->size!=0) //只要元素个数不为0,都可以出栈。
{
pop_stack(s,&a); //函数作用: 取出栈顶元素,存储在变量a中,然后将栈顶元素删除。
printf("出栈的元素是:%d\n",a);
printf("========================\n");
show_stack(s);
}
//5. 释放栈管理结构体空间。
free(s);
return 0;
}
六、队列
1、什么是队列? 在一条储存结构中,插入节点与删除节点分别在两端进行。例如:插入数据在队尾插入,删除数据在队头删除,那么这种逻辑叫做队列,其特点:先进先出,后进后出。
插入数据 -> 入队 删除数据 -> 出队
2、队列的模型?
3、示例代码
#include <stdio.h>
#include <stdlib.h>
struct node{
int data;
struct node *next;
};
struct queue{
struct node *head;
struct node *tail;
int size;
};
struct queue *init_queue()
{
//1. 为管理结构体申请空间。
struct queue *q = malloc(sizeof(struct queue));
if(q == NULL)
printf("malloc queue error!\n");
//2. 为管理结构体赋值。
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
void in_queue(struct queue *q,int num)
{
//1. 为新节点申请空间。
struct node *new = malloc(sizeof(struct node));
if(new == NULL)
printf("malloc new error!\n");
//2. 为数据域与指针域赋值。
new->data = num;
new->next = NULL; //新排队的人后面肯定没有人。
if(q->size==0) //如果插入的节点是第一个节点,则需要特殊处理。
{
q->head = new; //如果队列中只有你一个人,那么队头队尾都是你
q->tail = new;
}
else{
q->tail->next = new; //让现在队尾的那个元素的指针域指向新节点。
q->tail = new; //让指向队尾的那个指针指向新节点new。
}
q->size++; //元素的个数+1
return;
}
int show_queue(struct queue *q)
{
//1. 判断是否为空队。
if(q->size == 0)
return -1;
//2. 遍历队列。
struct node *p = NULL;
for(p=q->head;p!=NULL;p=p->next)
{
printf("%d -> ",p->data);
}
printf("\n");
return 0;
}
int out_queue(struct queue *q,int *a)
{
//1. 判断队列是否为空。
if(q->size == 0)
return -1;
//2. 不为空,就可以正常地出队。
struct node *tmp = q->head; //tmp就是指向将来出队的那个人。
//3. 分情况讨论
if(q->size == 1) //整个队列就剩一个节点
{
q->head = NULL;
q->tail = NULL;
}
else{ //不止一个节点。
q->head = q->head->next;
}
*a = tmp->data;
free(tmp);
q->size--;
return 0;
}
int main(int argc,char *argv[])
{
//1. 初始化一条空队。
struct queue *q = NULL;
q = init_queue();
//2. 入队。
in_queue(q,10); //队头
in_queue(q,5);
in_queue(q,3); //队尾
//3. 遍历队列。
show_queue(q);
//4. 出队。
int a;
while(q->size!=0)
{
out_queue(q,&a);
printf("出队的元素是:%d\n",a);
show_queue(q);
printf("-----------------\n");
}
//5. 释放。
free(q);
return 0;
}