线性表的链式存储

导读:本篇文章讲解 线性表的链式存储,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

目录 

链式存储结构

单链表

双链表

循环链表

头指针、头结点、首元结点

线性表的链式表示

如何表示空表

在链表中设置头结点的好处

头结点的数据域中装的是什么

单链表的定义和表示

带头结点的单链表

单链表各种操作的实现

单链表的初始化(带头结点的单链表)

判断链表是否为空

单链表的销毁(销毁后不存在)

单链表的清空(头指针和头结点仍然在)

求链表的表长

单链表的取值        O(n)

单链表的按值查找        O(n)

根据指定数据获取该数据所在的位置(地址)

根据指定数据获取该数据的位置序号

单链表的插入操作        O(n)

单链表的删除操作        O(n)

单链表的建立

头插法        O(n)

尾插法        O(n)

循环链表

循环链表的定义

循环链表的操作

尾指针表示的单循环链表

 带尾指针循环链表的合并(将Tb合并在Ta之后)

双向链表

双向链表的结点及其类型定义

双向链表的插入(先左后右)         O(n)

双向链表的删除        O(n)

单链表、循环链表、双向链表的比较

 顺序表和链表的比较

线性表的应用

线性表的合并

有序表的合并

用顺序表实现

用链表实现

案例分析与实现

一元多项式的运算

稀疏多项式的运算

顺序存储实现

链式存储实现

图书信息管理系统

链式存储结构

  • 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
  • 用一组物理位置任意的存储单元来存放线性表的数据元素(这组存储单元即可以是连续的,也可以是不连续的)
  • 链表中元素的逻辑次序和物理次序不一定相同
  • 在链表的第一个结点之前附设一个头结点(头指针)head指向第一个结点。头结点的数据域可以不存储任何信息
  • 单链表是由表头唯一确定,因此单链表可以用头指针的名字来命名
  • 各结点由两个域组成:①数据域:存储元素数值数据;②指针域:存储直接后继结点的存储位置
  • 顺序表是随机存取;链表是顺序存取

单链表

结点只有一个指针域的链表,称为单链表或线性链表

双链表

结点有两个指针域的链表,称为双链表

循环链表

首尾相接的链表称为循环链表

头指针、头结点、首元结点

头指针:指向链表中第一个结点的指针

首元结点:链表中存储第一个数据元素a1的结点

头结点:在链表的首元结点之前附设的一个结点

线性表的链式表示

如何表示空表

  • 无头结点时,头指针为空时表示空表
  • 有头结点时,头结点的指针域(首元结点)为空时表示空表

在链表中设置头结点的好处

  • 便于首元结点的处理(首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其他位置相同)
  • 便于空表和非空表的统一处理

头结点的数据域中装的是什么

  • 头结点的数据域可以为空,也可存放线性表长度等附加信息,但此结点不能计入链表长度值

单链表的定义和表示

带头结点的单链表

数据域 指针域

data next

typedef struct Lnode{
	ElemType data;//数据域,保存结点的值
	struct Lnode *next;//指针域
}LNode,*LinkList;	//LinkList为指向结构体LNode的指针类型
  • 定义一个单链表L

LinkLIst    L;

  •  定义结点指针

LNode *p

  •  存储学生学号、姓名、成绩的单链表结点类型定义
typedef Struct student{
    char num[8];
    char name[8];
    int score;
    struct student *next;    //指针域
}LNode,*LinkList;

为了统一链表的操作,通常这样定义

typedef Struct{
    char num[8];
    char name[8];
    int score;
}ElemType;

typedef struct Lnode{
	ElemType data;
	struct Lnode *next;
}LNode,*LinkList;	

单链表各种操作的实现

p = L;    //p指向头结点
s = L -> next;        //s指向首元结点
p = p -> next;        //p指向下一结点

单链表的初始化(带头结点的单链表)

Status InitList(LinkList &L){    //构造一个空的单链表
    L = new LNode;        //或L = (LinkList) malloc (sizeof(LNode));
    L -> next = NULL;
    return OK;
}

判断链表是否为空

空表:链表中无元素,称为空链表(头指针和头结点仍然在)

判断头结点指针域是否为空

int ListEmpty(LinkList L){    //若L为空表,则返回1,否则返回0
    if(L -> next)    //非空
        return 0;
    else
        return 1;
}

单链表的销毁(销毁后不存在)

从头指针开始,依次释放所有结点

Status DestoryList_L(LinkList &L){
    LNode *p;
    while(L){            //表L不为空
        p = L;
        L = L -> next;
        delete p;
    }
    return OK;
}

