数据结构(线性表Linear List)

  • Post author:
  • Post category:其他



类c语言的操作补充


顺序表类型定义:

typedef struct{
    ElemType data[];    //ElemType可换成任意数据类型:char,int,struct(要先定义结构类型)等
    //数组名data[]表示首地址,也可以将其定义为指针变量 ElemType *data;道理类似
    int length;
}SqList;


数组静态分配

#define MaxSize 100;
typedef struct{
    ElemType data[MaxSize];    //线性表最大长度不可变
    int length;
}SqList;


数组动态分配

//c语言
typedef struct{
    ElemType *data;
    int length;
}SqList;
    //使用动态内存分配函数
SqList L;
L.data = (ElemType *)malloc(sizeof(ElemType)*MaxSize);
//(ElemType *)强制类型转换:因为最后的分配好的结果是一个地址,所以要存入指针类型中
//sizeof(ElemType)*MaxSize    ElemType所占字节数乘以最大表长,得到需要开辟的空间大小
//malloc((int)m),开辟长度为m字节长的地址,返回首地址
//sizeof(x)计算x的长度
//free(p)释放指针p所指变量的空间,即彻底删除一个变量



//c++动态内存分配
    //new 类型名T(初值列表)
    //new 类型名T[n]
    //功能:申请用于存放T类型对象的内存空间,并依列表初值进行赋值
    //结果:成功——>T类型指针,指向新分配的内存(得到的是一个地址,必须赋值给指针变量)
          //失败——>NULL
    //new 类型名T(初值列表) 分配这种类型的一个大小的内存空间,并以括号中的值初始化这个变量
    //new 类型名T[n]分配这种类型的n个大小的内存空间,并默认初始化这些变量
    int *p = new int(10);

    //delete 指针p
    //功能:释放指针p指向的内存,p必须是new操作的返回值


2.1线性表的定义和特点


定义:由n(n>=0)个

数据特性相同

的元素构成的

有限序列

称为线性表


元素的个数n定义为线性表的长度,n=0时为空表。


特点:1)同一线性表中元素有相同的特点,属于同一数据对象,相邻数据元素存在偶序关系


2)存在唯一一个被称为

线性起点(起始节点)

的数据元素


3)存在唯一一个被称为

线性终点(终端结点)

的数据元素


4)内部结点只有一个

直接前趋

和一个

直接后继





5)线性表中


元素位序是从1开始的



2.2线性表的顺序表示和实现



2.2.1线性表的顺序表示



线性表的顺序表示指用


一组地址连续的存储单元依次存储线性表的数据元素


,也称作顺序存储结构或顺序映像,即顺序表。



特点:1)逻辑上相邻的数据元素,物理次序也是相同的



2)在访问线性表时,可以快速计算出任何一个元素地址,因此可粗略认为访问每个元素地址所花时间相同



3)其顺序存储结构是一种


随机存取


的存储结构



一般来说,线性表第i个数据元素ai的存储位置为



LOC(ai)= LOC(a1) + (i-1)*l



LOC(a1)为线性表的


首地址(基地址)


,l为该类型数据所占单个空间长度



note:

1 2 void 3 3(length)


该表元素地址不连续,存在空的存储单元。因此

不是一个线性表顺序存储结构


顺序表存储结构模板:

#define MAXSIZE 100    //定义表的最大容量
typedef struct         //定义顺序表结构体
{
    ElemType *elem;    //存储空间的基地址(*elem<==>&elem[0])
    int length;        //表长(并非表的所占空间,而是其中元素个数)
                       //length只是表示,最后要依据此对表进行操作
}SqList;               //类型名SqList


注意:1)线性表元素位序是从1开始的,而数组下标是从0开始的


2)数组空间通过初始化分配得到,初始化完成后,数组指针*elem指向顺序表的基地址,数组空间大小为MAXSIZE,表长则为length。


3)上述类型定义后,可以定义变量SqList L;借助L.elem[i]访问第i+1个元素



顺序表的示意图


数组元素

L.elem[0]

