目录
一、单向链表
逻辑结构:线性结构
存储结构:链式存储
1、特点
除最后一个结点外,每一个结点都有唯一的后继结点
2、链表的结构体
因为链表中每一个数据的地址是不连续的,所以需要申请多个结点的空间,每个结点包含存数据的数据域,以及存下一个结点地址的指针域

3、单项链表的构建
1)定义单个结点的结构体
由于链表的结构体是不连续的,需要存储下一个结点的首地址,
因此一个结点有数据和地址两个部分
cs
typedef struct node
{
union //用共用体区分头结点和普通结点的数据域
{
int data; //数据节点的数据域
int len; //头结点的长度
};
struct node *next;
}node,*node_p;
2)构建结构体的组成部分,头结点和后继结点
cs
//1、申请单链表(头结点)
node_p create_link()
{
node_p H = (node_p)malloc(sizeof(node));
if(H==NULL)
{
return NULL;
}
H->next = NULL;
H->len = 0; //当只有头结点时,长度为0
return H;
}
//2、申请数据结点
node_p create_node(int value)
{
node_p new_node = (node_p)malloc(sizeof(node));
if(new_node==NULL)
{
printf("申请空间失败\n");
return NULL;
}
new_node->data = value; //给数据结点的数据域赋值
new_node->next = NULL; //数据节点的指针域赋值
return new_node;
}
3)头插
插入在头结点的下一个位置,链表的第一个位置
所有从新结点出发的线都可以先连

cs
//4、头插
void insert_head(node_p H,int value)
{
if(H==NULL)
{
printf("入参为空,请检查\n");
return;
}
//申请新结点
node_p new = create_node(value);
//让新结点指向原来头结点的后继结点
new->next = H->next;
//让头结点指向新结点
H->next = new;
//头结点保存的链表长度自增
H->len++;
}
4)尾插

cs
//5、尾插
void insert_tail(node_p H,int value)
{
if(H==NULL)
{
printf("入参为空,请检查\n");
return;
}
//1、找到尾结点
node_p p = H;
//尾结点的条件p->next==NULL;
while(p->next!=NULL)
{
p = p->next; //找下一个结点
}
//2、申请新的数据结点
node_p new = create_node(value);
//3、让最后一个结点的指针域指向新结点
p->next = new;
//4、因为新结点的指针域在申请后已经置NULL了,无需再次重置
H->len++;
}
5)输出
cs
//6、输出单向链表
void show_link(node_p H)
{
if(H==NULL){return;}
if(empty_link(H))
{
printf("链表为空,无需数超出\n");
return;
}
//从第一个结点开始输出,
//到最后一个结点结束,最后一个结点需要进入循环
node_p p = H->next;
while(p!=NULL)
{
printf("%d->",p->data);
p = p->next;
}
//退出循环说明p走到了NULL,遍历结束整条链表
printf("NULL\n");
}
6)头删
链表的删除需要释放堆区空间

cs
//7、头删
void delete_head(node_p H)
{
if(H==NULL){return;}
if(empty_link(H)){return;}
//1、保存要删除结点的首地址
node_p dele = H->next;
//2、让头结点指向原来的第二个结点
H->next = H->next->next; //H->next = dele->next;
free(dele);
H->len--;
}
7)尾删
尾删需要找到倒数第二个结点,因为单项链表只能向后访问

cs
//8、尾删
void delete_tail(node_p H)
{
if(H==NULL){return;}
if(empty_link(H)){return;}
//由于是单向链表只能向后查找
//所以需要找到倒数第二个结点
node_p p = H;
while(p->next->next!=NULL)
{
p = p->next;
}
//退出循环说明找到倒数第二个结点
node_p dele = p->next; //保存倒数第一个结点
p->next = NULL; //给倒数第二个结点的指针域置空
free(dele);
H->len--;
}
8)按位置插入

