数据结构之链表/单链表/静态链表/循环链表/双向链表

链表

具体实现代码见此博客:单链表的CPP实现

单链表

每个结点中只含有一个指针域

单链表的存储结构:

typedef struct LNode{
    ElemType data;//数据域
    struct LNode *next;//指针域
}

单链表可以包含头结点,也可以直接在头部装入第一个元素。

即,所有的链表都有一个头指针head,head中不装数据项叫做带头结点,带有数据项则称为不带头结点。如果不带头结点,每次头插法时,head都需要进行变化。

以下讨论带头结点的单链表

  • 头指针

指向链表中第一个结点的指针(头结点或者首元结点)的指针

  • 头结点

在链表的首元结点,数据域内只放空表标志和表长等信息

  • 首元节点

链表中存储线性表第一个数据元素的结点

先创建一个头指针,实际上就是创建一个头结点,然后头指针指向头结点

链表的定义:一个线性表由若干个结点组成,每个结点至少含有两个域:数据域和指针域,由这样的结点存储的线性表称为链表。

指针域存储的是数据的首地址。数据的宽度是由数据自己的数据类型所确定的,计算机自动截取。

指针所占的数据大小和计算机有关,与计算机的寻址能力相关,即与计算机地址线的条数相关。

单链表结构示意图:

[a1, -]–>[a2,- ]–>[a3,^]

逻辑次序和物理次序不一定相同,元素之间的逻辑关系用指针表示,需要额外的空间来存储元素之间的关系(指针)

头指针

在链式存储结构中以第一个结点的存储地址作为线性表的基地址,通常称它为头指针。线性表的最后一个元素没有后继,因此最后一个结点中的指针域是一个特殊的值NULL,常称为空指针。

头结点

在单链表上设置一个结点,本身不存放数据,它的指针域指向第一个元素的地址(不是必须有的,有些情况使用,比如分组翻转链表,主要是找头指针方便,结构也比较规整)

头结点的作用:使得对第一个元素的操作与对其他元素的操作保持一致。

单链表的结点

typedef struct LNode{
    ElemType data;//数据域
    struct LNode* next;//指针域
}LNode,*LinkList;
LNode* L;
LinkList L;

构造一个空的带头结点的线性表L

LinkList L;
void InitList(LinkList &L){
    L=(LNode *)malloc(sizeof(LNode));
    L->next=NULL
}
//[#,^],头结点,下一个结点才开始存数据

//判断线性表L是否为空
bool ListEmpty(LinkList L){
    if(L-next==NULL)
        return TRUE;
    else
        return FALSE;
}

构造一个空的不带头结点的线性表L

LinkList L;
void(LinkList &L){
    L=NULL;
}

//判断线性表L是否为空
bool ListEmpty(LinkList L){
    if(L==NULL)
        return TRUE;
    else
        return FALSE;
}

插入结点

LinkList s=(LNode*)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;

删除结点

//需要知道被删除节点的前驱,记为p,被删除的记为q
q=p->next;
p->next=q->next;
free(q);

查找结点

//查找第i个结点,将第i个结点的值存入e
GetElem_L(LinkList L,int i,ElemType &e){
    p=L->next;
    j=1;
    while(p&&j<i){
        p=p->next;
        ++j;
    }
    if(!p)
        return ERROR;
    e=p->data;
    return OK;
}

固定位置插入节点

//在第i个结点之前插入新的元素e
ListInsert_L(LinkList L,int i,ElemType e){
    p=L;
    j=0;
    while(p&&j<i-1){
        p=p->next;
        ++j;
        }
	if(!p)
        return ERROR;
    else{
        s=(LinkList)malloc(sizeof(LNode));
        s->data=e;
        s-next=p->next;
        p->next=s;
        return OK;
    }
}

删除特定位置结点

//删除i位置的结点,值存入e
ListDelete_L(LinkList L,int i,ElemType &e){
    p=L;
    j=0;
    while(p&&j<i-1){
        p=p->next;
        ++j;
    }//首先找到被删除节点的前驱节点
    if(!(p->next))
        return ERROR;//结点不存在
    q=p->next;
    p->next=q->next;
    e->data=q->data;
    free(q);
    retun OK;
}

链表的优点:插入删除不需要移动数据、不需要预先分配空间

链表的缺点:指针占用存储空间,增加了内存负担,不是随机存储,是顺序存储结构(逻辑上)

头插法建立单链表

p=(LNode*)malloc(sizeof(LNode));
p->data=e;
p->next=L->next;
L->next=p;

尾插法建立单链表

r=L;
p=(LNode*)malloc(sizeof(LNode));
p->data=e;
r->next=p;
r=p;
r->next=NULL;//r跟踪链表的尾部

循环链表

单链表的最后一个结点的指针域没有利用,如果使其指向头指针(头结点),则首尾构成了一个循环,称作循环链表。

从循环链表的任意结点出发都可以找到表中的其他节点。

尾指针:指向表尾的指针。循环链表通常使用尾指针,因为使用头指针的话,不容易查找尾结点。

单链表合并

合并La、Lb

//带头结点的链表合并
R=La;
while(R->next)
    R=R->next;
R->next=Lb->next;
free(Lb);

循环链表合并

//带头结点头指针的循环链表合并
R1=La;
while(R1->next!=La)
    R1=R1->next;
R2=Lb;
while(R2->next!=Lb)
    R2=R2->next;
R1->next=Lb->next;
R2->next=La;
free(Lb);

//带头结点尾指针循环链表合并
L1=La->next;
L2=Lb->next;
La->next=L2->next;
Lb->next=L1;
free(L2);

双向链表

一个链表的每一个结点都包含两个指针域,一个指针域指向其前驱节点,另一个指向其后继结点。可以对链表进行双向查找。

typedef struct DuLNode{
    ElemType data;
    struct DuLNode *prior;
    struct DuLNode *next;
}DuLNode,*DuLinkList;

//p结点
p->next->prior;
p->prior->next;

双向链表插入节点

// 在a结点之前插入,记a结点位置是p,插入节点为s
s->prior=p->prior;
s->next=p->next;
p->prior->next=s;
p->prior=s;

双向链表删除结点

//删除a结点,位置为p
p->prior->nezt=p->next;
p->next->prior=p->prior;
free(p);

双向链表的插入算法

void ListInsert_Dul(DuLinkList &L,int i,ElemType e){
    find(p);
    s-data=e;
    s->prior=p->prior;
    s->next=p;
    p->prior->next=s;
    p->prior=s;
}

数据结构之链表/单链表/静态链表/循环链表/双向链表_第1张图片

静态链表

在没有指针的语言中构造的链表。使用前仍然需要预先分配空间。采用一个叫游标的东西实现链表的功能。使用数组来维护下标。静态链表实际上是高级的线性表。

不同的是,数据在数组中的存储是随机的。数据之间通过游标进行链接。

FAT文件存储的FAT表就是静态链表。

静态链表的结点结构:

typedef struct{
    int data;
    int cur;//游标
}Sta_Lis_Node;

此外,静态链表除了数据本身外,还包含备用链表,用来存储未使用的空间。

数据结构之链表/单链表/静态链表/循环链表/双向链表_第2张图片

通常a[0]放备用链表的表头,a[1]放数据链表的表头。

你可能感兴趣的:(数据结构与算法/刷题笔记,数据结构)