L.elem[1]

L.elem[2]

L.elem[3]

L.elem[4]

L.elem[5]


void


数据元素位序

1

2

3

4

5

6


void


线性表L的内容

a1

a2

a3

a4

a5

a6


6


线性表成员

L.elem


void


void

void

void

void


L.length


2.2.2线性表中基本操作的实现


预定义常量和类型说明

//函数结果状态代码
#define OK 1
#define ERROR 0
#define OVERFLOW -2
#define TRUE 1
#define FALSE 0
#define INFEASIBLE -1(infeasible不可实行的)
//类型定义
typedef int status;


算法


2.1初始化:构造一个空的顺序表


算法步骤:1)为顺序表L动态分配一个预定义大小的数组空间,使elem指向其首地址


2)定义表长为0


算法描述:

status LnitList(SqList &L)    //初始化一个表(使用引用类型是要最终得到该表)
{
    L.elem = new ElemType[MAXSIZE];    //为表动态分配空间
    if(!L.elem) exit(OVERFLOW);        //如果分配失败,L.elem为空值,则退出并返回失败
    L.length = 0;                      //设置表长为0
    return OK;                         //返回ok
}


2.2清空线性表


算法步骤:令表长为0


算法描述:

void ClearList(SqList &L)
{
    L.length = 0;    //将线性表长度设置为0
}


2.3销毁线性表


算法步骤:释放已有的线性表的空间


算法描述

void DestroyList(SqList &L)
{
    if(L.elem) delete L.elem;//在表存在的前提下,销毁表所占内存
}


2.4判断是否为空表,求线性表长


算法步骤:对成员length判断,如果为0则是空表,否则为表长


算法描述

int emptyOrLength(SqList &L)
{
    if(L.length==0) return 0;
    else return L.length;
}


2.5顺序表取值


算法步骤:1)首先判断取值i是否合理,如不合理返回ERROR


2)若值合理,则将第i个值赋给参数e,并返回出来


算法描述

status GetElem(SqList L,int i,ElemType &e)//e要通过引用得出
{
    if(i<1||i>L.length) return ERROR;    //判断取值是否合理
    e = L.elem[i-1];                     //赋值
    return OK;
}


算法分析:显然该算法的时间复杂度为O(1)。


2.6顺序表的查找(此处采用顺序查找的算法)


算法步骤:1)从第一个元素起,依次与参数e比较,如果找到第一个与e相同的元素L.elem[i],则返回序号i+1


2)若查遍整个顺序表都没找到,则查找失败,返回0


算法描述

int LocatElem(SqList L,ElemType e)
{
    for(int i = 0;i < L.length;i++)
    {
        if(L.elem[i]==e)    //如果找到,则返回顺序表元素序号
        return i+1;
    }
    return 0;              //没找到则返回0
}        


算法分析:


在查找时,为确定元素在顺序表中的位置,需和给定值进行比较的元素个数的期望值就称为查找算法在查找成功时的

平均查找长度(ASL)


假设pi是查找到第i个元素的概率,Ci为已经进行比较过的元素个数,则


ASL=\sum_{i = 1}^{n}piCi


假设查找到每个元素的概率相等,且一般情况下,查找过程Ci = i;




ASL=1/n\sum_{i=1}^{n}i=(n+1)/2


由此可见,该查找算法的时间复杂度为O(n)


2.7顺序表的插入


算法步骤:1)判断插入位置是否合法(1=<i<=n+1),不合法则返回ERROR


2)判断线性表是否已满,如满返回ERROR


3)将第n个元素到第i个元素依次向后移动一位(如插入位置为n+1则无需移动)


4)将要插入的元素e放到第i个位置上


5)表长+1


算法描述

status InsertList(SqList &L,int i,ElemType e)
{
    if(i<1||i>L.length+1) return ERROR;    //插入位置是否不合法
    if(L.length == MAXSIZE) return ERROR;  //表是否已满
    for(int j = L.length-1;j >= i-1;j--)   //移动元素
    {
        L.elem[j+1]=L.elem[j];
    }
    L.elem[i-1] = e;                       //插入元素
    ++L.length;                            //表长+1
    return OK;
}