cs
//9、按位置插入
void insert_pos(node_p H,int pos,int value)
{
if(H==NULL){return;}
//第一个循环变量i记录位置
if(pos<=0)
{
printf("位置不合理\n");
return;
}
int i = 0;
//定义指针循环结点
node_p p = H;
for(i=0,p=H;i<pos-1;i++,p=p->next)
{
//判断位置不合理的情况
if(p==NULL)
{
printf("插入位置不合理\n");
return;
}
}
node_p new = create_node(value);
//新结点指向pos位置结点
new->next = p->next;
//pos-1位置结点,指向新结点
p->next = new;
H->len++;
}
9)按位置删除

cs
//10、按位置删除
void dele_pos(node_p H,int pos)
{
if(H==NULL){return;}
if(pos<1)
{
printf("位置不合理\n");
return;
}
int i;
node_p p;
//删除pos位置还是找到pos-1位置结点
for(i=0,p=H;i<pos-1;i++,p=p->next);
//找到pos-1位置结点后,判断是否有pos位置的结点
if(p->next==NULL)
{
printf("位置不合理\n");
return;
}
//保存要删除结点的首地址
node_p dele = p->next;
p->next = p->next->next; //p->next = dele->next;
free(dele);
H->len--;
}
11)按位置查找
cs
//11、按位置查找返回元素的值
int search_pos(node_p H,int pos)
{
if(H==NULL){return -2;}
if(pos<1){return -1;}
if(empty_link(H)){return -3;}
int i;
node_p p;
for(i=1,p=H->next;i!=pos;i++,p=p->next)
{
if(p==NULL)
{
printf("位置不合理\n");
return -1;
}
}
return p->data;
}
12)按值修改
cs
//12、按值修改
void update_value(node_p H,int value,int new_value)
{
if(H==NULL){return;}
if(empty_link(H)){return;}
node_p p = H->next;
//循环整条链表
for(;p!=NULL;p=p->next)
{
if(p->data==value)
{
p->data = new_value;
return;
}
}
}
13)逆置

cs
//13、逆置
void reverse_link(node_p H)
{
if(H==NULL){return;}
if(empty_link(H)){return;}
if(H->next->next==NULL){return;}
//提前将第二个结点开始链表保存下来
node_p p = H->next->next;
node_p q;
//让原来的第一个结点变成现在的最后一个结点
H->next->next = NULL;
//循环头插
//只要p的指向的结点不为空说明要头插
while(p!=NULL)
{
//先保留下一个要头插结点的首地址
q = p->next;
//头插
p->next = H->next;
H->next = p;
p = q; //让p指向下一个要头插的结点
}
}
二、单向循环列表
1、特点
每个结点都有唯一的后继结点,尾结点的后继结点是头结点
2、单项循环链表的

