由于单链表的插入删除需要利用前驱结点,每次需要遍历寻找不方便,引入双链表
双链表包含prior 和next两个指针,prior非常方便找到他的前驱结点
插入删除时间复杂度变为O(1)
双链表的基本操作
1.插入操作(画图)
防止断链需要保证顺序,p后继结点的修改要在后继结点相关操作完成
插入结点的next,p后继结点的prior;
后插
bool insertdoule(DLnode *p,DLnode *s){
s->next=p->next;
if(p->next!=NULL)
p->next->prior=s;
s->prior=p;
p->next=s;
}
前插:类似后插不保证断链,p的前驱结点修改要在相关操作之后
s->prior=p->prior p->prior=s 才可以修改 p->prior=s
2.删除操作(画图)
删除P的后继结点q
p->next=q->next;
q->next->prior=p;
free(q);
删除q结点的前驱结点p 类似指向
q->prior=p->prior;
p->prior->next=q;
free(q);
二、循环单链表
和单链表的区别:最后一个结点的next不指向NULL,指向头结点 从而形成一个环
三、循环双链表
头结点的prio指向尾结点,尾结点的next指向头结点
四、总结顺序表和链表的比较
1.存取(读写)方式
顺序表可以随机存取,也可以顺序存取;链表只能顺序存取
2.逻辑结构和物理结构
顺序表(存储):逻辑上相邻的元素在物理上也相邻
链表(存储):采用链式存储,逻辑相邻,物理不一定相邻,通过指针的链接表示
3.查找元素
按值查找顺序表:无序 时间复杂度:O(n) 有序折半查找 时间复杂度O(logn)
按序查找顺序表:时间复杂度 O(1)
链表的平均时间复杂度都为O(n)
4.删除插入元素
顺序表平均需要移动半个表长
链表只需要修改指针域 O(1),但指针域浪费空间
5.空间分配
顺序表的静态分配:一旦存储空间装满不能扩充
顺序表的动态分配:虽然可以扩充但是效率过低,没有相当大的连续空间也是分配失败
链表:只在有需要的时候申请空间。有空间就分配,灵活高效
如何选择最优存储方式:
1.基于存储:如果不预先知道数据规模,不能采用顺序表。若不考虑存储密度,采用链表
2.基于运算考虑 若经常的操作是按序号访问做运算,采用顺序表
经常插入删除采用链表
3.环境考虑:数组实现容易,指针较难。顺序表容易实现
#include <iostream>
//双链表
//定义节点
typedef struct DLnode{
int data;
struct DLnode *prior,*next;
}DLnode,*DLinklist;
//
bool initiDlinklist(DLinklist &L){
L=(DLnode *)malloc(sizeof(DLnode));
if (L==NULL)
return false;
L->next=NULL;
L->prior=NULL; //永远指向NULL
return true;
}
bool empty(DLinklist &L){
if (L==NULL)
return true;
else
return false;
}
//插入节点
bool insertDlinklist(DLinklist &L,DLnode *s,DLnode *p){
s->next=p->next;
if (p->next!=NULL) //P没后节点
p->next->prior=s;
s->prior=p;
p->next=s;
}
//删除节点
bool deleteDLnode(DLnode *s){
DLnode *p=s->next;
if (s==NULL)
return false;
if (p==NULL) //s是否有后继
return false;
s->next=p->next;
if (p->next!=NULL)
p->next->prior=s;
free(p);
return true;
}
//双链表遍历
//往后遍历
//while(p!=NULL){
// p=p->next;
//}
//往前遍历
//while(p!=NULL){
// p=p->prior;
// }
//跳过头头节点
//while(p->prior!=NULL){
// p=p->prior;
//}
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}