算法分析:


假定pi为第i个元素之前插入一个元素的概率,Eins表示在长度为n的线性表中插入一个元素所需移动元素次数的期望值,则


E_{ins} = \sum_{i=1}^{n+1}pi(n+1-i)
有n+1个可以插入的位置


假设每个位置插入的概率相同,为1/(n+1),则


E_{ins} = (1/(n+1))\sum_{i=1}^{n+1}(n+1-i)=n/2


显然算法时间复杂度为O(n)


2.8顺序表的删除


算法步骤:1)判断删除位置是否合法(1=<i<=n),不合法则返回ERROR


2)将第i+1个元素到第n个元素依次向前移动一位


3)表长-1


算法描述

status ListDelete(SqList &L,int i)
{
    if(i<1||i>L.length) return ERROR    //判断i值是否合法
    for(int j = i;j < L.length-1;j++)   //移动元素
    {
        L.elem[j-1] = L.elem[j];
    }
    --L.length;                         //表长-1
    return OK;
}


算法分析:


假定pi为删除第i个元素的概率,Edei为在长度为n的线性表中删除第i个元素所需移动元素个数的期望值,则


E_{dei}=\sum_{i=1}^{n}pi(n-i)


假定删除每个元素的概率相同,即1/n,则


E_{dei}=(1/n)\sum_{i=1}^{n}(n-i)=(n-1)/2


显然算法时间复杂度为O(n)


2.2.3顺序表的优缺点


优点:存储密度大,可以随机存储表中的任一元素


缺点:1)做插入和删除操作时,需要移动大量元素


2)存储空间浪费


3)静态存储形式,数据元素最大个数不能自由扩充


2.3 线性表的链式存储结构


2.3.1单链表的定义和表示


单链表:用一组任意的存储单元存储线性表的数据元素,其逻辑次序和物理次序不一定要相同。每个单元称为一个

结点

。,它包括两个域:其中存储数据元素的称为

数据域

,存储直接后继位置的称为

指针域

,指针域中存储的信息称为

指针或链

。这两部分信息组成数据元素ai的

存储映像。



又由于此链表只包含一个指针域,所以又称为单链表或线性链表。


这种存储结构称为非顺序映像或链式映像。但是是

顺序存储结构


除单链表之外,还包括双向链表:结点有两个指针域

循环链表:首尾相接的链表。。。


单链表存储结构示意


L是

头指针

,是

指向链表第一个元素

的指针(有头结点则指向头结点,无则指向首元结点)


L指向的是

头结点

,是

在首元结点前附设的一个结点

,数据域可以为空,指针域指向首元结点


a1所在结点是

首元结点



存储第一个数据元素

a1


若单链表为空表,则头结点指针域为NULL。且仅包含头指针和头结点


单链表是由头指针唯一确定的,因此可以用头指针的名字来命名


单链表的语言描述:

typedef struct Lnode
{
    ElemType data;         //结点数据域data
    struct Lnode *next;    //存储后继节点位置的指针域next(等同于LinkList next)
}Lnode,*LinkList;          //对该结构体指针类型起了两个名字,本质上等价。通常习惯LinkList定义单 
                             链表,而Lnode定义结点中的指针变量。若定义Lnode *p则表示结点中的指                    
                             针变量,定义LinkList L表示表L

//更常用的定义方法
struct ElemType            //单独定义一个结构体变量存储更多信息
{
    ElemType1 data1;
    ElemType2 data2;
    ...
};

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


note:利用指针间接访问结构体成员,不用.点运算符而是->来访问


链表增加头结点


1.增肌头结点后,首元结点的地址保存在头结点中,则无需对首元结点进行特殊处理


2.不设头结点,若为空表L为NULL,设头结点,若为空表,头结点的指针域为空


3.头结点中数据域内容不是数据元素,统计表长不能统计进去


2.3.2单链表基本操作的实现


算法2.9单链表的初始化