3、关于单项循环链表的各项操作
1)定义链表结构体
cs
typedef struct node
{
union
{
int len;
int data;
};
struct node *next;
}node,*node_p;
2)构建单项循环列表
cs
//1、申请单向循环链表
node_p create_loop()
{
node_p H = (node_p)malloc(sizeof(node));
if(H==NULL)
{
printf("申请空间失败\n");
return NULL;
}
H->len=0;
H->next = H;
return H;
}
//2、申请结点
node_p create_node(int value)
{
node_p new = (node_p)malloc(sizeof(node));
new->next = NULL;
new->data = value;
return new;
}
3)判空
cs
//3、判空
int empty_loop(node_p H)
{
if(H==NULL){return -1;}
return H->next==H?1:0;
}
4)头插
cs
//4、头插
void insert_head(node_p H,int value)
{
if(H==NULL){return;}
node_p new = create_node(value);
//新结点的后继指向头结点的后继
new->next = H->next;
H->next = new;
H->len++;
}
5)尾插
cs
//5、尾插
void insert_tail(node_p H,int value)
{
if(H==NULL){return;}
node_p p=H; //p用于遍历链表,找到最后一个结点
while(p->next!=H)
{
p=p->next;
}
node_p new = create_node(value);
//退出循环后p指向最后一个结点
p->next = new;
new->next=H;
//new->next = p->next;
//p->next = new;
H->len++;
}
6)头删
cs
//6、头删
void dele_head(node_p H)
{
if(H==NULL){return;}
if(empty_loop(H)){return;}
//保存要删除结点的首地址
node_p p=H->next;
//让头结点指向原来的第二个结点
H->next=p->next;
free(p);
H->len--;
return;
}
7)尾删
cs
//7、尾删
void dele_tail(node_p H)
{
if(H==NULL){return;}
if(empty_loop(H)) {return;}
node_p p=H;
//尾删要找倒数第二个结点
while(p->next->next!=H)
{
p=p->next;
}
//循环退出时p指向倒数第二个节点
node_p q=p->next;
p->next=H;
free(q);
//free(p->next); //先释放最后一个结点
//p->next = H;
H->len--;
}
8)输出
cs
//8、输出
void show_loop(node_p H)
{
if(H==NULL){return;}
if(empty_loop(H)){return;}
node_p p = H->next;
while(p!=H)
{
printf("%d->",p->data);
p = p->next;
}
printf("H\n");
}
9)删除单项循环链表的头结点
cs
//9、删除单向循环链表的头结点
//删除头结点后需要返回给主调函数处新的链表的头
node_p delete(node_p H)
{
if(H==NULL){return NULL;}
if(empty_loop(H)){return NULL;}
//保存第一个结点的首地址
node_p p = H->next;
//找到最后一个结点
node_p tail = H->next;
while(tail->next!=H)
{
tail=tail->next;
}
//让最后一个结点的后继节点变成原来的第一个结点
tail->next = p;
//释放头结点
free(H);
return p;
}
10)删除头结点后的单项循环输出
原本的输出语句是不输出头结点的,删除头结点以后,原先的输出语句就不输出第一个结点了,
因此把while改成do{···}while,这样就会正常输出第一个结点了
cs
//10、删除头结点后的单向循环链表的输出
void show_no_head(node_p H)
{
if(H==NULL){return;}
//不需要再判空
//因为没有头结点了传过来的结点只要不是空说明链表中就有元素
node_p p = H;
do
{
printf("%d->",p->data);
p = p->next;
}while(p!=H);
printf("H\n");
//需要第一次循环时不判断条件
//只能使用do··while
}
三、双向链表
1、特点
除头结点外,每个结点都有唯一的前驱
除尾结点外,每个结点都有唯一的后继
2、结构
逻辑结构:线性结构
存储结构:顺序结构

3、双向链表结构体构建
1)定义双向链表结构体
因为双向链表,可以双向访问,那么就要存两个地址,前驱的地址和后继的地址。
cs
typedef struct node
{
union
{
int len;
int data;
};
struct node *pri; //指向前驱结点的指针
struct node *next; //指向后继结点的指针
}node,*node_p;
2)申请结点空间
cs
//1、创建双向链表
node_p create_double()
{
node_p H=(node_p)malloc(sizeof(node));
if(H==NULL)
{
return NULL;
}
H->len=0;
H->next=NULL;
H->pri=NULL;
return H;
}
//2、创建结点
node_p create_node(int data)
{
node_p new = (node_p)malloc(sizeof(node));
if(new==NULL){return NULL;}
new->data = data;
new->pri = NULL;
new->next = NULL;
return new;
}
3)判空
cs
//3、判空
int empty_double(node_p H)
{
if(H==NULL){return -1;}
return H->next==NULL;
}
4)头插

cs
//4、头插
void insert_head(node_p H,int value)
{
if(H==NULL){return;}
node_p new = create_node(value);
//新结点的后继保存原来头结点的后继
new->next = H->next;
//新结点的前驱指向头结点
new->pri = H;
//头结点后继节点的前驱指向新结点
if(H->next!=NULL)
{
H->next->pri = new;
}
//头结点的后继指向新结点
H->next = new;
H->len++;
}
4)尾插

cs
//5、尾插
void insert_tail(node_p H,int value)
{
if(H==NULL)
{return;}
node_p p = H;
while(p->next!=NULL)
{
p = p->next;
}
node_p new = create_node(value);
//新结点的前驱指向尾结点
new->pri = p;
//尾结点的后继指向新结点
p->next = new;
H->len++;
}
5)按位置插入

