利用单链表存储一元多项式_2.3 线性表的链式存储(1)

f25c5b3b3f3aa2b6db0d9d22cde8d5bc.png

返回目录:

Chilan Yu:《数据结构》目录链接​zhuanlan.zhihu.com
90705c66296f193072c219557e74cfea.png

2.3.1 单链表

结点包括两个域:数据域用来存储结点的值,指针域用来存储数据元素的直接后继的地址(或位置)。

7a9fc2873877b9df5e0258023f07e801.png
单链表结点结构

由于此线性表的每个结点只有一个next指针域,故将这种链表称为单链表

单链表中每个结点的存储地址存放在其前驱结点的指针域中,由于线性表中的第一个结点无前驱,所以应设一个头指针H指向第一个结点。由于线性表的最后一个结点没有直接后继,则指定单链表的最后一个结点的指针域为“空”(NULL)

附设头结点的目的:为方便统一空表或非空表的运算处理。
头结点的数据域可以存储一些关于线性表的长度等附加信息,也可以不存储任何信息,对头结点数据域信息无特别规定
而头结点的指针域则用来存储指向第一个结点的指针(即第一个结点的存储位置)。此时头指针就不再指向表中第一个结点而是指向头结点。如果线性表为空表,则头结点的指针为“空”。

6318e1cd958f60cb53f69b8f39954142.png

单链表的存储结构:

#define ElemType int

typedef struct Node{
    ElemType data;
    struct Node * next;
}Node, *LinkList;//LinkList为结构指针类型

LinkList与Node * 同为结构指针类型,这两种类型是等价的。通常习惯上用LinkList说明指针变量,强调它是某个单链表的头指针变量。例如,使用定义LinkList L,则L为单链表的头指针,从而提高程序的可读性。用Node * 来定义指向单链表中结点的指针,例如,Node * p,则p为指向单链表中结点的指针变量

  • 若L是单链表的头指针,它指向表中的第一个结点,若L==NULL,则表示单链表为一个空表。
  • 若L是单链表的头指针,它指向单链表的头结点,若L->next==NULL,则表示单链表为一个空表。
  • 若指针p所指结点是尾结点,则p->next==NULL。

2.3.2 单链表上的基本运算

1. 初始化单链表

/*初始化单链表*/
void InitList(LinkList *L){
    *L = (Node *)malloc(sizeof(Node));
    (*L)->next = NULL;
}

注意:L是指向单链表的头指针的指针,用来接收主程序中待初始化单链表的头指针变量的地址,*L相当于主程序中待初始化单链表的头指针变量。

2. 建立单链表

假设线性表中结点的数据类型是字符,逐个输入这些字符,并以“$”作为输入结束标识符。

(1)头插法建表

【算法思想】从一个空表开始,每次读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头结点之后,直至读入结束标志为止。

4db1235b39a42928e1e422e611fa6d41.png
/*头插法建表*/
void CreateFromHead(LinkList L){//L是带头结点的空链表头指针
    Node * s;
    ElemType c;
    int flag = 1;
    while(flag){//flag的初值为1,当输入“$”时,置flag为0,建表结束
        c = getchar();
        if(c!='$'){
            s = (Node *)malloc(sizeof(Node));
            s->data = c;
            s->next = L->next;
            L->next = s;
        }
        else flag = 0;
    }
}

采用头插法得到的单链表的逻辑顺序与输入元素顺序相反,头插法建表为逆序建表法

(2)尾插法建表

尾插法建表可以使生成的链表中结点的次序与输入的顺序一致。该方法是将新结点插到当前单链表的表尾上。为此需要增加一个尾指针r,使之指向当前单链表的表尾。

f550c6c729b78c6f5ab46e062797ff27.png
/*尾插法建表*/
void CreateFromTail(LinkList L){//L是带头结点的空链表头指针
    Node *r, *s;
    ElemType c;
    int flag = 1;
    r = L;//r指针动态指向链表的当前表尾,以便做尾插入,其初值指向头结点
    while(flag){
        c = getchar();
        if(c!='$'){
            s = (Node *)malloc(sizeof(Node));
            s->data = c;
            r->next = s;
            r = s;
        }
        else{
            flag = 0;
            r->next = NULL;//将最后一个结点的next域置空,表示链表结束
        }
    }
}