构造一个空表


算法步骤:1.生成新结点作为头结点,头指针L指向头结点


2.头结点的指针域置空


算法描述:

status LintList(LinkList &L)    //构建一个新的单链表
{                            
    L = new Lnode;              //创建一个头结点
    L->next=NULL;               //头结点的指针域置空
    return OK
}


算法2.10判断是否为空表


算法步骤:判断是否为空表


算法描述:

int EmptyList(LinkList L)
{
    if(L->next) return 0;    //判断头结点的指针域是否为空
    else return 1;
}


算法2.11销毁单链表


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


算法步骤:1.先将L后移,在释放前一个结点


2.重复直到所有结点(包括头结点)都被清空


算法描述:

status destroyList(LinkeList &L)
{
    Lnode *p;
    while(L)        //保证删除所有结点(头指针所指结点不为空)
    {
        p = L;      //将p指向L所指的结点
        L=L->next;  //L向后移动一个结点
        delete p;   //删除前一个结点
    }
    return OK;
}


算法2.12清空链表


算法步骤:依次释放所有结点,头结点指针域置空


算法描述:

status ClearList(LinkList &L)
{
    Lnode *p,*q;    //两个指针交替移动删除结点
    p = L->next;    //p首先指向头结点
    while(p)        //保证删除全部数据元素
    {
        q = p->next;//q指向p所指的下一个结点
        delete p;   //释放p所指空间
        p = q;      //再将q赋值给p,重复删除
    }
    L->next=NULL;   //将头结点指针域置空
    return OK;
}
        
         


算法2.13求链表表长


算法步骤:1.首先判断是否为空表


2.利用计数器依次求表长


算法描述:

int GetLength(LinkList L)
{
    Lnode *p = L->next;    //设置一个指针指向头结点
    if(L->next) return 0;  //保证链表不是空表
    else 
        int j = 0;         //设置计数器,从头结点(0)开始计数
    while(p)
    {
        p = p->next;       //p每次向后移动一个结点
        j++;               //计数器+1
    }
    return j;
}



重要操作:p = L;//令p指向头结点



p = p->next;//p沿着链表向后推移一个结点



s = L->next;//s指向首元结点


算法2.14取值


从首元结点出发,依次访问直到找到第i个位置


算法步骤:1.用指针p指向首元结点,设置计数器j从1开始计数


2.若指针p不为空且没找到第i个元素,则执行:


*1.指针p沿next域向后移动


*2.计数器j+1


3.退出循环时,若p为空或j>i,说明给定序号i不合法,返回ERROR。若j = i,p所指结点就是要找的结点,用参数e保存当前数据域,返回OK


算法描述:

status GetElem(LinkList L,int i,ElemType &e)
{
    Lnode *p = L->next;        //令p指向首元结点
    int j = 1;                 //设置计数器
    while(p&&j<i)              //p不为空且j<i
    {    
        p = p->next;           //p依次向后推移
        j++;                   计数器+1
    }
    if(!p||j>i) return ERROR;  //遍历完仍然没有找到或i<1 即给定的i不合法
    else
        e = p->data;           //保存找到的结点的数据域
    return OK;
}


算法分析:语句频度与i的位置有关,与顺序表类型,算法时间复杂度为O(n)


算法2.15查找


从首元结点出发,依次将data域的值与给定参数e比较,返回结果


算法步骤:1.将指针p指向首元结点


2.若p不为空且数据域值不等于e,则继续向后推进


3.若查找到则返回结点地址或位置,未查找到则返回ERROR。


算法描述:

//返回地址
Lnode* LocatElem(LinkList L,ElemType e)
{
    Lnode *p = L->next;        //p指向首元结点
    while(p&&p->data!=e)       //p不为空且p所指结点的数据域不为0
    {
        p = p->next;           //p向后推移
    }
    if(!p) return NULL;        //没找到
    else 
        return p;
}
//返回位置
int LocateElem(LinkList L,ElemType e)
{
    Lnode *p = L->next;
    int j = 1;                //设置计数器
    while(p&&p->data!=e)
    {
        p = p->next;
        j++;                  //每推移一次,计数器+1
    }
    if(!p) return 0;
    else
        return j;
}


