数据结构知识整理 - 链表(本篇包括单链表、循环链表以及双向链表)

版权声明: https://blog.csdn.net/Ha1f_Awake/article/details/85146895

主要内容


 

链表的基本概念

链表的特点是:用一组任意的存储单元存储某逻辑结构的数据元素(存储单元可连续可不连续)。

因此,为了表示每个数据元素与其直接后继数据元素的逻辑关系,除了要存储数据元素本身的信息外,还需要存储一个指向直接后继数据元素的指针(对于线性表而言)。这两部分信息组成数据元素的存储映像,称为结点

结点包括两个域:数据域(存储数据元素信息)和指针域(存储直接后继的指针)。

但结点的指针域并不是只能存放一个指针。根据链表结点所含指针数、指针指向和指针连接方式的不同,可以将链表分为(线性表:)单链表、循环链表、双向链表(树:)二叉链表(图:)邻接表、十字链表、邻接多重表


链表的基本术语和存储结构

1)首元结点链表中存储第一个数据元素的结点。

2)头结点在首元结点之前的附加结点,其数据域为NULL(也可以存储其他有用的信息),指针域存储指向首元结点的指针。

3)头指针指向链表中的第一个结点,或是首元结点,或是头结点。

4)末尾结点链表中存储最后一个数据元素的结点。

5)尾指针指向链表中的最后一个结点。

typedef struct LNode    /*单链表结点*/
{
    Elemtype data;
    struct LNode *next;
} LNode, *LinkList;     /*LNode表示结点内容,LinkList表示单链表的起始存储位置*/

因为链表的存储单元不像顺序表那样固定,所以我们只能用链表的起始存储位置来表示一个链表,而不能像顺序表那样用一个数组来表示。

扫描二维码关注公众号,回复: 4587910 查看本文章

链表的基本操作

1)初始化链表

void InitList(Linklist &L)
{
    L = new LNode;    /*生成新的头结点,返回指针给L*/
    L->next = NULL;   /*指针域设置为空*/
}

2)根据给定的结点位置取值

Elemtype GetElem(LinkList L, int i)    /*传递指定位置i*/
{
    LNode *p = L->next;      /*使指针p指向首元结点*/
    int timer = 1;

    while(p && timer < i)    /*当p为非空且timer未到i时循环*/
    {
        p = p->next;
        timer++;
    }

    if(!p || timer > i) return ERROR;    /*如果p为空或timer超过i,报错*/

    else return p->data;
}

3)查找结点,返回指向结点的指针:

LNode *SearchNode(LinkList L, Elemtype check)
{
    LNode *p = L->next;            /*p指向首元结点*/

    while(p && p->data != check)   /*当p为非空且p->不等于给定值时循环*/
        p = p->next;

    return p;                      /*成功则返回地址,失败时p为NULL,返回NULL*/
}

4)在给定位置上插入结点,插入的思想还可推广到创建链表跟合并链表上:

void InsertNode(LinkList &L, int i, Elemtype e)
{
    LNode *p = L->next;
    int timer = 1;
    
    while(p && timer < (i-1))    /*找到第i-1个结点的位置*/
    {
        p = p->next;
        timer++;
    }

    LNode insert = new LNode;    /*新结点*/

    insert->data = e;

    insert->next = p->next;      /*插入*/
    p->next = insert;
}

5)删除给定位置的结点:

在删除结点时,除了修改相连结点的指针外,还需要释放结点的存储空间(通过delete实现)。所以我们要引入一个指针临时保存被删除结点的地址,以备释放。

void DeleteNode(LinkList &L, int i)
{
    LNode *p = L->next;
    int timer = 1;

    while(p && timer < (i-1))                  /*找到第i-1个结点*/
    {
        p = p->next;
        timer++;
    }                              /*上面这段代码写好多遍了...*/

    if(!p->next || j > (i-1)) return ERROR;    /*如果没找到要删除的结点,报错*/

    LNode *temp = p->next;         /*temp暂存第i个结点的地址*/

    p->next = temp->next;          /*使第i-1个结点指向第i个结点指向的下一个结点*/

    delete temp;    /*释放空间*/
}

将上面的代码稍微修改也能得到“删除数据元素等于给定值的结点”的算法,这里就不多写了。


循环链表

使单链表中的最后一个结点指向头结点,整个链表形成一个环,这就是循环链表。从表中的任意一点出发都能找到表中的其他结点。

在某些情况下,若在循环链表中设尾指针(指向最后一个结点)而不设头指针,可以简化一些操作。

例如合并两个线性表,只需要将第二个表的末尾结点指向第一个表的头结点a,第一个表的末尾结点指向第二个的首元结点b,然后释放第二个表的头结点c

/*first为第一个表的尾指针,second为第二个表的尾指针*/

LNode *temp1 = second->next;    /*temp1暂存第二个表的头结点地址*/

LNode *temp2 = temp1->next;     /*temp2暂存第二个表的首元结点地址*/

second->next = first->next;     /*a*/

first->next = temp2;            /*b*/

delete temp1;                   /*c*/

双向链表

在单链表中,查找直接后继结点的执行时间为O(1),而查找直接前驱的执行时间为O(n)。为克服单链表的这种单向性缺点,可利用双向链表,即增加指向直接前驱的指针域。

typedef struct DuLNode
{
    Elemtype data;
    struct DuLNode *prior;    /*直接前驱*/
    struct DuLNode *next;     /*直接后继*/
} DuLNode, *DuLinkList;

虽然增加一个指针域后,插入和删除操作需要修改更多的信息,但其实双向链表和单链表的时间复杂度都为O(n)

猜你喜欢

转载自blog.csdn.net/Ha1f_Awake/article/details/85146895