3. 查找

(1)按序号查找

【算法描述】设带头结点的单链表的长度为n,要查找表中第i个结点,则需要从单链表的头指针L出发,从头结点(L->next)开始顺着链域扫描,用指针p指向当前扫描到的结点,初值指向头结点(pL->next),用j做记数器,累计当前扫描过的结点数(初值为0),当j = i 时,指针p所指的结点就是要找的第i个结点 。

/*查找第i个结点*/
Node * Get(LinkList L,int i){//查找第i个结点,找到则返回该结点的存储位置,否则返回NULL
    int j;
    Node * p;
    if(i<=0) return NULL;
    p = L;
    j = 0;
    while((p->next!=NULL) && (j<i)){
        p = p->next;
        j++;
    }
    if(i==j) return p;
    else return NULL;
}

(2)按值查找

【算法思想】按值查找是指在单链表中查找是否有值等于e的结点。查找过程从单链表的头指针指向的头结点出发,顺链逐个将结点值和给定值e作比较,返回查找结果。

/*查找值为key的结点*/
Node * Locate(LinkList L,ElemType key,int *loc){//查找值为key的结点,找到则返回该结点的存储位置,否则返回NULL,用loc指针返回其所在位置
    Node * p;
    p = L->next;//从表中第一个结点开始
    *loc = 1;
    while(p!=NULL){
        if(p->data!=key){
            p = p->next;
            (*loc)++;
        }
        else break;
    }
    return p;
}

以上两个算法的平均时间复杂度均为O(n)。

4. 求单链表长度操作

【算法思想】从“头”开始“数”(p=L->next),用指针p依次指向各个结点,并附设计数器j计数,一直“数”到最后一个结点(p->next==NULL),从而得到单链表的长度。

/*求单链表的长度*/
int ListLength(LinkList L){//求带头结点的单链表L的长度
    Node * p;
    p = L->next;
    int j = 0;
    while(p!=NULL){
        p = p->next;
        j++;
    }
    return j;
}

算法的时间复杂度为O(n)。

5. 单链表插入操作

【问题要求】在线性表的第i(1<=i<=n+1)个位置前插入一个新元素e。【算法思想】
(1)查找:在单链表中找到第i-1个结点并由指针pre指示。
(2)申请:申请新结点s,将其数据域的值置为e。
(3)插入挂链:通过修改指针域将新结点s挂入单链表L。

43bf8a146156e97288e3bde2aa46741a.png
/*插入操作*/
void InsList(LinkList L,int i,ElemType e){//在带头结点的单链表L中第i个位置插入值为e的新结点
    Node * pre, * s;
    int k;
    if(i<=0) return ;
    pre = L;
    k = 0;//从头开始,查找第i-1个结点
    while(pre!=NULL && k<i-1){//表未查完,找到pre指向第i-1个
        pre = pre->next;
        k++;
    }
    if(pre==NULL){//当表已被查找完却还没有数到第i个
        cout << "插入位置不合理!" << endl;
        return ;
    }
    s = (Node *)malloc(sizeof(Node));
    s->data = e;
    s->next = pre->next;
    pre->next = s;
    return ;
}

6. 单链表删除操作

【问题要求】将线性表的第i(1<=i<=n+1)个元素e删除。【算法思想】
(1)查找:通过计数方法找到第i-1个结点并由指针pre指示。
(2)删除第i个结点并释放结点空间。

9118ef0e8b1a2be145417b7be2aa4bb8.png
/*删除操作*/
void DelList(LinkList L,int i,ElemType *e){//删除第i个元素,并将其保存到变量*e中
    Node * pre, * r;
    int k = 0;
    if(i<=0){
        cout << "删除结点的位置不合理!" << endl;
        return ;
    }
    pre = L;
    while(pre->next!=NULL && k<i-1){//使pre指向第i-1个结点
        pre = pre->next;
        k++;
    }
    if(pre->next==NULL){
        cout << "删除结点的位置不合理!" << endl;
        return ;
    }
    r = pre->next;
    pre->next = r->next;
    *e = r->data;
    free(r);
    return ;
}

