2019年8月1日星期四(数据结构)
2019年8月1日星期四
一. 双向链表与双向循环链表区别?
1. 双向循环链表最后一个节点的后继指向头节点,头节点的前驱指针指向最后一个节点。
2. 节点模型?
struct list_node{
int a;
struct list_node*prev;
struct list_node*next;
}
二. 双向循环链表的增删改查?
1.初始化链表头
struct list_node *init_list_head(struct list_node *head)
{
head = (struct list_node *)malloc(sizeof(struct list_node));
head->prev = head;
head->next = head;
return head;
}
2.尾插节点
int tail_add_list(struct list_node *head,int num)
{
struct list_node *Node = NULL;
struct list_node *p = NULL;
Node = (struct list_node *)malloc(sizeof(struct list_node));
Node->a = num;
p = head->prev; //p指向最后一个节点
Node->prev = p;
Node->next = head;
p->next = Node;
head->prev = Node;
return 0;
}
3.头插节点
int head_add_list(struct list_node *head,int num)
{
struct list_node *Node = NULL;
Node = (struct list_node *)malloc(sizeof(struct list_node));
Node->a = num;
Node->next = head->next;
Node->prev = head;
head->next->prev = Node;
head->next = Node;
return 0;
}
4.往前遍历链表
int backward_show_list(struct list_node *head)
{
struct list_node *p = NULL;
for(p=head->prev;p!=head;p=p->prev)
{
printf("p->a = %d\n",p->a);
}
return 0;
}
5.往后遍历链表
int forward_show_list(struct list_node *head)
{
struct list_node *p = NULL;
for(p=head->next;p!=head;p=p->next)
{
printf("p->a = %d\n",p->a);
}
return 0;
}
6.根据特征值搜索节点
void show_node(struct list_node *p)
{
printf("p->a = %d\n",p->a);
return;
}
int search_list_node(struct list_node *head,int num)
{
struct list_node *p = NULL;
/*
for(p=head->next;p!=head;p=p->next)
{
if(p->a == num)
{
show_node(p);
return 0;
}
}
*/
for(p=head->prev;p!=head;p=p->prev)
{
if(p->a == num)
{
show_node(p);
return 0;
}
}
return -1;
}
7.根据特征值删除节点
int delete_list_node(struct list_node *head,int num)
{
struct list_node *p = NULL;
struct list_node *q = NULL;
for(q=head,p=head->next;p!=head;q=p,p=p->next)
{
if(p->a == num)
{
q->next = p->next;
p->next->prev = q;
free(p);
return 0;
}
}
return -1;
}
8.删除链表
int delete_list(struct list_node *head)
{
struct list_node *p = NULL;
struct list_node *q = NULL;
head->prev->next = NULL;
for(p=q=head;p!=NULL;p=q)
{
q = p->next;
free(p);
}
return 0;
}
三. 双向链表实例。 -> 触摸屏点击切换图片,第一张切换到最后一张,最后一张切换到第一张。
#include <stdlib.h> #include <string.h> #include <sys/types.h> #include <dirent.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <linux/input.h> #include <strings.h> struct list_node{ char picname[20]; struct list_node *next; struct list_node *prev; }; struct list_node *init_list_head(struct list_node *head) { head = (struct list_node *)malloc(sizeof(struct list_node)); head->prev = head; head->next = head; return head; } int tail_add_list(struct list_node *head,char *picname) { struct list_node *Node = NULL; struct list_node *p = NULL; Node = (struct list_node *)malloc(sizeof(struct list_node)); strcpy(Node->picname,picname); p = head->prev; //p指向最后一个节点 Node->prev = p; Node->next = head; p->next = Node; head->prev = Node; return 0; } void show_bmp(char *name) { char bmp_buf[800*480*3];//BMP格式图片缓冲区 char lcd_buf[800*480*4];//LCD液晶缓冲区 char show_buf[800*480*4]; int ret,lcd; int i,j,x,y; //1. 访问BMP图片 FILE *fp = fopen(name,"r"); if(fp == NULL) printf("fopen error!\n"); //2. 跳过BMP图片的54个头数据 ret = fseek(fp,54,SEEK_SET); if(ret != 0) printf("fseek error!\n"); //3. 读取BMP图片的数据 ret = fread(bmp_buf,sizeof(bmp_buf),1,fp); if(ret != 1) printf("fread error!\n"); //4. 访问LCD液晶 lcd = open("/dev/fb0",O_WRONLY); if(lcd < 0) printf("open error!\n"); //5. 像素点赋值 for(i=0,j=0;i<800*480*4;i+=4,j+=3) { lcd_buf[i] = bmp_buf[j]; lcd_buf[i+1] = bmp_buf[j+1]; lcd_buf[i+2] = bmp_buf[j+2]; lcd_buf[i+3] = 0; } //6. 上下翻转 for(y=0;y<480;y++) { for(x=0;x<800*4;x++) { show_buf[(479-y)*800*4+x] = lcd_buf[y*800*4+x]; } } //7. 将图片数据写入到LCD液晶屏幕上 ret = write(lcd,show_buf,sizeof(show_buf)); if(ret != sizeof(show_buf)) printf("write error!\n"); //8. 关闭设备与文件 close(lcd); fclose(fp); } int main(int argc,char *argv[]) { //0. 初始化链表 struct list_node *head = NULL; head = init_list_head(head); //1. 打开目录 DIR *dp = opendir("./pic/"); if(dp == NULL) printf("opendir error!\n"); //2. 切换目录 chdir("./pic/"); //3. 读取目录中内容 struct dirent *ep = NULL; while(1) { ep = readdir(dp); if(ep == NULL) break; if(ep->d_name[0] == '.') continue; tail_add_list(head,ep->d_name); } //4. 显示第一张图片 struct list_node *p = head->next; show_bmp(p->picname); //5. 访问触摸屏设备 int fd = open("/dev/input/event0",O_RDONLY); if(fd < 0) printf("open event0 error!\n"); //6. 不断读取触摸屏数据 struct input_event buf; int x; while(1) { bzero(&buf,sizeof(buf)); read(fd,&buf,sizeof(buf)); if(buf.type == EV_ABS && buf.code == ABS_X) x = buf.value; if(buf.type == EV_KEY && buf.code == BTN_TOUCH && buf.value == 0) { if(x < 400) //点击了左边 { if(p == head->next) { p = head->prev; } else{ p = p->prev; } show_bmp(p->picname); } if(x > 400) //点击了右边 { if(p == head->prev) { p = head->next; } else{ p = p->next; } show_bmp(p->picname); } } } close(fd); closedir(dp); return 0; }
四. linux内核链表。
1. 什么是内核链表?
内核链表与传统链表不一样,传统链表的数据域与指针域都是用户自定义,但是内核链表数据域是用户自定义,但指针域是在内核链表的头文件中已经定义好了。
内核链表是一种双向循环,头节点无效的链表。
2. 内核链表模型与传统链表差异?
内核链表头文件: kernel_list.h
既然内核链表指针域已经定义好,那么究竟这个指针域是长什么样子?
该指针域是一个结构体来的:
struct list_head{
struct list_head *next; -> 后继指针
struct list_head *prev; -> 前驱指针
};
3. 设计内核链表模型?
struct list_node{
/* 自定义数据域 */
int a;
/* 头文件中已经定义好的指针域 */
struct list_head list;
}
五. 内核链表的增删改查?
0. 设计节点
struct list_node{
int a;
struct list_head list; //指针域,里面已经包含了两个指针
};
1. 初始化链表?
1)为头节点申请堆空间。
head = (struct list_node *)malloc(sizeof(struct list_node));
2)为头节点成员赋值
INIT_LIST_HEAD(&(head->list));
解析: kernel_list.h 49行:
#define INIT_LIST_HEAD(ptr)
do{
(ptr)->next = (ptr); -> ptr应该是"struct list_head *"这种指针类型变量
(ptr)->prev = (ptr);
}while(0)
2. 尾插数据
1)为新节点申请空间
Node = (struct list_node *)malloc(sizeof(struct list_node));
2)为新节点数据域赋值
Node->a = num;
3)调用尾插函数
list_add_tail(&(Node->list),&(head->list));
解析:
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
new: 新节点的指针域的地址
head: 头节点的指针与的地址
__list_add函数例程:
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
3. 头插数据
1)为新节点申请空间
Node = (struct list_node *)malloc(sizeof(struct list_node));
2)为新节点数据域赋值
Node->a = num;
3)调用头插函数
list_add(&(Node->list),&(head->list));
解析:
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
4. 往后遍历链表
===========================================================
功能:list_for_each - iterate over a list
解析:
#define list_for_each(pos, head) -> 这个宏定义就是一个循环来的!
for(pos=(head)->next;pos != (head);pos=pos->next)
pos: 指向每一个指针域结构体的地址
head: 头节点指针的地址
功能:list_entry – get the struct for this entry -> 根据小结构体(指针)地址得到大结构体(数据+指针)的地址
解析:
#define list_entry(ptr, type, member)
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
ptr: 指向小结构体的地址的指针变量
type: 大的结构体的数据类型
member:在大的结构体中,小的结构体叫什么名字
例子1:20%
int forward_show_list(struct list_node *head)
{
struct list_head *p = NULL;
struct list_node *tmp = NULL;
list_for_each(p,&(head->list))
{
tmp = list_entry(p,struct list_node,list);
printf("tmp->a:%d\n",tmp->a);
}
return 0;
}
===========================================================
功能: list_for_each_entry - iterate over list of given type
其实就是list_entry和list_for_each的相结合。
解析:
#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: 指向大的结构体的地址指针变量
head: 头节点的指针域的地址
member: 在大的结构体中,小的结构体叫什么名字
例子2: 80%
int forward_show_list(struct list_node *head)
{
struct list_node *p = NULL;
list_for_each_entry(p,&(head->list),list)
{
printf("p->a:%d\n",p->a);
}
return 0;
}
===========================================================
5. 往前遍历链表
功能:list_for_each_prev - iterate over a list backwards
解析:
#define list_for_each_prev(pos, head)
for (pos = (head)->prev; pos != (head);
pos = pos->prev)
pos: 指向每一个指针域结构体的地址
head: 头节点指针的地址
例子:
int backward_show_list(struct list_node *head)
{
struct list_head *p = NULL;
struct list_node *tmp = NULL;
list_for_each_prev(p,&(head->list))
{
tmp = list_entry(p,struct list_node,list);
printf("tmp->a:%d\n",tmp->a);
}
return 0;
}
===========================================================
练习2:使用内核链表做通讯录
要求1: 每一个节点代表一个用户
要求2: 每一个用户包含姓名,电话号码,学校,序号,关系: 同学classmate 朋友friend 亲人 family
要求3: 根据序号查找,根据关系来查找。
要求4: 根据输入序号/姓名删除电话号码
6. 根据特征值来删除链表节点
===========================================================
功能: list_for_each_safe - iterate over a list safe against removal of list entry -> p和q一起遍历。
解析:
#define list_for_each_safe(pos, n, head)
for (pos = (head)->next, n = pos->next; pos != (head);
pos = n, n = pos->next)
功能: 从链表总删除节点 -> 这个函数仅仅是把节点脱离开链表,并且保证链表不断开,但是内存没有释放。
解析:
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = (void *) 0;
entry->prev = (void *) 0;
}
__list_del函数例程:
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
next->prev = prev;
prev->next = next;
}
例子1:
int delete_list_node(struct list_node *head,int num)
{
struct list_head *p = NULL;
struct list_head *q = NULL;
struct list_node *tmp = NULL;
list_for_each_safe(p,q,&(head->list))
{
tmp = list_entry(p,struct list_node,list);
if(tmp->a == num)
{
list_del(p); -> p指向需要删除的节点的小结构体
free(tmp); -> tmp指向需要删除的节点的大结构体
}
}
return 0;
}
===========================================================
功能: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)
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))
例子2:
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->a == num)
{
list_del(&(p->list));
free(p);
}
}
return 0;
}
7. 删除整条链表
int 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)
{
list_del(&(p->list));
free(p);
}
list_del(&(head->list));
free(head);
return 0;
}