408计算机综合数据结构笔记–线性表(2)链表
分类
单链表,双链表,循环链表,静态链表
单链表
单链表的定义
每个节点除了存放数据外还要存放指向下一个节点的指针
代码定义
struct LNode{ //定义单链表节点类型
ElemType data;//定义存放数据类型
struct *LNode next;//存放下一个节点位置的指针
}LNode,*LinkList;
//新增一个节点,申请该节点所需空间,并用指针p指向该节点
LNode*p=(LNode*)malloc(sizeof(LNode))
Notes:
1)定义节点p时,LNodep和LinkList p意思一样。都是定义了一个struct LNode类型的指针。但是程序中所表达的含义中,LNode强调这是一个节点,LinkList强调这是一个单链表。
单链表的初始化
不带头节点
bool InitList(LinkList &L){
L=NULL;
return true;
}
void text(){
LinkList L;
//初始化一个空表
InitList(L);
}
带头结点
bool InitList(LinkList &L){
L=(LNode*)malloc(sizeof(LNode*));
if(L==NULL) //内存不足,创建头节点失败
return false;
L->next=NULL; //头节点之后还没有节点
return true;
}
void text(){
LinkList L;
//初始化一个空表
InitList(L);
}
头节点只存放指针不存放数据
二者区别:
对第一个数据节点和后续数据节点的处理的代码逻辑不同;
对空表和非空表的处理的代码逻辑不同。
总之,带头节点的写代码更方便,所以一般都带头节点
单链表的操作–插入
单链表按位序插入
ListInsert(&L,i,e):在表L中第i个位置插入元素e
带头节点
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1)
return false;
LNode *p; //指向当前扫描到的节点
int j=0; //当前p指向的是第几个节点
p=L; //L指向头节点,头节点是第0个节点(不存数据)
while(p!=NULL&&j<i-1){ //循环找到第i-1个节点
p=p->next;
j++;
}
if(p==NULL) //i值不合法
return false;
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s; //将节点s连到p之后
return true;//插入成功
}
不带头节点
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1)
return false;
if(i==1){//插入第1个节点的操作与其它节点不同
LNode*s=(LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=L;
L=s; //头指针指向新节点
return true;
}
LNode *p; //指向当前扫描到的节点
int j=1; //当前p指向的是第几个节点
p=L; //L指向第1个节点
while(p!=NULL&&j<i-1){ //循环找到第i-1个节点
p=p->next;
j++;
}
if(p==NULL) //i值不合法
return false;
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s; //将节点s连到p之后
return true;//插入成功
}
单链表操作–后插
在p节点之后插入元素e
bool InsertNextNode(LNode*p,ElemType e){
if(p==NULL)
return false;
LNode *s=(LNode*)malloc(siezof(LNode));
if(s==NULL)
return false;
s->data=e;
s->next=p->next;
p->next=s;//将节点s连到p之后
return true;
}
单链表操作–前插
在节点p之前插入元素e
bool InsertPriorNode(LNode*p,ElemType e){
if(p==NULL)
return false;
LNode *s = (LNode*)malloc(sizeof(LNode*));
if(s==NULL)
return false;
s->next=p->next;
p->next=s;//新节点s连接到p之后
s->data=p-<data;//p的元素给s
p->data=e;//用e覆盖p中的元素
return true;
}
单链表的操作–删除
按位序删除
ListDelete( &L,i,&e):删除表L中第i个节点,并用e返回删除数据的值
即:找到第i-1个节点,并将其指向i+1个节点,并释放第i个节点。
带头结点
bool ListDelete(LinkList &L,int i,ElemType &e){
if(i<1)
return false;
LNode*p;
j=0;
p=L;
while(p!==NULL&&j<i-1){
p->next;
j++;
}
if(p==NULL)
return false;
if(p->next==NULL)
return false;
LNode *q=p->next;//令q指向被删除的节点
e=q->data;//用e返回被删除节点的值
p->next=q->next;//将*q节点从链中断开,即i-1个节点指向i+1个节点
free(q);
return true;
}
删除指定节点
bool DeleteNode(LNode *p){
if(p==NULL)
return false
LNode *q=p->data;//令q指向*p的后继节点
p->data=p->next->data;//和后继节点交换数据域
p->next=q->next;//将*q从链中断开
free(q);
return true;
}
单链表的操作–查找
按位查找
返回第i个元素
带头结点
LNode*GetElem(LinkList L,int i){
if(i<0)
return NULL;
LNode *p;
int j;
p=L;
while(p!==NULL&&j<i){
p=p->next;
j++;
}
return p;
}
按值查找
LNode *LocadeElem(LinkList L,ElemType e){
LNode *p;
p=L->next;
while(p!==NULL&&p->data!=e)
p=p->next;
return p;
}
单链表的操作–求表长
int Length(LinkList L){
int len=0;
LNode *p=L;
while(p->next!=NULL){
p=p->next;
len++;
}
return len;
}
单链表的操作–建立
尾插法
在p节点之后插入元素e
bool InsertNextNode(LNode*p,ElemType e){
if(p==NULL)
return false;
LNode*s =(LNode*)malloc(sizeof(LNode));
if(s==NULL)
return false;
s->data=e; //用节点s保存元素e
s->next=p->next;
p->next=s;//将节点s连到P后面
return true;
}
尾插法建立示例
LinkList List_Taillnsert(LinkList &L){
int x;
L=(LinkList)malloc(sizeof(LNode));//建立头节点
LNode *s,*r=L; //r为表为指针;
scanf("%d",&x);//输入节点的值
while(x!=9999){ //设置的一个特殊数,即输入9999表示结束
s =(LNode*)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;//指向新的表尾节点
scanf("%d",&x);
}
r->next=NULL//尾节点指针为空
return L;
}
头插法建立示例
LinkList List_Headlnsert(LinkList &L){
LNode*s;
int x;
L=(LinkList)malloc(sizeof(LNode));//建立头节点
L->next=NULL;//初始化为空链表 //如果没有这句?
scanf("%d",&x);//输入节点的值
while(x!=9999){
s=(LNode*)malloc(sizeof(LNode));//创建新节点
s->data=x;
s->next=L->next;
L->next=s;
scanf("%d",&x);
}
return L;
}
如果没有L->next=NULL;,最终最后一个节点有可能指向一片未知的区域。
头插法应用:链表的逆置
双链表
双链表的初始化
带头结点
typedef struct DNode{
ElemType data;
struct DNode *Prior,*next;
}DNode,*DLinklist;
//初始化双链表
bool InitDLinkList(DLinklist &L){
L=(DNode*)malloc(sizeof(DNode)); //分配一个头节点
if(L==NULL) //内存不足,分配失败
return false;
L->prior=NULL;//头结点的prior永远指向NULL
L->next=NULL;//头结点之后暂时还没有节点
return true;
}
void testDLinkList(){
DLinkList L;
InitDLinkList(L);
}
双链表的插入
//p节点之后插入s节点
bool InsertNextDNode(DNode*p,DNode*s){
if(p==NULL||s==NULL)
return false;
s->next=p->next;
if(p->next!=NULL)//如果p后面还有后继节点
p->next->prior=s;
s-prior=p;
p->next=s;
}
如果要进行前插操作只需要找到该节点的前驱并进行后插操作即可
双链表的删除
//删除p结点的后继结点
bool DeleteNextDNode(DNode){
if(p==NULL) return false;
DNode*q=p->next;
if(q==NULL) return false;
p->next=q->next;
if(q->next!=NULL) //如果q有后继节点
q->next->prior=p;
free(q);
return true;
}
双链表的遍历
//后向遍历
while(p!=NULL){
p->next;
}
//前向遍历
while(p!=NULL){
p=p->prior;
}
//前向遍历跳过头结点
while(p->prior=NULL){
p=p->prior;
}
双链表不可随机存取,按值查找,按位查找,都可通过遍历来实现。
时间复杂度为O(n);
循环链表
即让单链表或者双链表的尾结点指向头结点。
循环单链表
初始化
typedef struct LNode{
ELemType data;
struct LNode *next;
}LNode,*LinkList;
//初始化一个循环单链表
bool InitList(LinkList &L){
L=(LNode*)malloc(sizeof(LNode));
if(L==NULL)
return false;
L->next=L;
return true;
}
循环双链表
初始化
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
bool InitDLinkList(DLinklist &L){
L=(DNode*)malloc(sizeof(DNode));
if(L==NULL) return false;
L->prior=L;
L->next =L;
return ture;
}
静态链表
分配一整片连续的空间,各个结点集中安置。
包含:数组元素和下一个节点的数组下标(游标)。
数组下标为0的节点为头结点,头结点下一个结点为头结点游标所指示的数组下标所在的节点。其中最后一个结点游标的值设置为-1
定义一个静态链表示例
#denfine MaxSize 10
struct Node{
ElemType data;
int next;
};
void testSLinkList(){
struct Node a[MaxSize];
}
//或者:
struct Node{
ElemType data;
int next;
}SLinkList[MaxSize];
顺序表与链表的比较
逻辑结构
都是线性结构,都属于线性表。
存储结构
顺序表 | 链式表 |
---|---|
顺序存储 | 链式存储 |
优点:支持随机存取 ,存储密度高 | 优点:离散的小空间分配方便,改变容量方便 |
缺点:大片连续空间分配不方便,改变容量不方便 | 缺点:不可随机存取,存储密度低 |
基本操作
顺序表 | 链式表 |
---|---|
销毁:1)修改length=0;静态数组,系统自动收回空间;动态数组,需要手动free | 销毁:依次删除(free)各个结点 |
插入/删除:要将元素后移/前移;时间复杂度O(n),时间开销主要来自于移动元素 | 插入删除:只需要修改指针即可;时间复杂度O(n),时间开销主要来自于查找目标元素;但是查找元素的时间代价要比移动元素的时间代价低 |
查找:按位查找O(1);按值查找O(n),若表内元素有序O( log 2 n \log_2n log2n) | 查找:按值查找O(n);按位查找O(n) |
比较总结
表长难以预估,经常要增加删除元素-----链表;
表长可以预估,查询操作更多-----顺序表