一、链表的种类。
1、传统链表一共有4种,分别是单向/单向循环/双向/双向循环链表。
因为这些链表增删改查过程都是用户自己设计的,所以称之为传统链表。
2、非传统链表只有1种,就是内核链表。
因为内核链表的增删改查已经写好了,用户只需要直接调用即可。
优点:用户只需要直接调用即可,不需要关心实现过程。
缺点:用户不能修改函数接口。
二、什么是内核链表?
1、内核链表本身就是一条双向循环链表。
2、内核链表头节点依然是无效。
3、内核链表数据域是由用户自己设计,指针域虽然也是跟双向循环链表一样,有一个前驱指针,有一个后继指针,但是指针域在linux下已经定义好了。所以用户不能自己去设计,只能使用系统定义好的。
三、内核链表模型?
即使是内核链表,也是由两部分组成的。
struct list_node{
/* 数据域 */ --> 由用户定义
/* 指针域 */ --> 已经在内核链表的头文件(kernel_list.h)中写好了,如果代码中使用内核链表,那么就必须包含#include "kernel_list.h"
};
1、指针域在头文件中长什么样子的?
struct list_head {
struct list_head *next; //后继指针
struct list_head *prev; //前驱指针
};
2、内核链表节点该如何设计?
struct list_node{
int data;
struct list_head list;
};
四、内核链表的增删改查。
案例: 假设有条内核链表,每一个节点存储int类型的数据,请写出该链表的实现。
#include <stdio.h>
#include <stdlib.h>
#include "kernel_list.h"
//设计节点
struct list_node{
int data;
struct list_head list;
};
struct list_node *init_list_head()
{.
//1. 为头节点申请空间
struct list_node *head = malloc(sizeof(struct list_node));
if(head == NULL)
printf("malloc head error!\n");
//2. 为头节点赋值。
INIT_LIST_HEAD(&(head->list));
return head;
}
void insert_node_to_tail(struct list_node *head,int num)
{
//1. 为新节点申请空间
struct list_node *new = malloc(sizeof(struct list_node));
if(new == NULL)
printf("malloc new error!\n");
//2. 为新节点数据域赋值
new->data = num;
//3. 为新节点指针域赋值
list_add_tail(&(new->list),&(head->list));
return;
}
void insert_node_to_head(struct list_node *head,int num)
{
//1. 为新节点申请空间
struct list_node *new = malloc(sizeof(struct list_node));
if(new == NULL)
printf("malloc new error!\n");
//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);
}
}
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)
{
list_del(&(p->list));
free(p);
return 0;
}
}
return -1;
}
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);
}
free(head);
return 0;
}
int main(int argc,char *argv[])
{
//1. 初始化链表
struct list_node *head = NULL;
head = init_list_head();
//2. 尾插
insert_node_to_tail(head,10);
insert_node_to_tail(head,20);
insert_node_to_tail(head,30);
insert_node_to_tail(head,40);
//3. 头插
insert_node_to_head(head,8);
insert_node_to_head(head,5);
insert_node_to_head(head,3);
//4. 遍历链表
show_list_node(head);
//5. 删除节点
delete_list_node(head,20);
show_list_node(head);
//6. 删除整条链表
delete_list(head);
return 0;
}
练习1:
使用内核链表存储以下的数据,并按要求输出想要的内容。
书名 总页数 封面颜色
-------------------------------------------
C language 428 red
C++ language 352 yellow
java language 569 blue
C# language 245 red
-------------------------------------------
要求1: 输出C语言的封面颜色。
要求2: 输出所有小于400页的书名。
要求3: 输出所有封面为红色的书名。
要求4: 把C++这本书删除。
要求5: 再输出所有的信息。
五、链式栈。
1、什么是栈?
在一条存储数据的链表中,插入节点与删除节点都是在同一端进行的,那么这种结构就是栈。
例如生活例子:拿碗,上次最后一个放进去的碗,下一次一定是第一个被拿出来的,这种逻辑后进先出。
往链式栈中插入数据:压栈/入栈
从链式栈中删除节点:出栈
2、设计栈的结构体。
1)栈的节点结构体。
struct node{
int data; //节点的数据域
struct node *next; //节点的指针域
};
2)栈的管理结构体
struct stack{
struct node *top; //指向栈顶的元素节点
int size; //统计当前栈中有多少个节点
};
-----------------------------------------------------------------
#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 error!\n");
//2. 为管理结构体赋值
s->top = NULL;
s->size = 0;
return s;
}
int 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; //更新栈顶
s->size++;
return 0;
}
int show_stack(struct stack *s)
{
struct node *tmp = NULL;
for(tmp=s->top;tmp!=NULL;tmp=tmp->next)
{
printf("tmp->data:%d\n",tmp->data);
}
return 0;
}
int pop_stack(struct stack *s)
{
//1. 出栈之前,先判断一下栈是否为空栈
if(s->top == NULL)
return -1;
//2. 取出栈顶节点的数据
struct node *tmp = s->top;
int ret = tmp->data;
//3. 更换栈顶。
s->top = s->top->next;
//4. 释放原来的栈顶
free(tmp);
//5. 节点的个数减1
s->size--;
//6. 将ret返回。
return ret;
}
void destroy_stack(struct stack *s)
{
free(s);
return;
}
int main(int argc,char *argv[])
{
//1. 初始化一条空栈。
struct stack *s = NULL;
s = init_stack();
//2. 压栈
push_stack(s,10); //会放在最底
push_stack(s,20);
push_stack(s,30); //会放在最上方
//3. 遍历栈里面的数据
show_stack(s);
//4. 出栈。
int ret;
while(s->size!=0)
{
ret = pop_stack(s);
printf("out:%d\n",ret);
show_stack(s);
}
//5. 释放管理结构体
destroy_stack(s);
return 0;
}
练习2: 程序执行之后,实现从键盘中获取一个十进制数字,程序就会求出对应的八进制的数出来,并打印终端上。
./xxxx
123 --> 手动输入一个十进制数字
173 --> 就会自动帮你求出123对应的八进制数字173
int main(int argc,char *argv[])
{
//1. 初始化空栈
struct stack *s = NULL;
s = init_stack();
//2. 输入一个十进制的数字
int n;
scanf("%d",&n);
while(n > 0)
{
push_stack(s,n%8);
n /= 8;
}
//3. 出栈
int ret;
while(s->top!=NULL)
{
ret = pop_stack(s);
printf("%d",ret);
}
printf("\n");
//4. 释放空间
destroy_stack(s);
return 0;
}
六、队列。
1、什么是队列?
在一条存储的结构中,插入节点与删除节点分别在两端进行,例如:插入数据在队尾处理,删除数据在队头处理,那么这种逻辑就叫做队列。
其特点是先进先出,后进后出。
插入数据 --> 入队
删除数据 --> 出队
2、设计队列的结构体
1)队列节点结构体
struct node{
int data;
struct node *next;
};
2)队列管理结构体
struct queue{
struct node *head; //指向队头
struct node *tail; //指向队尾
int size; //统计当前队列中节点的个数
};
----------------------------------------------------
#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 q error!\n");
//2. 为管理结构体赋值
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
int 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;
//3. 分情况讨论
if(q->size == 0) //如果插入的节点是第一个节点,则需要特殊处理
{
//如果队列中只有你一个人,那么队头队尾都是你。
q->head = new;
q->tail = new;
}
else{
q->tail->next = new; //原来的那个队尾的指针域指向这个新节点
q->tail = new; //队尾就是指向这个新节点
}
q->size++;
return 0;
}
int show_queue(struct queue *q)
{
//1. 先判断是不是空队
if(q->size == 0)
return -1;
//2. 遍历队列
struct node *tmp = NULL;
for(tmp=q->head;tmp!=NULL;tmp=tmp->next)
{
printf("%d -> ",tmp->data);
}
printf("\n");
return 0;
}
int out_queue(struct queue *q,int *ret)
{
//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;
}
//4. 保存以下将要释放的节点的数据
*ret = tmp->data;
//5. 释放tmp
free(tmp);
//6. 节点个数-1
q->size--;
return 0;
}
void destroy_queue(struct queue *q)
{
free(q);
return;
}
int main(int argc,char *argv[])
{
//1. 初始化一条空队
struct queue *q = NULL;
q = init_queue();
//2. 入队
in_queue(q,10);
in_queue(q,20);
in_queue(q,30);
in_queue(q,40);
//3. 遍历队列
show_queue(q);
//4. 出队
int ret;
while(q->size!=0)
{
out_queue(q,&ret);
printf("out queue:%d\n",ret);
show_queue(q);
}
//5. 销毁管理结构体
destroy_queue(q);
return 0;
}
----------------------------------------------------
练习3: 写一个程序,不断处于输入整数状态
如果输入的正整数,则将该正整数入队。 --> 模拟客户取号
如果输入是负整数,则输出队列中头节点的值。 --> 柜台工作人员按下按键,排在最开头的那个人可以办理业务。
如果输入是一个0,则程序退出。 -> 今天收工
int main(int argc,char *argv[])
{
//1. 初始化一条空队
struct queue *q = NULL;
q = init_queue();
//2. 不断输入整数的状态
int n;
int ret;
while(1)
{
scanf("%d",&n);
if(n > 0)
{
in_queue(q,n);
show_queue(q);
}
else if(n < 0)
{
out_queue(q,&ret);
printf("pls %d usr ....\n",ret);
show_queue(q);
}
else{
break;
}
}
//3. 因为有可能队列还有节点,所以释放。
if(q->size != 0)
{
delete_queue(q);
}
destroy_queue(q);
return 0;
}