6)头删
cs
//8、头删
void dele_head(node_p H)
{
if(H==NULL){return;}
if(empty_double(H)){return;}
//保存要删除结点的首地址
node_p del = H->next;
//如果链表中节点个数多于一
if(del->next!=NULL)
{
del->next->pri = H;
}
//让头结点的后继保存第一个结点的后继
H->next = del->next;
free(del);
H->len--;
}
7)尾删
cs
//9、尾删
void dele_tail(node_p H)
{
if(H==NULL){return;}
if(empty_double(H)){return;}
//找到最后一个结点
node_p p = H;
while(p->next!=NULL)
{
p = p->next;
}
//退出循环时p是最后一个结点
//让倒数第二个结点的后继指向NULL
p->pri->next = NULL;
free(p);
H->len--;
}
8)按位置删除

cs
//10、按位置删除
void dele_pos(node_p H,int pos)
{
if(H==NULL){return;}
if(empty_double(H)){return;}
if(pos<=0){return;}
//找到pos位置的结点
int i;node_p p;
for(i=0,p=H;i<pos;i++,p=p->next)
{
if(p==NULL)
{
printf("位置不合理\n");
return;
}
}
//循环结束p指向pos位置的结点
if(p==NULL)
{
printf("位置不合理\n");
return;
}
//pos+1结点的前驱指向pos-1结点
if(p->next!=NULL)
{
p->next->pri = p->pri;
}
//pos-1结点的后继指向pos+1结点
p->pri->next = p->next;
free(p);
H->len++;
}
9)按值查找
cs
//11、按值查找返回位置
int search_value(node_p H,int value)
{
if(H==NULL)
{return -1;}
if(empty_double(H)){return -2;}
}
10)按位置改
cs
//12、按位置修改元素
void update_pos(node_p H,int pos,int new_value)
{
if(H==NULL){return;}
if(empty_double(H)){return;}
if(pos<=0){return;}
}
11)释放链表
cs
//13、释放双向循环链表
void des_double(node_p *H)
{
if(H==NULL||*H==NULL)
{return;}
while((*H)->next)
{
dele_head(*H);
}
//退出循环说明链表中只有头结点了
free(*H);
*H=NULL;
}
四、双向循环链表
1、特点:
每个结点都有唯一的前驱,头结点的前驱是尾结点
每个结点都有唯一的后继,尾结点的后继是头结点
2、结构

3、双向循环链表的构建
1)定义双向循环链表结构体
cs
typedef struct node
{
union
{
int len;
int data;
};
struct node *pri;
struct node *next;
}node,*node_p;
2)申请结点空间
cs
//创建头结点
node_p create_head()
{
node_p H=(node_p)malloc(sizeof(node));
if(H==NULL) {return NULL;}
H->pri=H;
H->len=0;
H->next=H;
return H;
}
//创建结点
node_p create_node(int value)
{
node_p new=(node_p)malloc(sizeof(node));
if(new==NULL) {return NULL;}
new->pri=NULL;
new->data=value;
new->next=NULL;
return new;
}
3)判空
cs
//判空
int empty_double(node_p H)
{
if(H==NULL) return -2;
return H->next==H;
}
4)头插
cs
//头插
void insert_head(node_p H,int value)
{
if(H==NULL) {return;}
node_p new=create_node(value);
new->pri=H;
new->next=H->next;
H->next=new;
new->next->pri=new;
H->len++;
}
5)尾插
cs
//尾插
void insert_tail(node_p H,int value)
{
if(H==NULL) {return;}
node_p new=create_node(value);
node_p p=H;
while(p->next!=H) {p=p->next;}
p->next=new;
new->pri=p;
new->next=H;
H->pri=new;
H->len++;
}
6)按位置插
cs
//按位置插
void insert_pos(node_p H,int pos,int value)
{
if(H==NULL) {return;}
node_p new=create_node(value);
node_p p=H;
for(int i=0;i<pos-1;i++,p=p->next)
{
if(p->next==H) {return;}
}
new->next=p->next;
new->pri=p;
p->next=new;
new->next->pri=new;
H->len++;
7)头删
8)尾删
9)按位置删
10)输出
cs
//输出
void show(node_p H)
{
if(H==NULL) {return;}
if(empty_double(H)) {return;}
node_p p=H->next;
for(;p!=H;p=p->next)
{
printf("%d->",p->data);
}
putchar(10);
}