说明:删除算法中的循环条件(pre->next!=NULL && k<i-1)与 前插入算法中的循环条件(pre!=NULL && k<i-1)不同,因为前插时的插入位置有m+1个(m为当前单链表中数据元素的个数)。i=m+1是指在第m+1个位置前插入,即在单链表的末尾插入。而删除操作中删除的合法位置只有m个,若使用与前插操作相同的循环条件,则会出现指针指空的情况,使删除操作失败。


给出以上操作的完整代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;

#define ElemType char

typedef struct Node{
    ElemType data;
    struct Node * next;
}Node, *LinkList;//LinkList为结构指针类型

/*初始化单链表*/
void InitList(LinkList *L){
    *L = (Node *)malloc(sizeof(Node));
    (*L)->next = NULL;
}

/*头插法建表*/
void CreateFromHead(LinkList L){//L是带头结点的空链表头指针
    Node * s;
    ElemType c;
    int flag = 1;
    while(flag){//flag的初值为1,当输入“$”时,置flag为0,建表结束
        c = getchar();
        if(c!='$'){
            s = (Node *)malloc(sizeof(Node));
            s->data = c;
            s->next = L->next;
            L->next = s;
        }
        else flag = 0;
    }
}

/*尾插法建表*/
void CreateFromTail(LinkList L){//L是带头结点的空链表头指针
    Node *r, *s;
    ElemType c;
    int flag = 1;
    r = L;//r指针动态指向链表的当前表尾,以便做尾插入,其初值指向头结点
    while(flag){
        c = getchar();
        if(c!='$'){
            s = (Node *)malloc(sizeof(Node));
            s->data = c;
            r->next = s;
            r = s;
        }
        else{
            flag = 0;
            r->next = NULL;//将最后一个结点的next域置空,表示链表结束
        }
    }
}

/*查找第i个结点*/
Node * Get(LinkList L,int i){//查找第i个结点,找到则返回该结点的存储位置,否则返回NULL
    int j;
    Node * p;
    if(i<=0) return NULL;
    p = L;
    j = 0;
    while((p->next!=NULL) && (j<i)){
        p = p->next;
        j++;
    }
    if(i==j) return p;
    else return NULL;
}

/*查找值为key的结点*/
Node * Locate(LinkList L,ElemType key,int *loc){//查找值为key的结点,找到则返回该结点的存储位置,否则返回NULL,用loc指针返回其所在位置
    Node * p;
    p = L->next;//从表中第一个结点开始
    *loc = 1;
    while(p!=NULL){
        if(p->data!=key){
            p = p->next;
            (*loc)++;
        }
        else break;
    }
    return p;
}

/*求单链表的长度*/
int ListLength(LinkList L){//求带头结点的单链表L的长度
    Node * p;
    p = L->next;
    int j = 0;
    while(p!=NULL){
        p = p->next;
        j++;
    }
    return j;
}

/*插入操作*/
void InsList(LinkList L,int i,ElemType e){//在带头结点的单链表L中第i个位置插入值为e的新结点
    Node * pre, * s;
    int k;
    if(i<=0) return ;
    pre = L;
    k = 0;//从头开始,查找第i-1个结点
    while(pre!=NULL && k<i-1){//表未查完,找到pre指向第i-1个
        pre = pre->next;
        k++;
    }
    if(pre==NULL){//当表已被查找完却还没有数到第i个
        cout << "插入位置不合理!" << endl;
        return ;
    }
    s = (Node *)malloc(sizeof(Node));
    s->data = e;
    s->next = pre->next;
    pre->next = s;
    return ;
}

