数据结构之美(四)线性表的链式存储结构

(总结源自《大话数据结构》,初学数据结构推荐此书)

目录

线性表的链式存储结构

基础知识与概念

头指针与头结点

单链表的读取 O(n)

单链表的插入  O(n)

单链表的删除 O(n)

单链表的整表创建

单链表的整表删除



线性表的链式存储结构

基础知识与概念

顺序存储结构比较简单,但它是有缺点的:插入和删除的时候需要移动大量元素,若在第一个元素之前的位置插入元素或删除第一个位置的元素,则所有元素都需要往后移动。

为什么呢? 因为顺序存储是紧挨着的,元素和元素之间没有空隙,容不下其他元素插足,而删除元素留下的空隙也需要被填补。为了解决这个问题,大神们引入了链式存储结构:每个元素不再紧挨直接后继元素,而是知道直接后继元素的位置。如何知道呢?指针!

指针是指向直接后继元素的位置的,而最后一个元素没有直接后继元素,则它的指针域为NULL或^。

头指针与头结点

头指针:链表中第一个结点的存储位置就叫头指针

头结点:头结点可不是第一个结点! 为了更加方便对链表的操作,会在单链表的第一个结点前附设一个结点,称为头结点,它可以不存储任何信息(也可以存储线性表长度等附加信息),头结点的指针域存储指向第一个结点的指针。

(《大话数据结构》书中图3-6-4与图3-6-6的头指针在头结点位置,是错的。头指针指向头结点(如果有头结点的话,图中则表示为黑色结点))

解释:头指针无论什么时候都不为空——链表为空时,头结点的指针域为空,头指针指向头结点,不为空。

/*线性表的单链表存储结构*/
typedef int ElemType;
typedef int Status;

typedef struct Node
{
    ElemType data;
    struct Node *next;
}Node;

typedef struct Node *LinkList; /*定义LinkList*/

那么,若p是指向线性表第i个元素的指针,则p->data则可以表示a(i)的数据与,p->next则可以表示a(i)的指针域。

那么,p->next->data=a(i+1)的数据域,即

单链表的读取 O(n)

由于是链表,所以我们并不知道第i个元素的位置,只能从头开始找。

思路:声明一个指针指向第一个结点,遍历链表,若到链表最末端,仍未查到所需数据,则数据不存在,反之查找成功。

//查找链表第i个数据
Status GetElem(LinkList L ,int i,ElemType *e)
{
    int j; //计数器
    LinkList p; //定义一个结点
    p=L->next;
    j=1;
    while(p && j<i)  //遍历链表
    {
        p=p->next;
        ++j;
    }
    if( !p || j>1 )  //查找失败的情况
        return ERROR;
    *e=p->data;
    return OK;
} 

(上图来自极客时间的数据结构与算法之美专栏)

单链表的插入  O(n)

前面的读取相比顺序存储结构而言,并不能看出链表的优势,而链表的插入相比较顺序存储结构就会显得非常方便。

这是因为顺序存储结构在插入与删除操作时,需要移动插入与删除后的所有数据,而链表在插入操作中,只有两个结点(插结点S与被插前结点P)发生了联系与变化。(核心代码:s->next=p->next; p->next=s;  顺序千万不能错,可以自己推导一下错了会怎么样)

//在第i个数据前插入结点
Status ListInsert(LinkList *L,int i, ElemType e )
{
    int j;    //计数器
    LinkList p,s; //定义结点
    p=*L;
    j=1;
    while(p&&j<i) //遍历
    {
        p=p->next;
        ++j;
    }
    if(!p||j>i)
        return ERROR;
    s=(LinkList)malloc(sizeof(Node)); //开辟一块空间给e数据
    
    //将e赋予s结点,后进行插入操作
    s->data=e;
    s->next=p->next;
    p->next=s;
    return OK;
}

单链表的删除 O(n)

单链表的删除操作本质上就是绕过要删除的结点。A牵着B的手,B牵着C的手,A现在绕过B,直接牵起C的手,就是单链表的删除操作。(核心:p->next=p->next->next)

//删除第i个数据的结点
Status ListDelete (LinkList *L,int i)
{
    int j;
    LinkList p,q;   
    p=*L;
    j=1;

    while(p->next && j<i)   //寻找第i个数据
    {
       p=p-> next;
       ++j;
    }

    if(!(p->next) || j>i)   //数据不存在
        return ERROR;

    q=p->next;              
    p->next=q->next;

    free(q);              //释放第i个数据
    
   return OK;
}

单链表的整表创建

创建单链表的思路:先创建一个空表,再创建结点续在空表后。续分前后,所以就有了两种方法:头插法和尾插法

头插法:在表头插入

//随机产生n个元素的值,建立代表头结点的单链线性表L(头插法)
void CreateListHead(LinkList *L,int n)
{
    LinkList p;
    int i;

    srand(time(0));
    *L = (LinkList)malloc(sizeof (Node));
    (*L)->next=NULL;    //L为空表

    for(i=0;i<n;i++)
    {
        p=(LinkList)malloc(sizeof(Node));
        p->data =rand()%100+1;
        p->next = (*L)->next;
        (*L)->next=p;
    }
}

尾插法:在表尾插入

void CreateListTail(LinkList *L, int n )
{
    LinkList p,r;
    int i;
    srand( time (0) );
    *L = (LinkList) malloc (sizeof(Node));
    r=*L;
    for (i=0; i<n;i++)
    {
        p=(Node *)malloc(sizeof(Node));
        p->data=rand()%100+1;
        r->next=p;
        r=p;
    }   
    r->next=NULL;
}    

这里要分清r和L的区别,r是指向尾结点的变量(这也是最后r=p的原因),L是整个单链表。

单链表的整表删除

删除的思路:

Status ClearList(LinkList *L)
{
    LinkList p,q;
    p=(*L)->next;

    while(p)
    {
        q=p->next;
        free(p);
        p=q;
    }

    (*L)->next=NULL;
    return OK;
}
发布了38 篇原创文章 · 获赞 6 · 访问量 1918

猜你喜欢

转载自blog.csdn.net/weixin_43827227/article/details/99613398
今日推荐