链表——用链式结构的方式实现线性表
代码定义如下:
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
不带头结点的单链表
bool InitList(LinkList &L){
L=NULL;
return true;
}
bool ListEmpty(LinkList L){
return (L==NULL);
}
带头结点的单链表
bool InitList(LinkList &L){
L=(LNode *)malloc(sizeof(LNode));
if (L==NULL){
return false;
}
L->next=NULL;
return true;
}
bool ListEmpty(LinkList L){
return (L->next==NULL);
}
注:不带头结点,写代码更麻烦,对第一个数据结点和后续数据结点的处理需要用不同的代码逻辑,对空表和非空表的处理需要用不同的代码逻辑。
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1){
return false;
}
LNode *p;
int j=0;
p=L;
while(p!=NULL && jnext;
j++;
}
//以下代码可以在后插操作封装后替换称InsertNextNode(p,e)
if(p==NULL){
return false;
}
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s-next=p->next;
p->next=s;
return true;
}
注:不带头结点的链表按位插入,需要对插入第1个结点单独处理,即 i = = 1 i==1 i==1时做单独处理。
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-next=p->next;
p->next=s;
return true;
}
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->data=p->data;
p->data=e;
return true;
}
bool ListDelete(LinkList &L,int i,ElemType &e){
if(i<1){
return false;
}
LNode *p;
int j=0;
p=L;
while(p!=NULL && jnext;
j++;
}
if(p->next==NULL){
return false;
}
LNode *q=p->next
p-next=q->next;
e=q->data;
free(q);
return true;
}
bool DeleteNode(LNode *p){
if(p==NULL){
return false;
}
LNode *q=p->next;
p->data=q->data;
p->next=q->next;
free(p);
return true;
}
LNode *GetElem(LinkList L,int i){
if(i<0){
return false;
}
LNode *p;
int j=0;
while(p!=NULL && jnext;
j++;
}
return p;
}
LNode *LocateElem(LinkList L,ElemType e){
LNode *p=L->next;
while(p!=NULL && p-data!=e){
p=p->next;
}
return p;
}
LinkList List_TailInsert(LinkList &L){
int x;
L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;
LNode *s,*r=L;
scanf("%d",&x);
while(x!=-1){
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;
scanf("%d",&x);
}
r->next=NULL;
return L;
}
当输入:10 16 27时,单链表如下所示
重要应用于链表的逆置
LinkList List_HeadInsert(LinkList &L){
int x;
L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;
LNode *s;
scanf("%d",&x);
while(x!=-1){
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
s->next=L->next;
L->next=s;
scanf("%d",&x);
}
return L;
}
当输入:10 16 27时,单链表如下所示
对于单链表存在无法逆向检索的问题,双链表可进可退,但是存储密度相对于单链表更低一些。
结点定义如下:
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
bool InitDLinkList(DLinkList &L){
L=(DlinkList)malloc(sizeof(DNode));
if(L==NULL){
return false;
}
L->next=NULL;
L->prior=NULL;
return true;
}
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;
return true;
}
void DestoryList(DLinkList &L){
while(L->next != NULL){
DeleteNextDNode(L);
}
free(L);
L=NULL;
}
bool DeleteNextDNode(DNode *p){
if(p==NULL){
return false;
}
DNode *q=p-next;
if(q==NULL){
return false;
}
p->next=q->next;
if(q->next!=NULL){
q->next->prior=p;
}
free(q);
return true;
}
while (p!=NULL){//后向遍历
p =p->next;
}
while (p!=NULL){//前向遍历
p= p->prior;
}
while (p-> prior != NULL){//跳过头结点的前向遍历
p = p->prior;
}
表尾结点的next指针指向头结点。
从一个结点出发可以找到其他任何一个结点。
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;
}
表头结点的 prior 指向表尾结点,表尾结点的 next 指向头结点。
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
bool InitDLinkList(DLinkList &L){
L=(DlinkList)malloc(sizeof(DNode));
if(L==NULL){
return false;
}
L->next=L;
L->prior=L;
return true;
}
分配一整片连续的内存空间,各个结点集中安置。
代码定义如下:
#define MaxSize 10
typedef struct {
ElemType data;
int next;
}SLinkList[MaxSize];
void testSLinkList(){
SLinkList a;//a为静态链表
}
查找:从头结点出发挨个往后遍历结点
插入位序为 i 的结点:
①找到一个空的结点(next为特殊值,例如-2表示结点为空),存入数据元素
②从头结点出发找到位序为 i-1 的结点
③修改新结点的 next
④修改 i-1 号结点的 next
删除某个结点:
①从头结点出发找到前驱结点
②修改前驱结点的游标
③被删除结点 next 设为 -2
优点:增、删 操作不需要大量移动元素
缺点:不能随机存取,只能从头结点开始依次往后查找;容量固定不可变
适用场景:①不支持指针的低级语言;②数据元素数量固定不变的场景(如操作系统的文件分配表FAT)
类型 | 逻辑结构 | 存储结构 | 基本操作 |
---|---|---|---|
顺序表 | 线性表,线性结构。 | 支持随机存取、存取密度高;但是大片连续空间分配不方便、改变容量不方便。 | 在查找方面,顺序表支持随机存取,时间复杂度为 O ( 1 ) O(1) O(1),优于链表;在插入元素和删除元素方面,顺序表时间开销主要来自移动元素。 |
链表 | 线性表,线性结构。 | 离散的小空间分配方便、改变容量方便;但是不可随机存取、存储密度低。 | 创建方面,链表具有弹性,可扩容,优于顺序表;在插入元素和删除元素方面,链表只需要修改指针,时间开销主要来自查找元素,时间代价更低,优于顺序表。 |
问题: 请描述顺序表和链表的…实现线性表时,用顺序表还是链表好?
答:顺序表和链表的逻辑结构都是线性结构,都属于线性表。但是二者的存储结构不同,顺序表采用顺序存储…(特点,带来的优点缺点);链表采用链式存储…(特点、导致的优缺点)。由于采用不同的存储方式实现,因此基本操作的实现效率也不同。当初始化时…;当插入一个数据元素时…;当删除一个数据元素时…;当查找一个数据元素时…