算法分析:类似算法2.14,与e相关,算法时间复杂度为O(n)


算法2.16插入


算法步骤:1.查找结点a(i-1)并由指针p指向该结点


2.生成一个新节点*s


3.将新节点*s的数据域置为e


4.将*s的指针域指向ai


5.将a(i-1)的指针域指向*s


算法描述:

status insertElem(LinkList &L,int i,ElemType e)
{
    Lnode *p = L->next;            
    int j = 0;                     
    while(p&&j<i-1)                
    {
        p = p->next;
        j++;
    }
    if(!p||j>i-1) return ERROR;        //查找算法
    else
    {
        s = new Lnode;                //生成一个新结点s        
        s->data = e;                  //将s的数据域置为e
        s->next = p->next;            //s指向ai
        p->next = s;                  //a(i-1)指向s
    }
    return OK;
}
        
    



重要步骤:1.s->next = p->next;



2.p->next = s;


算法分析:在i处插入结点的算法时间复杂度为O(1),但查找到第i-1个结点为O(n)


算法2.17删除结点


算法步骤:1.查找a(i-1)处结点并令指针p指向该结点


2.临时保存待删除结点ai的地址在q中,数据在e中,以备删除


3.令a(i-1)的指针域指向第a(i+1)个结点


4.释放ai


算法描述:

status deleteElem(LinkList &L,int i,ElemType &e)
{
    Lnode *p = L->next;    
    int j = 0;
    while(p->next&&j<i-1)
    {
        p = p->next;
        j++;
    }                                   
    if(!(p->next)||j>i-1) return ERROR;    //查找算法
    Lnode *q = p->next;                    //q指向第i个元素
    e = q->data;                           //保存第i个元素的数据域(即p->next->data)
    p->next = q->next;                     //将第a(i-1)个结点直接指向第a(i+1)个结点
    delete p;            //释放第i个结点     (即p->next=p->next->next)
    return OK;    
}
   
    



note:删除算法能够删除第1-n个元素,有n个删除点。而插入算法能够在1到n+1个位置插入,有n+1个插入点,故循环判定条件不同



算法分析:删除算法的时间复杂度为O(1),而查找为O(n);



算法2.18创建单链表



2.18.1前插法



前插法是将新结点诸葛插入链表的头结点之后,每次申请一个新结点。



算法步骤:1.创建一个只有头结点的空链表



2.要插入n个数据元素,则循环执行以下操作n次



*1.生成一个新结点p


*2.输入数据作为新结点的数据


*3.插入到头结点之后


算法描述:

status CreateList_H(LinkList &L,int n)
{
    Lnode *L = new Lnode;        
    L->next=NULL;                //空链表创建算法
    for(int i = 0;i < n;i++)     //循环n次
    {
        Lnode *p = new Lnode;    //生成一个新的结点
        cin >> p->data;          //输入数据域
        p->next = L->next;       //新结点数据域置空
        L->next = p;             //头结点连接新结点
    }
    return OK;
}
    


算法分析:显然时间复杂度为O(n),并且数据需要逆向输入。




2.18.2后插法


将新结点逐个连接到链表尾部来创建链表,需要额外设置一个尾指针


算法步骤:1.创建一个空链表


2.设置一个尾指针r,指向头结点


3.根据输入n个数据,执行n次以下操作


*1.生成一个新结点


*2.输入数据作为新结点的数据域


*3.新结点插入当前的尾结点后


*4.尾指针指向尾结点


算法描述:

status CreatList_R(LinkList &L,int n)
{   
    Lnode *L = new Lnode;
    L->next = NULL;                   //创建空表算法
    Lnode *r = L;                     //设置尾指针
    for(int i = 0;i < n;i++)          //循环n次
    {
        Lnode *p = new Lnode;         //生成新结点
        cin >> p->data;               //输入数据域    
        p->next=NULL;                 //新结点插入尾部,所以next域置空
        r->next = p;                  //新结点与原来尾结点连接
        r = p;                        //尾指针指向新的尾结点
    }
    return OK;
}


