【数据结构】线性表的链式表示

一、单链表

(1)单链表的定义

单链表是通过任意的存储单元来存储线性表中的数据元素的,每个链表结点,不仅包含元素的信息,而且还有一个指向其后继结点的指针。

其结构如下图:

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

因为单链表的元素在存储空间的分布是离散的,所以区别于顺序表,单链表不能采取随机存取。只能从表头遍历,依次查找。

头指针指向链表的起始地址,当头指针为NULL时,说明这个链表为空链表。通常,会在链表的第一个数据结点前添加一个头节点,头结点的数据域可以不设信息,但头结点的指针域要指向后继结点,若无后继结点则为NULL。在单链表中,表的最后的结点的指针域也为NULL。

(2)基本操作的实现

①初始化

单链表的初始化有两种,分别为带头结点以及不带头结点。

而这两种方式的初始化操作也存在一定的差异。

a.带头结点初始化:

bool InitList(LinkList &L){
    L=(LNode*)malloc(sizeof(LNode);        //创建头结点
    L.next->NULL;                          //头结点的指针域为空
    return true;
}

b.不带头结点初始化:

bool InitList(LinkList &L){
    L=NULL;                    //头指针为空
    return true;
}

②求表长操作

求表长,即计算含数据信息结点的个数,需要从第一个结点开始依次遍历,每完成依次遍历则让计数变量加一,直至访问到指针域为NULL的结点。

int Length(LinkList L){
    int length=0;
    LNode *p=L;                //创建p指针,指向链表的头指针
    while(p!=NULL){      //依次遍历,直到表尾
        p=p->next;
        length++;
    }
    return length;
}

③按序号查找表结点

从单链表的第一个元素开始遍历,找到所查序号的结点结束,并返回该结点的指针。若没有,返回NULL。

LNode *GetElem(LinkList L,int i){
    LNode *p=L;
    int j=0;
    while(p!=NULL && j<i){    //找到第i个结点时跳出循环
        p=p->next;
        j++;
    }
    return p;
}

④按值查找表结点

从单链表的第一个元素开始遍历,依次与结点的指针域作比较,若找到结点的数据域等于给定的值,则输出指向该节点的指针;若没有,返回NULL。

LNode *LocateElem(LinkList L,int x){
    LNode *p=L;
    while(p!=NULL && p->data!=x)
        p=p->next;
    return p;
}

⑤插入结点

插入一个新的结点到第i个位置上。首先判断i的值是否合法,再找到第i个位置的前驱,再进行插入操作。

bool InsertList(LinkList &L,int i,ElemType e){
    LNode *p=L;
    int j=0;
    while(p!=NULL && j<i-1){            //找到第i-1个结点
        p=p->next;
        j++;
    }

    if(p==NULL) return false;           //i值不合法退出程序

    LNode *s=(LNode*)malloc(sizeof(LNode));    //为新结点分配存储空间
    s->data=e;
    s->next=p->next;
    p->next=s;
    return true;
}

⑥删除结点

将单链表中第i个结点删除。首先查找第i-1个结点,再检查输入的i值是否合法,再删除第i个结点。最后,返回所删除结点的数据域信息。

bool DeleteList(LinkList &L,int i,ElemType &x){
    LNode *p=L;
    int j=0;
    while(p!=NULL && j<i-1){        //找到删除结点的前驱结点
        p=p->next;
        j++;
    }
    if(p==NULL) return false;       //i值不合法
    LNode *q=p->next;               //令q指针指向第i个结点
    x=q->data;                      //存储所删除结点的数据
    p->next=q->next;
    free(q);                        //释放q结点
    return true;
}

⑦头插法建立单链表

#define quitnum 999                         //退出数字
LinkList HeadInsert(LinkList &L){
    LNode *s;
    int x;
    L=(LNode*)malloc(sizeof(LNode));        //创建头结点
    L->next=NULL;
    scanf("%d",&x);
    while(x!=quitnum){
        s=(LNode*)malloc(sizeof(LNode));    //创建新结点
        s->data=x;
        s->next=L->next;
        L->next=s;
        scanf("%d",&x);
    }
    return L;
}

⑧尾插法建立单链表

#define quitnum 999                         //退出数字
LinkList RearInsert(LinkList &L){
    int x;
    L=(LNode*)malloc(sizeof(LNode));        //创建头结点
    LNode *s,*r=L;                         //r指针为表尾指针
    scanf("%d",&x);
    while(x!=quitnum){
        s=(LNode*)malloc(sizeof(LNode));    //创建新结点
        s->data=x;
        r->next=s;
        r=s;                                //r指向新的表尾结点
        scanf("%d",&x);
    }
    r->next=NULL;
    return L;
}

二、双链表

(1)双链表的定义

与单链表相区别,双链表的结点中有两个指针prior和next,这两个结点分别指向该结点的前驱结点及后继结点。特别地,双链表的表头结点的prior指针为NULL,表尾结点的next指针为NULL。

typedef struct DNode{
    ElemType data;
    struct DNode *prior,*next;
}DNode,*DLinkList;

(2)基本操作的实现

 ①插入结点

插入一个新的结点到第i个位置上。首先判断i的值是否合法,再找到第i个位置的前驱,再进行插入操作。

bool InsertDList(DLinkList &L,int i,ElemType e){
    DLNode *p=L;
    int j=0;
    while(p!=NULL && j<i-1){            //找到第i-1个结点
        p=p->next;
        j++;
    }

    if(p==NULL) return false;           //i值不合法退出程序

    DLNode *s=(DLNode*)malloc(sizeof(DLNode));    //为新结点分配存储空间
    s->data=e;
    s->next=p->next;
    s->prior=p->next->prior;
    p->next->prior=s;
    p->next=s;
    return true;
}

②删除结点

将单链表中第i个结点删除。首先查找第i-1个结点,再检查输入的i值是否合法,再删除第i个结点。最后,返回所删除结点的数据域信息。

bool DeleteDList(DLinkList &L,int i,ElemType &x){
    DLNode *p=L;
    int j=0;
    while(p!=NULL && j<i-1){        //找到删除结点的前驱结点
        p=p->next;
        j++;
    }
    if(p==NULL) return false;       //i值不合法
    DLNode *q=p->next;               //令q指针指向第i个结点
    x=q->data;                      //存储所删除结点的数据
    p->next=q->next;
    q->next->prior=p;
    free(q);                        //释放q结点
    return true;
}

三、循环链表

(1)循环单链表

定义一个表尾指针r,r->next==L即r的指针域指向L,从而形成一个环。

(2)循环双链表

L为头结点,r为尾结点。

L->prior==r
r->next==L

四、顺序表和链表的差异

(1)底层存储空间:顺序表使用一块连续的内存空间来存储元素,并通过下标来访问和操作元素。而链表则不同,它的各个节点在物理存储上并不连续,而是通过指针或引用进行连接。

(2)插入和删除操作:由于顺序表是连续存储的,因此在插入或删除元素时,可能需要移动其他元素以保持顺序。这种操作的时间复杂度较高,通常为O(n)。而链表在插入或删除元素时,只需要修改相关节点的指针或引用,无需移动大量元素,因此效率较高,时间复杂度通常为O(1)。

(3)随机访问:顺序表支持随机访问,即可以通过下标直接访问任意位置的元素,具有快速的随机访问能力。而链表则不支持随机访问,只能通过从头节点开始遍历来访问元素,访问时间复杂度较高。

(4)扩容:顺序表在插入元素时,如果当前空间已满,需要进行扩容操作,即开辟新的内存空间并复制原有元素。而链表在插入元素时无需扩容,只需动态分配新的节点即可。

(5)空间利用率:顺序表在预先分配的内存空间内可以充分利用空间,空间利用率较高。而链表由于需要存储节点的指针或引用信息,因此在空间利用率上相对较低。

(6)灵活性:链表相对于顺序表具有更高的灵活性。链表可以在运行时动态地创建和销毁节点,以适应不同规模的数据集。而顺序表则需要预先分配足够的内存空间,一旦空间不足就需要进行扩容操作,这可能会带来额外的开销。

  • 18
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值