/*删除操作*/
void DelList(LinkList L,int i,ElemType *e){//删除第i个元素,并将其保存到变量*e中
    Node * pre, * r;
    int k = 0;
    if(i<=0){
        cout << "删除结点的位置不合理!" << endl;
        return ;
    }
    pre = L;
    while(pre->next!=NULL && k<i-1){//使pre指向第i-1个结点
        pre = pre->next;
        k++;
    }
    if(pre->next==NULL){
        cout << "删除结点的位置不合理!" << endl;
        return ;
    }
    r = pre->next;
    pre->next = r->next;
    *e = r->data;
    free(r);
    return ;
}

void show(LinkList L){
    Node * s = L->next;
    while(s!=NULL){
        cout << s->data << " ";
        s = s->next;
    }
    cout << endl;
}

int main()
{
    LinkList L;
    InitList(&L);
    cout << "请依次输入链表元素(以$结束):";
    CreateFromTail(L);
    show(L);

    /*按序号查找结点*/
    int loc1;
    Node * tmp1;
    cout << "请输入待查结点的位置:";
    cin >> loc1;
    tmp1 = Get(L,loc1);
    cout << "该结点的值为" << tmp1->data << endl;

    /*按值查找*/
    ElemType x;
    int loc2;
    Node * tmp2;
    cout << "请输入待查结点的值:";
    cin >> x;
    tmp2 = Locate(L,x,&loc2);
    if(tmp2) cout << "查找成功,其位于第" << loc2 << "号位置" << endl;
    else cout << "查找失败" << endl;

    /*单链表长度*/
    cout << "单链表的长度为:";
    cout << ListLength(L) << endl;

    /*插入元素*/
    int loc3;
    ElemType tmp3;
    cout << "请输入待插入元素:";
    cin >> tmp3;
    cout << "请输入待插入位置:";
    cin >> loc3;
    InsList(L,loc3,tmp3);
    show(L);

    /*删除元素*/
    int loc4;
    ElemType tmp4;
    cout << "请输入待删除元素的位置:";
    cin >> loc4;
    DelList(L,loc4,&tmp4);
    if(tmp4) cout << "成功删除" << tmp4 << endl;
    show(L);

    return 0;
}

3223da1a1e5e3535e3498c6b224db7a5.png

例2.4:有两个单链表LA和LB,其元素均为非递减有序排列,编写一个算法,将它们合并成一个单链表LC,要求LC也是非递减有序排列。要求:利用新表LC利用现有的表LA和LB中的元素结点空间,而不要额外申请结点空间。例如LA=(2,2,3),LB=(1,3,3,4),则LC=(1,2,2,3,3,3,4)

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;

#define ElemType int

typedef struct Node{
    ElemType data;
    struct Node * next;
}Node, *LinkList;//LinkList为结构指针类型

/*初始化单链表*/
void InitList(LinkList *L){
    *L = (Node *)malloc(sizeof(Node));
    (*L)->next = NULL;
}

/*尾插法建表*/
void CreateFromTail(LinkList L,int len){//L是带头结点的空链表头指针
    Node *r, *s;
    ElemType c;
    r = L;//r指针动态指向链表的当前表尾,以便做尾插入,其初值指向头结点
    while(len--){
        cin >> c;
        s = (Node *)malloc(sizeof(Node));
        s->data = c;
        r->next = s;
        r = s;
    }
    r->next = NULL;//将最后一个结点的next域置空,表示链表结束
}

/*合并两个有序的单链表*/
LinkList MergeLinkList(LinkList LA,LinkList LB){//将递增有序的单链表LA和LB合并成一个递增有序的单链表LC
    Node *pa, *pb, *r;
    LinkList LC;
    /*j将LC置为空表。pa和pb分别指向两个单链表LA和LB中的第一个结点,r初值为LC且r始终指向LC的表尾*/
    pa = LA->next;
    pb = LB->next;
    LC = LA;
    LC->next = NULL;
    r = LC;
    /*当两个表中均未处理完时,比较选择将较小值结点插入到新表LC中*/
    while(pa!=NULL && pb!=NULL){
        if(pa->data<=pb->data){
            r->next = pa;
            r = pa;
            pa = pa->next;
        }
        else{
            r->next = pb;
            r = pb;
            pb = pb->next;
        }
    }
    if(pa)//若表LA未完,将表LA中后继元素链到新表LC表尾
        r->next = pa;
    else
        r->next = pb;
    free(LB);
    return LC;
}