算法分析:显然时间复杂度为O(n),数据元素的输入顺序与逻辑顺序相同


2.3.3循环链表(表的操作常在首尾上进行)


特点:表中最后一个结点的指针域指向头结点,整个链表形成一个环。


从表中任一结点出发均能找到其他结点




循环单链表和单链表的操作基本一致,但循环结束的判别条件为p!=L或p->next!=L


算法2.19,循环链表的合并


算法步骤:1.在循环链表分别设置头指针和尾指针


2.将第一个表的尾指针指向第二个表的第一个结点


3.将第二个表的尾指针指向第一个表的头结点


4.释放第二个表的头结点


算法描述


LinkList connect(LinkList a,LinkList b)
{                       //A,B都指向表a,b的尾结点
    p=B->next->next;    //p保存b的首元结点
    B->next=A->next;    //b的尾结点指向a的头结点
    A->next=p;          //a的尾结点指向b的头结点
    return a;
}

LinkList connect(LinkList a,LinkList b)
{
    p=A->next;            //将a的头结点保存在p
    A->next=B->next->next;//a的尾结点指向b的首元结点
    B->next=p;            //b的尾结点指向a的头结点
    return a;
}
 


算法分析:显然时间复杂度为O(1);


2.3.4双向链表


单向链表查找直接后继便利,但查找直接前趋不方便,可利用双向链表


特点:有两个指针域,一个指向直接前趋,一个指向直接后继。头结点的prior域指向尾结点,尾结点的next域指向头结点

typedef struct
{
    ElemType data;
    struct DuLnode *prior;
    struct DuLnode *next;
}DuLnode,*DuLinkList;


双向链表同样具有循环表


结构为





双向链表的对称性:p->next->prior=p=p->prior->next


在仅需进行一个方向的操作时,双向链表与单链表相同。但在进行插入和删除等操作时,双向链表与单向链表不同。其中,双向链表的插入需要修改四个指针,而删除需要修改两个指针




算法2.20双向链表的插入


算法步骤:与单向链表的插入操作类似,但修改指针步骤较多


算法描述:

status ListLnsert_Du(DuLinkList &L,int i,ElemType e)//在第i个元素之前插入
{
    if(!p=GetElem(L,i)) return ERROR;//查找算法
    DuLnode *s = new DuLnode;
    s->data = e;
    s->prior = p->prior;    //s指向a(i-1)个结点
    p->prior->next=s;       //a(i-1)个结点指向s
    s->next = p;            //s指向ai结点
    p->prior = s;           //ai结点指向s
    return OK;
}


算法分析:查找算法O(n),插入算法O(1)


算法2.21双向链表的删除


算法步骤:和单链表删除类似,指针修改较多


算法描述:

