双向链表
引言
页面的前进与回退
已知某指针q的位置,求指针p。
由于单链表节点中只有一个指向其直接后继结点的指针域,因此若已知某结点的指针域为p,找其前驱结点只能从该链表的头结点开始,顺链查找,直到找到某个结点q的直接后继为p为止。q所指的结点即为p的直接前驱,其时间复杂度为O(n)。
所以要想让其时间复杂度达到O(1),可以选择增加一个指向直接前驱的指针域。
双向链表的基本操作
//定义结构体变量
typedef struct node{
int data; //data域存放数据
struct node *prev; //prev指针域存放上一个结点的地址
struct node *next; //next指针域存放下一个结点的地址
}LNode,*Linklist;
//创建头结点
Linklist CreatList();
//双向链表判空操作
int isEmpty(Linklist head);
//链表的创建 [头插法]
void ListByInsertHead(Linklist head );
//链表的创建[尾插法]
void ListByInsertTail(Linklist head );
//将p结点插入q结点之前
void Insert(LNode *p, LNode *q);
//删除给定结点
void delete_Node(LNode *p);
//删除链表
void delete_List(LNode *p);
链表创建头结点[初始化]
/**
*创建链表,返回头指针。
**/
Linklist CreatList(){
Linklist head = (Linklist)malloc(sizeof(LNode));//创建头结点
head->next=NULL; //将next指针域置空
head->prev=NULL; //将prev指针域置空
return head; //返回头结点
}
/**
*双向链表判空操作
**/
int isEmpty(Linklist head){
if(head->next==NULL && head->prev==NULL){
//为空
return 1;
}
//不为空
return 0;
}
如何连接我相信大家画图都是知道的,但是有时候代码跑不起来的原因就是因为插入的次序没有。
链表头插法创建
断开两个连接
我们先看一下原图
断的次序(head->next后断):
特殊情况:
当只存在head结点时,head的next指针域为空。
已知Head结点和p结点,上面共四步:
Head->next=p;(1)
p->prev=Head;(2)
Head->next->prev=p;(3)
p->next=Head->next;(4)
3 4 1
/**
*头插法创建链表
**/
void ListByInsertHead(Linklist head ){
LNode *p; //指针p用来接受数据,进行插入
int x;
while(scanf("%d",&x)&&x!=-1){
p=(Linklist)malloc(sizeof(LNode)) ;
p->data=x;
//头插法
//只有头结点时,头结点的next域为NULL,不存在prev指针域,第一步要放在第三步之前,此时未更新head->next
if(head->next!=NULL){
head->next->prev=p;
}
p->next=head->next; //单链表头插操作
head->next=p; //单链表头插操作
p->prev=head; //将p的prev指针域指向head(这一步放哪都行)
}
return ;
}
链表尾插法创建
此时有四步改动
p->next=s;(1)
p=s;(2)
s->prev=p;(3)
s->next=p->next;(4)
//还有一种写法是s->next=NULL;
3 2
/**
*尾插法创建链表
**/
void ListByInsertTail(Linklist head ){
LNode *p,*s; //指针p用来保存尾指针位置,指针s用来存数据进行插入
p=head;//只有头结点时,头结点即为最后一个结点
int x;
while(scanf("%d",&x)&&x!=-1){
s=(Linklist)malloc(sizeof(LNode)) ;
s->data=x;
//尾插法
s->next=p->next;//s的结点指向尾结点p的下一个,即NULL
p->next=s;//将尾结点的next指针指向要插入的结点
//第三步和第四步不能交换
s->prev=p;//要插入的结点的前一个结点更新为尾结点
p=s;//将尾结点更新为新插入的结点
}
return ;
}
/**
*尾插法创建链表第二种写法
**/
void ListByInsertTail(Linklist head ){
LNode *p,*s; //指针p用来保存尾指针位置,指针s用来存数据进行插入
p=head;//只有头结点时,头结点即为最后一个结点
int x;
while(scanf("%d",&x)&&x!=-1){
s=(Linklist)malloc(sizeof(LNode)) ;
s->data=x;
//尾插法
p->next=s;//将尾结点的next指针指向要插入的结点
//第二步和第三步不能交换
s->prev=p;//要插入的结点的前一个结点更新为尾结点
p=s;//将尾结点更新为新插入的结点
}
s->next=NULL;//尾结点指向为空
return ;
}
指定结点前插入
在q结点前插入p结点
此处共有四处改动
p->prev=q->prev;(1)
q->prev->next=p;(2)
p->next=q;(3)
q->prev=p;(4)
/**
*指定结点前插入(在q结点前插入p结点)
**/
void Insert(LNode *p, LNode *q){
//只有等q指针前面那个结点的相关操作处理完之后才能修改q->prev
p->prev=q->prev;
q->prev->next=p;
p->next=q;
q->prev=p;
}
链表删除
特殊:p为尾指针
上述有三处改动
p->prev->next=p->next;(1)
p->next=p->prev(2)
free(p)(3)
/**
*删除指定结点
**/
void delete_Node(LNode *p){
p->prev->next=p->next;
if(p->next!=NULL){
p->next->prev=p->prev;
}
free(p);
}
双向链表完整代码
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
typedef struct node{
int data;
struct node *prev;
struct node *next;
}LNode,*Linklist;
//创建链表
Linklist CreatList();
//双向链表判空操作
int isEmpty(Linklist head);
//链表的创建 [头插法]
void ListByInsertHead(Linklist head );
//链表的创建[尾插法]
void ListByInsertTail(Linklist head );
//将p结点插入q结点之前
void Insert(LNode *p, LNode *q);
//删除p结点
void delete_Node(LNode *p);
//删除链表
void delete_List(LNode *p);
//打印链表
void print_List(Linklist head);
int main(){
Linklist head = CreatList();
printf("%d\n",isEmpty(head));
ListByInsertTail(head);
print_List(head);
printf("\n");
Linklist head1 = CreatList();
ListByInsertHead(head1);
print_List(head1);
// LNode *t = (Linklist)malloc(sizeof(LNode));
// t->data=10;
// Insert(t,head->next);
// print_List(head);
// printf("\n");
// delete_Node(t);
// print_List(head);
// delete_List(head);
return 0;
}
Linklist CreatList(){
Linklist head = (Linklist)malloc(sizeof(LNode));
head->next=NULL;
head->prev=NULL;
return head;
}
int isEmpty(Linklist head){
if(head->next==NULL && head->prev==NULL){
//为空
return 1;
}
//不为空
return 0;
}
void ListByInsertHead(Linklist head ){
LNode *p;
int x;
while(scanf("%d",&x)&&x!=-1){
p=(Linklist)malloc(sizeof(LNode)) ;
p->data=x;
if(head->next!=NULL){
head->next->prev=p;
}
p->next=head->next;
head->next=p;
p->prev=head;
}
return ;
}
void ListByInsertTail(Linklist head ){
LNode *p,*s;
p=head;
int x;
while(scanf("%d",&x)&&x!=-1){
s=(Linklist)malloc(sizeof(LNode)) ;
s->data=x;
s->next=p->next;
p->next=s;
s->prev=p;
p=s;
}
//s->next=NULL;
return ;
}
void Insert(LNode *p, LNode *q){
p->prev=q->prev;
q->prev->next=p;
p->next=q;
q->prev=p;
}
void delete_Node(LNode *p){
p->prev->next=p->next;
if(p->next!=NULL){
p->next->prev=p->prev;
}
free(p);
}
void delete_List(LNode *p){
LNode *t,*temp;
t=p->next;
while(t){
temp=t->next;
delete_Node(t);
t=temp;
print_List(p);
}
free(p);
}
void print_List(Linklist head){
Linklist p;
p=head;
while(p->next){
p=p->next;
printf("%d ",p->data);
}
system("pause");
printf("\n");
while(p->prev){
printf("%d ",p->data);
p=p->prev;
}
}
循环双向链表的实现
小思考:自己实现循环双向链表
循环双向链表和双向链表的操作基本一致, 注意由于head指针的prev域和尾指针的next域不为空,因此部分操作需要增加对这两个指针的连接
双向循环链表完整代码:
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
typedef struct node{
int data;
struct node *prev;
struct node *next;
}LNode,*Linklist;
//创建链表
Linklist CreatList();
//双向循环链表判空操作
int isEmpty(Linklist head);
//链表的创建 [头插法]
void ListByInsertHead(Linklist head );
//链表的创建[尾插法]
void ListByInsertTail(Linklist head );
//将p结点插入q结点之前
void Insert(LNode *p, LNode *q);
//删除p结点
void delete_Node(LNode *p);
//删除链表
void delete_List(LNode *p);
//打印链表
void print_List(Linklist head);
int main(){
Linklist head = CreatList();
printf("%d\n",isEmpty(head));
ListByInsertTail(head);
print_List(head);
printf("\n");
//Linklist head1 = CreatList();
//ListByInsertHead(head1);
//print_List(head1);
LNode *t = (Linklist)malloc(sizeof(LNode));
t->data=10;
Insert(t,head->next);
print_List(head);
printf("\n");
delete_Node(t);
print_List(head);
delete_List(head);
return 0;
}
Linklist CreatList(){
Linklist head = (Linklist)malloc(sizeof(LNode));
head->next=head;
head->prev=head;
return head;
}
int isEmpty(Linklist head){
if(head->next==head && head->prev==head){
//为空
return 1;
}
//不为空
return 0;
}
void ListByInsertHead(Linklist head ){
LNode *p;
int x;
while(scanf("%d",&x)&&x!=-1){
p=(Linklist)malloc(sizeof(LNode)) ;
p->data=x;
head->next->prev=p;
p->next=head->next;
head->next=p;
p->prev=head;
}
return ;
}
void ListByInsertTail(Linklist head ){
LNode *p,*s;
p=head;
int x;
while(scanf("%d",&x)&&x!=-1){
s=(Linklist)malloc(sizeof(LNode)) ;
s->data=x;
s->next=p->next;
//增加代码:尾指针所指的下一个结点即头指针的prev修改为要插入的结点,即将头指针与新要插入的结点连接起来
p->next->prev=s;
p->next=s;
s->prev=p;
p=s;
}
return ;
}
void Insert(LNode *p, LNode *q){
p->prev=q->prev;
q->prev->next=p;
p->next=q;
q->prev=p;
}
void delete_Node(LNode *p){
p->prev->next=p->next;
//if(p->next!=NULL){ //p->next不会为空,可删去
p->next->prev=p->prev;
// }
free(p);
}
void delete_List(LNode *p){
LNode *t,*temp;
t=p->next;
while(t!=p&&t!=p){//判断条件改为当t又绕回一圈时即链表只剩一个结点
temp=t->next;
delete_Node(t);
t=temp;
print_List(p);
printf("\n");
}
free(p);
}
void print_List(Linklist head){
Linklist p;
p=head->next;
while(1){
printf("%d ",p->data);
if(p->next==head){
break;
}
p=p->next;
}
system("pause");
printf("\n");
while(1){
printf("%d ",p->data);
if(p->prev==head){
break;
}
p=p->prev;
}
}
模拟链表
引言
前面介绍的链表都是由指针实现的,链表中结点的分配和回收都是动态的,称为"动态链表“。与之对应的,还有一种"静态链表",又称模拟链表,静态链表由数组实现,每个数据元素除了存储数据信息外,还要存储逻辑相邻的下一个数据元素在数组中的位置。可见,静态链表虽然是用数组实现的,但是逻辑相邻的数据元素不一定在物理上是相邻的。
下表就是一个模拟链表,他的存储顺序是:a1->a2->a3->a4->a5->……,但是他的物理顺序是a4->a2-a3->a1->a5->……
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aCqcRdob-1615363269774)(.\双向链表.assets\image-20201208221730863.png)]
模拟链表通过两个数组实现链式存储,data数组用来存储当前结点的数据,next数组用来存储下一个结点的位置。(是不是感觉和链表很像),通过相同点的数组下标,标识每一个元素存储的数据及下一个结点的位置信息,一样的,第一个结点并没有存放任何元素。
模拟链表的基本操作
//定义next=-1为空
typedef struct{
int data;
int next;
}imitate;
//定义静态链表
imitate im[Maxsize];
//链表表头和新要插入的位置
int head,pos;
//初始化链表
void init_list();
//链表判空操作
int isEmpty() ;
//链表增加结点
void add_toList();
//指定位置之后插入结点
int add_postoList(int point,int x);
//删除指定位置元素
int delete_pos_list(int point);
模拟链表的初始化
/**
*链表初始化
**/
void init_list(){
head=0;//头结点放在0位置
pos=1;
im[0].next=-1;
}
链表判空
int isEmpty(){
if(im[head].next==-1){
return 1;
}
return 0;
}
链表增加节点
/**
*链表尾部增加结点
**/
void add_toList(int x){
//从头开始遍历,找到尾结点
int i=head;
while(im[i].next!=-1){
i=im[i].next;
}
im[pos].data=x;//读入新要插入的元素
im[i].next = pos;//将尾结点的next指针指向新要插入的元素
im[pos].next = -1;//将新插入的元素的尾指针置空
++pos;
}
指定位置后增加结点
/**
*链表指定位置后增加结点 成功返回1 失败返回0
*point变量表示链表中第几个数据
**/
int add_postoList(int point,int x){
for(int i= im[head].next;i!=-1;i=im[i].next){
if((--point)==0){//找到那个要增加结点的位置
im[pos].data=x;//读入值yuanxie
im[pos].next=im[i].next;//新插入的值的下一个结点为第point个位置的结点的下一个结点
im[i].next=pos;//第point个结点的下一个结点更新为新插入的结点
++pos;//插入点后移
return 1;
}
}
return 0;
}
删除指定位置的结点
/**
*链表指定位置删除结点 成功返回1 失败返回0
*point变量表示链表中第几个数据
**/
int delete_pos_list(int point){
point=point-1;//找要删除的结点的前一个结点
for(int i= im[head].next;i!=-1;i=im[i].next){
if((--point)==0){ //循环找到那个结点
im[i].next=im[im[i].next].next; //该结点等于要删除的结点的下一个结点。
return 1;
}
}
return 0;
}