单链表的清空(头指针和头结点仍然在)

链表仍存在,但链表中无元素,成为空链表

依次释放所有结点,并将头结点指针域置为空

Status ClearList(LinkList &L){        //将L重置为空表,最后还是头结点的地址
    LNode *p,*q;
    p = L -> next;
    while(p){                //当p和q指针同时指向空结点时,退出循环
        q = p -> next;
        delete p;
        p = q;
    }
    L -> next = NULL;        //将头结点指针域置为空
    return OK;
}

求链表的表长

int ListLength_L(LinkList L){        //返回L中数据元素的个数
    LNode *p;
    p = L -> next;                    //p指向第一个结点
    i = 0;
    while(p){
        i++;
        p = p -> next;
    }
    return i;
}
    

单链表的取值        O(n)

Status GetElem(LinkList L,int i, ElemType &e){
    p = L -> next;    j = 1;
    while(p && j<i){
        p = p -> next;
        ++j;
    }
    if(!p || j>i)    return ERROR;        //第i个元素不存在
    e = p -> data;
    return OK;
}

平均时间复杂度为O(n)

平均查找长度为(n-1)/ 2

单链表的按值查找        O(n)

根据指定数据获取该数据所在的位置(地址)

LNode *LocateElem_L(LinkList L,ELemType e){
    //在线性表L中查找值为e的数据元素
    //找到,则返回L中值为e的数据元素的地址,查找失败返回NULL
    p = L -> next;
    while(p && p -> data!=e){
        p = p -> next;
    return p;            //找到了就是目的结点,没找到就是空
}

根据指定数据获取该数据的位置序号

int LocateElem_L(LinkList L,ElemType e){
    //返回L中值为e的数据元素的位置序号,查找失败返回0
    p = L -> next; j = 1;
    while(p && p -> data!= e){
        p = p-> next;
        j++;
    }
    if(p)    return j;    //如果找到了,就返回当前位置
    else    return 0;
}

单链表的插入操作        O(n)

在第i个结点前插入值为e的新结点

Status ListInsert_L(LinkList &L,int i,ElemType e){
    p = L; j = 0;
    while(p && j < i-1){            //寻找第i-1个结点,p指向第i-1个结点
        p = p-> next;
        ++j;
    }
    if(!p || j > i-1)    return ERROR;    //i大于表长+1或者小于1,插入位置非法
    s = new LNode;    s -> data = e;
    s -> next = p -> next;
    p -> next = s;
    return OK;
}

单链表的删除操作        O(n)

删除第i个结点

Status ListDelete_L(LinkList &L,int i,ElemType &e){
    p = L; j = 0;
    while(p -> next && j < i-1){
        p = p -> next;    
        ++j;            //寻找第i个结点,并令p指向其前驱
    }
    if(!(p -> next) || j>(i-1))    return ERROR;    //删除位置不合理
    q = p -> next;            //临时保存被删结点的地址以备释放
    p -> next = q -> next;        //改变删除结点前驱结点的指针域
    e = p -> data;                //保存删除结点的数据域
    delete q;                     //释放删除结点的空间
    return OK;
}   
    

单链表的建立

头插法        O(n)

void CreateList_H(LinkList &L, int n){
    L = new LNode;
    L -> next = NULL;        //建立一个带头结点的单链表
    for(i = n;i > 0;--i){
        p = new LNode;    //生成新结点 p = (LNode *)malloc(sizeof(LNode));
        cin >> p -> data;
        p -> next = L -> next;    //插入到表头
        L -> next = p;
    }
}

尾插法        O(n)

void CreateList_R(LinkList &L, int n){
    L = new LNode;
    L -> next = NULL;        //建立一个带头结点的单链表
    for(i = 0;i < n;++i){
        p = new LNode;    //生成新结点,输入元素值
        cin >> p -> data;
        p -> next = NULL;    //p指针指向的新结点为单个结点
        r -> next = p;       //插入到表尾
        r = p;        //r指向新的尾结点
    }
}

循环链表

循环链表的定义

  • 一种头尾相接的链表。其结点是最后一个结点的指针域指向链表的头结点,整个链表的指针域链接成一个环

  • 从循环链表的任意一个结点出发都可以找到链表中的其它结点,使得表处理更加方便灵活

循环链表的操作

  • 对于单循环链表,除链表的合并外,其他的操作和单线性链表基本上一致,仅仅需要在单线性链表操作算法基础上作修改

p != L     //p不等于头指针,说明不是空链表        
p->next != L        //判断是否是表尾结点      

上述操作的时间复杂度为O(1)

尾指针表示的单循环链表

5169793560c445f9a51288aae40787dd.png

 带尾指针循环链表的合并(将Tb合并在Ta之后)

LinkList Connect(LinkList Ta,LinkList Tb){
                                //假设Ta,Tb都是非空的单循环链表
    p = Ta -> next;                     //p存Ta的表头结点
    Ta -> next = Tb -> next -> next;    //Tb表头连结Ta表尾
    delete Tb -> next;                  //释放Tb表头结点
    Tb -> next = p;                     //修改指针,使Tb表的尾指针指向Ta的表头结点
    return    Tb;
}

双向链表

  • 构成链表的每个结点中设立两个指针域:一个指向其直接前驱的指针域piror,一个指向其直接后继的指针域next。这样形成的链表中有两个方向不同的链,故称为双向链表

双向链表的结点及其类型定义

typedef struct DuLNode{
	ElemType data;
	struct DuLNode *piror,*next;
}DulNode,*DuLinkList;
  • 双向循环链表

        ①让头结点的前驱指针指向链表的最后一个结点

        ②让最后一个结点的后继指针指向头结点

  • 双向链表结构的对称性
(p->piror)->next = p = (p->next)->piror;
  • 在双向链表中(求链表长度、链表元素取值),因仅涉及一个方向的指针,故它们的算法与线性链表相同。但在插入、删除时,则需同时修改两个方向上的指针,两者的操作时间复杂度为O(n)

双向链表的插入(先左后右)         O(n)

25e733d03212484fa421dc4609956d0f.png

void ListInsert_DuL(DuLinkList &L,int i,ElemType e){
            //在带头结点的双向循环链表L中第i个位置之前插入元素e
    if(!(p = GetElemP_DuL(L,i)))    return ERROR;    //p指向位置不合理
        s = new DuLNode;    s -> data = e;
        s -> piror = p -> piror;
        p -> piror -> next = s;
        s -> next = p;
        p -> piror = s;
    return OK;
}

双向链表的删除        O(n)

void ListDelete_DuL(DuLink &L,int i,ElemType &e){
                        //删除带头结点的双向循环链表L的第i个元素,并用e返回
    if(!(p = GetElemP_DuL(L,i)))    return ERROR;    
                        //得到的时第i个结点的位置,p指向位置不合理
    e = p -> data;
    p -> piror -> next = p -> next;
    p -> next -> piror = p -> piror;
    free(p);
    return OK;
}

单链表、循环链表、双向链表的比较

4391c90e9f40460c9d9b7612272ee637.png

 顺序表和链表的比较

  • 存储密度:结点数据本身所占的存储量和整个结点结构中所占的存储量之比

        存储密度 = 结点数据本身占用的空间 / 结点占用的空间总量

        一般地,存储密度越大,存储空间的利用率就越高。显然,顺序表的存储密度为1,而链表的存储密度小于1

1abbe83f357a4e0eb369171cff9aca5d.png

线性表的应用

线性表的合并

时间复杂度为O(ListLength(La)*ListLength(Lb))

void union(List &La,List Lb){
    La_Len = ListLength(La);
    Lb_Len = ListLength(Lb);
    for(i = 1;i <= Lb_Len;i++){
        GetElem(Lb,i,e);
        if(!LocateElem(La,e))    ListInsert(&La,++La_Len,e);
    }
}

有序表的合并

用顺序表实现

时间复杂度为        O(ListLength(La) + ListLength(Lb))

空间复杂度为        O(ListLength(La) + ListLength(Lb))

void MergeList_Sq(SqList LA,SqList LB,SqList &LC){
    pa = LA.elem;
    pb = LB.elem;            //指针pa和pb的初值分别指向两个表的第一个元素
    LC.length = LA.length + LB.length;    //新表长度为合并两表的长度之和
    LC.elem = new ElemType[LC.length];    //为合并后的新表分配一个数组空间
    pc = LC.elem;            //指针pc的初值指向表的第一个元素
    pa_last = LA.elem + LA.length -1;     //指针pa_last指向LA表的最后一个元素
    pb_last = LB.elem + LB.length -1;     //指针pb_last指向LB表的最后一个元素
    while(pa <= pa_last && pb <= pb_last){    //两个表都非空
        if(*pa <= *pb)    *pc++ = *pa++;    //依次摘取两个表中值较小的结点
            else    *pc++ = *pb++;        //先赋值,然后指针向后移动
    }
    while(pa <= pa_last)    *pc++ = *pa++;    //LB表已到达表尾,将LA中剩余元素加入LC
    while(pb <= pb_last)    *pc++ = *pb++;    //LA表已到达表尾,将LB中剩余元素加入LC
}

用链表实现

时间复杂度为        O(ListLength(La) + ListLength(Lb))

void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc){
    pa = La -> next;    pb = pb -> next;
    pc = Lc = La;        //用La的头结点作为Lc的头结点
    while(pa && pb){
        if(pa -> data <= pb -> data){
            pc -> next = pa;
            pc = pa;            //pc指针向后移动
            pa = pa -> next;
        }
        else{
            pc -> next = pb;
            pc = pb;
            pb = pb -> next;
        }
        pc -> next = pa? pa:pb;    //插入剩余段
        delete Lb;        //释放Lb的头结点
}

案例分析与实现

一元多项式的运算

主要是用两个线性表,分别存储每一项的每个指数对应的系数

指数相等的系数相加之后,就可以存储在一个新的线性表中

稀疏多项式的运算

313552cc6149437e8af8c85866450fdc.png

 线性表B = ((8,1)(22,7)(-9,8))

顺序存储实现

①存储空间分配不灵活

②运算的空间复杂度高

链式存储实现

结点类型定义

typedef struct PNode{
    float coef;        //系数
    int expn;          //指数
    struct PNode *next;    //指针域
}PNode,*Polynomial;
  • 多项式创建         O(n^2)

        多项式的指数从小到大依次增大

9f3cdc605700478da5225343a051b318.png

void CreatePolyn(Polynomial &P,int n){
                        //输入m项的系数和指数,建立表示多项式的有序链表P
    P = new LNode;
    P -> next = NULL;        //建立一个带头结点的单链表
    for(i = 1;i < n;++i){    //依次输入n个非零项
        s = new PNode;        //生成新结点
        cin>>s->coef>>s->expn;        //输入系数和指数
        pre = P;                //pre用于保存q的前驱,初值为头结点
        q = P -> next;          //q初始化,指向首元结点
        while(q && q->expn < s->expn){    //找到第一个大于输入项指数的项*q
                pre = q;    q=q->next;
        }
        s -> next = q;            //将插入项s插入到q和其前驱结点pre之间
        pre -> next = s;
    }
}
  • 多项式相加         时间复杂度为O(m+n)        空间复杂度为O(1)

ed29fea303af412091ed73b20c93c3ff.png

void AddPolyn(Polynomial &Pa,Polynomial &Pb){
    p1 = Pa -> next;
    p2 = Pb -> next;            //p1和p2初值分别指向Pa和Pb的首元结点
    p3 = Pa;                    //p3指向和多项式的当前结点,初值为Pa
    while(p1 && p2){    //二者非空
        if(p1 -> expn == p2 -> expn){    //指数相等
            sum = p1 -> coef + p2 -> coef;        //sum保存两项的系数和
            if(sum != 0){
                p1 -> coef = sum;        //修改Pa当前结点的系数值为两项系数的和
                p3 -> next = p1;    p3 = p1;    //将修改后的Pa当前结点链在p3之后
                p1 = p1 -> next;         //p1指向后一项
                r = p2;    p2 = p2 -> next;    delete r;    //删除Pb当前结点,p2指向后一项
            }
            else{
                r = p1;    p1 = p1 -> next;    delete r;    //删除Pa当前结点,p1指向后一项
                r = p2;    p2 = p2 -> next;    delete r;    //删除Pb当前结点,p2指向后一项
            }
        }
        else if(p1 -> expn < p2 -> expn){        //Pa当前结点的指数值小
            p3 -> next = p1;            //将p1链在p3之后
            p3 = p1;                    //p3指向p1
            p1 = p1 -> next;            //p1指向后一项
        }else{
            p3 -> next = p2;            //将p2链在p3之后
            p3 = p2;                    //p3指向p2
            p2 = p2 -> next;            //p2指向后一项
        }
    }
    p3 -> next = p1?p1:p2;                //插入非空多项式的剩余段
    delete Pb;                            //释放Pb的头结点
}

图书信息管理系统

struct Book{
    char id[20];       //ISBN
    char name[50];    //书名
    int price;        //定价
};

typedef struct{        //顺序表
    Book *elem;
    int length;
}SqList;

typedef struct LNode{    //链表
    Book data;
    struct LNode *next;
}LNode,*LinkList;

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/83256.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!