status DeleteList_Du(LinkList &L,int i)
{
    if(!p=GetElem(L,i)) return ERROR;    //查找算法
    p->prior->next = p->next;            //p的前一个结点next域指向p的后一个结点
    p->next->prior = p->prior;           //p的后一个结点prior域指向前一个结点
    delete p;
    return OK;


算法分析:查找算法O(n),删除算法O(1);


小结:


2.3.6顺序表和链表的比较




2.3.7线性表的应用


2.3.7.1线性表的合并




算法2.21


线性表的合并:例如求出两个集合的并集




算法步骤:1.分别获取LA表长m和LB表长n


2.从LB中第1个元素开始,执行n次以下操作


*1.去除LB中第i个元素,并将其赋给e


*2.在LA中查找e,如果没有,则将其插入到表尾


算法描述:

 void MergeList(LinkList &LA,LinkList LB)
{
    int m = LinkLength(LA),n = LinkLength(LB);    //得到表a和表b的长度
    for(int i = 0;i < n;i++)                        
    {
        e = GetElem(LB,i);                        //摘取表中第i个元素
        if(!LocateList(LA,e))                     //在a表中查找该元素,如果没有,则在表后插入
            InsertList(LA,m++,e);
    }
}


算法分析:定位b表中元素需要定位n次,每次要在a中查找m次,算法时间复杂度为O(m*n);


2.3.7.2有序表的合并




有序表:数据元素在线性表中依值非递增或非递减有序排列


算法2.22有序表的合并:


有序集合:集合中元素有序排列


算法2.22.1顺序有序表的合并


算法步骤:1.创建一个表长为m+n的空表


2.初始化指针pc指向表c的第一个元素


3.初始化指针pa指向表a的第一个元素,pb指向表b的第一个元素


4.当指针pa和pb均未到达表尾时,一次比较pa和pb中的值,取较小的插入到pc后


5.若表a先到达表尾,则将表b剩余元素依次插入在表c后


6.若表b先到达表尾,则将表a剩余元素依次插入在表c后


算法描述:

void MergeList_sq(sqList LA,sqList LB,sqList &LC)
{
    LC.length = LA.length + LB.length;    //求出LC表表长
    LC.elem = new ElemType[LC.length];    //为LC表分配空间
    pc = LC.elem;                         //pc指向表LC的第一个元素
    pa = LC.elem;                         //pa指向表LA的第一个元素
    pb = LB.elem;                         //pb指向表LB的第一个元素
    pa_last = pa + LA.length - 1;         //pa_last指向表最后一个元素,用于检查是否到达表尾
    pb_last = pb + LB.length - 1;         //pa_last同上
    while(pa<=pa_last&&pb<=pb_last)       //pa和pb均未到达表尾
    {
        if(*pa<*pb)                        
        *pc++=*pa++;
        else
        *pc++=*pb++;
    }                                     //将pa和pb较小的填入LC中,然后向后移动 
    while(pa<=pa_last)
    *pc++=*pa++;
    while(pb<=pb_last)
    *pc++=*pb++;                          //将未到达表尾的表依次填入剩余空间中
}


算法分析:算法最坏时间复杂度为O(m+n),即LA,LB元素全部填入LC表中,且需要开辟m+n长度的空间,算法空间复杂度为O(m+n)。


算法2.22.2链表有序表的合并


算法步骤:1.pa和pb初始化,分别指向表LA和表LB的首元结点


2.LC头结点初始化为LA的头结点


3.pc初始化,指向LC的头结点


4.一次比较pa和pb所指结点,摘取数据域较小的结点插入到LC后


5.将非空表剩余部分插入到pc所指结点后


6.释放表LB的头结点

void MergeList_L(LinkList &LA,LinkList &LB,LinkList &LC)
{
    pa = LA->next;                //pa指向LA首元结点
    pb = LB->next;                //pb指向LB首元结点
    LC = LA;                      //LC头结点初始化为LA的头结点
    LinkList pc = LC->next;       //pc指向LC的首元结点
    while(pa&&pb)                 //pa和pb均未到达表尾
    {
        if(pa->data<pb->data)
        {
           pc->next = pa;         //把pa连接在pc所指结点后
           pc = pa;               //pc指向pa所指结点(即新链表的尾结点)
           pa = pa->next;         //pa向后移动
        }
        else
        {
            pc->next = pb;
            pc = pb;
            pb = pb->next;
        }                         //同上
    pc->next = pa?pa:pb;          //一表到达尾结点后,pc后连接的是否是表LA呢?如果是,则连接
    delete LB;                    //表LA,不是,则连接表LB;
}            //释放LB表的头结点
         
            


算法分析:时间复杂度仍为O(m+n),但空间复杂度为O(1)。


2.3.8案例实现与分析


案例2.1一元多项式的运算


算法:由于指数相邻,直接使用数组储存多项式,下标作为指数,数据元素存储系数即可,多项式的运算直接对应项运算即可,不做演示。


案例2.2稀疏多项式的运算


利用单链表的操作实现稀疏多项式的运算


多项式的数据结构定义:

typedef struct
{
    double coef;            //系数
    int expn;               //指数
    struct pNode *next;     //指针域
}pNode,*Polynomial;


算法2.23多项式的创建


算法步骤:1.创建一个置于头结点的空链表


2.根据要输入n项数据,执行n次以下操作


*1.生成一个新结点s


*2.输入当前项的系数和指数并赋值给s的数据域


*3.设置一前驱指针pre,指向待找到第一个大于输入项的结点的前驱,初值为头结点


*4.指针q初始化,指向首元结点


*5.循链依次比较当前项和输入项指数,直到找到第一个比输入项指数大的结点


*6.输入项结点*s插入到*q之前


算法描述:

void CreatePolyn(Polynomial &P,int n)
{    
    P = new PNode;                    //创建多项式头结点
    p->next = NULL;                   //置空头结点
    for(int i = 0;i < n;i++)          //循环输入n次
    {
        PNode *s = new PNode;         //生成新结点
        cin >> s->coef >> s->expn;    //输入指数和系数
        LNode *pre = P;               //设置q的前驱指针,初始化为头结点
        LNode *q = P->next;           //q指向要找的第一个指数比输入项指数大的项
        while(q&&q->expn<s->expn)     //不满足条件时依次++
        {
            pre = q;
            q = q->next;
        }
        s->next = q;                
        pre->next = s;                //将新结点s连接到q之前
}


算法分析:最坏的情况n次每次都要查找n次,则算法时间复杂度为O(n*n);


算法2.24多项式的相加


算法步骤:1.指针p1和p2初始化,分别指向两多项式Pa和Pb的首元结点


2.p3指向和多项式的当前结点,初值为Pa的首元结点


3.当p1和p2均未到达表尾时,循环比较p1和p2的指数域,则有三种情况


*1.*p1和*p2的指数域相同,若二者和不为0,则修改*p1指数域的值;若二者和为0,则删除两个结点


*2.*p1的指数域小于*p2的指数域,则将p1插入到和多项式中


*3.*p1的指数域大于*p2的指数域,则将p2插入到和多项式中


算法描述:

void AddPolyn(Polynomial &Pa,Polynomial &Pb)
{
    LNode *p1 = Pa->next;                //p1指向Pa的首元结点
    LNode *p2 = Pb->next;                //p2指向Pb的首元结点
    LNode *p3 = Pa;                      //p3作为和多项式的指针,初始值为Pa的头结点
    while(p1&&p2)                        //p1p2均未到表尾
    {        
        if(p1->expn==p2->expn)            //情况1:*p1和*p2指数域相同
        {
            int sum = p1->expn + p2->expn;
            if(sum!=0)                    //情况1.1,系数和不为0
            {
                p1->coef = sum;           //修改p1的指数域为系数和
                p3->next = p1;            //把p1接到p3后
                p3 = p1;                  //p3随之移动到和多项式的尾结点
                p1 = p1->next;            //p1后移
                LNode *r = p2;            //保存p2的该结点
                p2 = p2->next;            //p2后移
                delete r;                 //释放该结点
            }
            else                          //情况1.2,系数和为0
            {
                LNode *r1 = p1;
                p1 = p1->next;            //p1后移
                delete r1;                //删除Pa中该结点    
                LNode *r2 = p2;            
                p2 = p2->next;            //p2后移
                delete r2;                //删除Pb中该结点
            }
        else if(p1->expn<p2->expn)        //情况2,*p1指数域小
        {
            p3->next = p1;                //p1连接到p3后
            p3 = p1;                      //p3后移
            p1 = p1->next;                //p后移
        }
        else                              //情况3,*p2的指数域小
        {
            p3->next = p2;
            p3 = p1;
            p1 = p1->next;                //同上
        }
    }
    p3->next = p1?p1:p2;                  //将剩余的非空表连接到p3后
    delete Pb;                            //删除Pb的头结点
}


算法分析:最坏算法时间复杂度为O(m+n),空间复杂度为O(1);


案例2.3图书管理系统



版权声明:本文为jxttutu原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。