void show(LinkList L){
    Node * s = L->next;
    while(s!=NULL){
        cout << s->data << " ";
        s = s->next;
    }
    cout << endl;
}

int main()
{
    LinkList L1;
    InitList(&L1);
    int len1;
    cout << "请依次输入链表L1长度:";
    cin >> len1;
    cout << "请依次输入链表L1元素:";
    CreateFromTail(L1,len1);

    LinkList L2;
    InitList(&L2);
    int len2;
    cout << "请依次输入链表L2长度:";
    cin >> len2;
    cout << "请依次输入链表L2元素:";
    CreateFromTail(L2,len2);

    cout << "L1:";
    show(L1);
    cout << "L2:";
    show(L2);

    LinkList L3;
    L3 = MergeLinkList(L1,L2);
    cout << "合并之后L3:";
    show(L3);

    return 0;
}

d25706b02dbd245fdb0dd160b3202d87.png

例2.5

已知以单链表表示集合,假设集合A用单链表LA表示,集合B用单链表LB表示,设计算法求两个集合的差,即A-B。

【算法思想】由集合运算的规则可知,集合的差A-B中包含所有属于集合A而不属于集合B的元素。具体做法是,对于集合A中的每个元素e,在集合B的链表LB中进行查找,若存在与e相同的元素,则从LA中将其删除。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;

#define ElemType int

typedef struct Node{
    ElemType data;
    struct Node * next;
}Node, *LinkList;//LinkList为结构指针类型

/*初始化单链表*/
void InitList(LinkList *L){
    *L = (Node *)malloc(sizeof(Node));
    (*L)->next = NULL;
}

/*尾插法建表*/
void CreateFromTail(LinkList L,int len){//L是带头结点的空链表头指针
    Node *r, *s;
    ElemType c;
    r = L;//r指针动态指向链表的当前表尾,以便做尾插入,其初值指向头结点
    while(len--){
        cin >> c;
        s = (Node *)malloc(sizeof(Node));
        s->data = c;
        r->next = s;
        r = s;
    }
    r->next = NULL;//将最后一个结点的next域置空,表示链表结束
}

/*两个集合的差集*/
LinkList Difference(LinkList LA,LinkList LB){//求两个集合的差集
    Node  *pre, *p, *r, *q;
    pre = LA;
    p = LA->next;//p指向表中的某一结点,pre始终指向p的前驱
    while(p!=NULL){
        q = LB->next;//扫描LB中的结点,寻找与LA中*P结点相同的结点
        while(q!=NULL && q->data!=p->data)
            q = q->next;
        if(q!=NULL){
            r = p;
            pre->next = p->next;
            p = p->next;
            free(r);
        }
        else{
            pre = p;
            p = p->next;
        }
    }
    return LA;
}


void show(LinkList L){
    Node * s = L->next;
    while(s!=NULL){
        cout << s->data << " ";
        s = s->next;
    }
    cout << endl;
}

int main()
{
    LinkList L1;
    InitList(&L1);
    int len1;
    cout << "请依次输入链表L1长度:";
    cin >> len1;
    cout << "请依次输入链表L1元素:";
    CreateFromTail(L1,len1);

    LinkList L2;
    InitList(&L2);
    int len2;
    cout << "请依次输入链表L2长度:";
    cin >> len2;
    cout << "请依次输入链表L2元素:";
    CreateFromTail(L2,len2);

    cout << "L1:";
    show(L1);
    cout << "L2:";
    show(L2);

    LinkList L3;
    L3 = Difference(L1,L2);
    cout << "差集L3:";
    show(L3);

    return 0;
}

8300304076871c115313b0830384eff3.png

返回目录:

Chilan Yu:《数据结构》目录链接​zhuanlan.zhihu.com
90705c66296f193072c219557e74cfea.png
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值