跳转至

线性表

线性表

Part I Part II Part III Part IV Part V
顺序线性表 单向链表 双向链表 循环链表 三个案例分析
顺序表 链表
存储空间 预先分配,可能会导致空间闲置或溢出 动态分配,不会出现空间闲置或者溢出
存储密度 存储密度为1,逻辑关系等于存储关系,没有额外开销 存储密度小于1,要借助指针域来表示元素之间的逻辑关系
存取元素 随机存取,按位置访问元素的时间复杂度O(1) 顺序存取,访问某位置的元素的时间复杂度为O(n)
插入、删除 插入和删除都要移动大量的元素。平均移动元素约为表的一半。时间复杂度O(n) 不需要移动元素,只需要改变指针位置,继而改变结点之间的链接关系。时间复杂度O(1)
适用情况 1.表长变化不大,或者事先就能确定变化的范围
2.很少进行插入和删除,需要下标访问元素
1.长度变化较大
2.频繁的插入和删除
这不就是vector的使用特点吗 这不就是list的使用特点吗

1. 顺序线性表

线性表的定义

struct SqList
{
    ElemType *elem;//顺序线性表的表头,ElemType元素类型
    int length;//顺序线性表的长度
};

线性表的初始化

bool InitList(SqList &L)
{
    L.elem = new ElemType[MAXSIZE]; //在堆区开辟内存
    if(!L.elem)
    {
        cerr<<"error"<<endl; //输出到标准错误的ostream对象,常用于程序错误信息;
        return false;
    }
    L.length = 0;//设定线性表长度为0,空表
    return 1;
}

线性表的销毁

void DestroyList(SqList &L)
{
    if(L.elem)
    {
        delete L.elem;  //释放存储空间,L是引用,转换为(*L).elem,应用本质是指针常量
                        //在堆区创建的数据,用delete删除
    }
}

线性表的清空

void CLearList(SqList &L)  //初始条件:顺序线性表L已存在。操作结果:将L置为空表
{
    L.length = 0;

}

求表长

int ListLength(SqList& L) {
    return L.length;
}

判断线性表是否为空

bool IsEmpty(const SqList& L) {
    return static_cast<bool>(L.length);  //c++运算符,将一个表达式转换为某种类型,但没有运行时类型检查来保证转换的安全

    if (L.length == 0) {
        return 1;
    }
    else {
        return 0;
    }
}

线性表的取值

bool GetELem(const SqList &L, const size_t i, ElemType &e)  //一种用来记录大小的数据类型
{
    if(i<1 || i>MAXSIZE)
    {
        cerr<<"out of range"<<endl;
        return false;
    }
    e = L.elem[i-1];
    return true;
}

线性表的查找

int LocateList(const SqList &L, const ElemType &e)  
{
    for(int i = 0; i<L.length; ++i)
    {
        if(L.elem[i] == e)
        {
            return i+1; //查找成功,返回其查找元素的第一个下标值,逻辑位序与物理位序相差1
        }
    }
    return 0; //未能找到对应元素,返回0
    //算法时间复杂度:O(n)
}

线性表的插入

bool InsertList(SqList &L, ElemType &e, int i)
{
    //判断线性表长度是否小于最大长度MAXSIZE
    if(L.length == MAXSIZE)
    {
        cerr<<"can not insert!"<<endl;
        return false;
    }
    if(i<0 || i>L.length)
    {
        cerr << "wrong insert position!" << endl;
        return false;
    }
    if(L.length > 0)
    {
        //将位于插入位置之后的元素依次向后挪动一位
        for (int p = L.length - 1; p >= i-1; p--) {
            L.elem[p+1]=L.elem[p];
        }
    }
    //插入元素
    L.elem[i-1] = e;   //插入到第i个位置,实际上是数组中的i-1个位置
    //线性表长度+1
    L.length += 1;
    return true;
    //算法时间复杂度:O(n)
}

线性表的删除

bool EraseList(SqList &L, const int &i)
{
    //异常判断
    if(i<0 || i>L.length)
    {
        cerr << "wrong erase position!" << endl;
        return false;
    }
    if(L.length == 0)
    {
        cerr << "List has no length" << endl;
        return false;
    }
    //将位于删除位置之后的元素依次向前挪动一位
    for (int p = i; p < L.length; p++) {
        L.elem[p - 1] = L.elem[p];  //删除第i个元素,实际上是数组中的第i-1个位置
    }
    //线性表长度-1
    L.length--;
    return true;
    //算法时间复杂度:O(n)
}

2. 单向链表

链表的定义

typedef struct Lnode {
    int data;//结点的数据域
    Lnode* next;//结点的指针域
}Lnode, * LinkList;

链表的初始化

bool InitList(LinkList &L)//插入题外话:LinkList &L等价于 Lnode *&L,Lnode *&L是一个指向指针的引用   简单理解:L就是Lnode*(指针),直接引用理解成取别名,不用去追究它是指针常量
//(Lnode* * const L = &L:L存的是头指针(L)的地址,*L是头指针,即Lnode* *L,L是引用,转换为*L,它是Lnode*类型;new Lnode返回Lnode*) 
{   
    L = new Lnode; //堆区开辟一个头结点, 结点的数据类型为Lnode,用头指针L指向头节点
    L->next = nullptr;  //空表,也就是说头结点的指针指向为空,C++中NULL就是0,nullptr在任何情况下都代表空指针
    return true;
}

头插法创建单向链表

void CreatListHead(LinkList &L, const size_t n)
{
    for (int i = 0; i < n; ++i)
    {
        Lnode *p = new Lnode;
        cin >> p->data;
        p->next = L->next;
        L->next = p; 
    }
}

尾插法创建单向链表

void CreatListTail(LinkList &L, const size_t n)
{
    Lnode *r = L;
    for (int i = 0; i < n; ++i)
    {
        Lnode *p = new Lnode;
        cin >> p->data;
        p->next = r->next;
        r->next = p;
        r = r->next;
    }
}

判断链表是否为空

bool IsEmpty(const LinkList &L)
{
    if(L->next)//非空
    {
        return false;
    }
    else 
    {
        return true;
    }
}

销毁链表

bool DestroyList(LinkList &L)
{
    //判断链表是否为空
    if(IsEmpty(L))
    {
        cerr << "empty List!" << endl;
        return false;
    }
    while (L)//链表还未到达尾端
    {
        auto temp = L->next;//将头指针指向下一个结点
        delete L;
        L = temp;
    }

    //判断链表是否为空
    //if(IsEmpty(L))
    //{
    //    cerr << "empty List!" << endl;
    //    return false;
    //}

    return true;
}

统计链表长度

//常量引用const修饰形参,防止形参改变实参
size_t GetLength(const LinkList &L)
{
    Lnode *p;
    p = L->next;
    size_t cnt = 0;
    while (p)
    {
        ++cnt;
        p = p->next;
    }
    return cnt;
}

int ListLength(const LinkList& L) {
    int i = 0;
    Lnode* p = L->next;
    while (p) {
        i++;
        p = p->next;
    }
    return i;
}

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

取链表中第i个元素的值

bool GetElem(const LinkList &L, const int &i, ElemType &e)
{
    if(i < 0)
    {
        cerr << "out of range" << endl;
        return false;
    }
    Lnode *p = L->next;
    for (int j = 1; j < i + 1; ++j)
    {
        p = p->next;
        if(!p)
        {
            cerr << "out of range" << endl;
            return false;//如果此时p为空,意味着已经到达链表尾端,停止循环
        }
    }
    e = p->data;
    return true;
}


bool GetElem(const LinkList& L, int i, int &e) {
    if (i < 0) {
        cerr << "out of range" << endl;
        return false;
    }
    int j = 1;
    Lnode* p = L->next;
    while (p && j < i) {
        p = p->next;
        j++;
    }
    if (!p || j > i) {
        return false;
    }
    e = p->data;
    return true;
}

按值查找链表

size_t LocateElem(LinkList &L, ElemType &e)
{
    Lnode *p = L->next;
    size_t cnt = 1;
    while (p)
    {
        if (p->data == e)
        {
            return cnt;
        }
        ++cnt;
        p = p->next;
    }
    cerr << "not found" << endl;
    return 0;
}


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


int LocateElem(LinkList L, int e) {
    Lnode* p = L->next;
    int j = 1;
    while (p && p->data != e) {
        p = p->next;
        j++;
    }
    if (p) {
        return j;
    }
    else {
        return 0;
    }
}

在链表中插入元素

bool InsertList(LinkList &L, const int &i, const ElemType &e)
{
    Lnode *p = L;
    int j = 0;
    while(p && j < i-1)
    {
        p = p->next;   //p指向i-1个结点
        ++j;
    }
    //异常判断
    if(!p || i<0)
    {
        cerr << "out of range" << endl;
        return false;
    }
    LinkList insert = new Lnode;
    insert->data = e;
    insert->next = p->next;
    p->next = insert;
    return true;
}
//算法的时间复杂度为O(n)

删除链表的某个结点

bool EraseList(LinkList &L, const int &i)
{
    Lnode *p = L;
    int j = 0;
    while (p->next && j < i - 1)
    {
        p = p->next;   //指向i-1个结点
        ++j;
    }
    if (!(p->next) || i < 0)
    {
        cerr << "out of range" << endl;
        return false;
    }
    Lnode *q = p->next;
    p->next = p->next->next;  //第i个结点的指针域指向第i+1个
    delete q;                 //删除第i个结点
    return true;
}

两个有序链表的合并

void MergeList(LinkList &La, LinkList &Lb, LinkList &Lc)
{
    Lnode *pa = La->next;
    Lnode *pb = Lb->next;
    Lc = La;
    Lnode *pc = Lc;
    while (pa && pb)
    {
        if (pa->data <= pb->data) //尾插法,插入元素
        {
            //pc的指针域指向小元素的地址
            pc->next = pa;
            //移动pc指针,使得pc永远都指向最后链表Lc的最后一个元素
            pc = pc->next;
            //pa的元素使用过后,要向后移动pa
            pa = pa->next;
        }
        else
        {
            //pc的指针域指向小元素的地址
            pc->next = pb;
            //移动pc指针,使得pc永远都指向最后链表Lc的最后一个元素
            pc = pc->next;
            //pb的元素使用过后,要向后移动pa
            pb = pb->next;
        }
    }
    //上面的while循环执行完毕后,较长的链表还会余留一段元素,这段元素的起始地址就是pa(或pb
    pc->next = (pa ? pa : pb);
    //链表合并完毕,释放Lb的头结点
    delete Lb;
}

​ 这个算法的时间复杂度为O(n),但是空间复杂度为O(1) ​ 自我感觉,这个算法的思想十分巧妙。La和Lb是两条有序链表,众所周知链表的元素的逻辑关系是通过指针域实现的。这个算法巧妙的地方在于:不需要在堆区(heap)申请新的内存空间组成合并链表,而就根据原有元素的地址,重新构建一组逻辑关系。总而言之,就是通过改变现有结点指针的指向,构造出一条新的链表

稀疏多项式的相加

也即“合并两个有序链表”的变形

void SPO_II(LinkList &La, LinkList &Lb, LinkList &Lc)
{
    Lnode *pa = La->next;
    Lnode *pb = Lb->next;
    Lc = La;
    Lnode *pc = Lc;
    while(pa && pb)
    {
        if(pa->data.index < pb->data.index)
        {
            pc->next = pa;
            pc = pc->next;
            pa = pa->next;
        }
        else if(pa->data.index > pb->data.index)
        {
            pc->next = pb;
            pc = pc->next;
            pb = pb->next;
        }
        else if(pa->data.index == pb->data.index)
        {
            pa->data.coef += pb->data.coef;
            pc->next = pa;
            pc = pc->next;
            pa = pa->next;
            pb = pb->next;
        }
    }
    pc->next = (pa ? pa : pb);
    delete Lb;
}

循环链表

循环链表的定义

typedef struct CLnode
{
    ElemType data;
    CLnode *next;
}*CircList;

循环链表的初始化

void InitList(CircList &L)
{
    L = new CLnode;
    L->next = L;
}

循环链表的基本操作和单链表基本上相同,唯一不同的是,由于循环链表的最后一个结点的next不再是空指针,而是指向头结点,因此,循环中的结束条件要发生变化

单链表--------------循环链表
while(p)--------->while(p!=L)
while(p->next)--->while(p->next!=L)

循环链表的连接

//Ta、Tb表示尾指针
CircList Connect(CircList Ta, CircList Tb) {
    CLnode* p = Ta->next;         //
    p = Ta->next;                 //p存放表头结点
    Ta->next = Tb->next->next;    //Tb表头连接Ta表尾
    delete Tb->next;              //释放Tb表头结点
    Tb->next = p;                 //修改指针
    return Tb;
}

双向链表

双向链表的定义

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

双向链表的初始化

void InitList(DuLinkList &L)
{
    L = new DuLnode;
    L->prior = nullptr;
    L->next = nullptr;
}

头插法创建双向链表

void CreatListHead(DuLinkList &L, const size_t n)
{
    for (int i = 0; i < n; ++i)
    {
        DuLnode *p = new DuLnode;
        cin >> p->data;
        p->prior = L;
        p->next = L->next;
        L->next = p;
    }
}

尾插法创建双向链表

void CreatListTail(DuLinkList &L, const size_t n)
{
    DuLnode *r = L;
    for (int i = 0; i < n; ++i)
    {
        DuLnode *p = new DuLnode;
        cin >> p->data;
        p->prior = r;
        p->next = r->next;
        r->next = p;
        r = p;
    }
}

在双向链表的第i个位置插入元素

bool ListInsert_DuL(DuLinkList &L, const int i, const ElemType &e)
{
    //移动指针到i处
    DuLnode *p = L->next;
    int j = 1;
    while (p->next && j < i)
    {
        ++j;
        p = p->next;  //指向i个结点
    }
    if (j < i || j < 1) //如果i在链表范围内,上面的while循环的终止条件就是j<i
    {
        cerr << "out of range" << endl;
        return false;
    }
    //在堆区创建要插入的结点
    DuLnode *s = new DuLnode;
    s->data = e;
    //重新建立链接关系
    s->prior = p->prior; //第一步:s的前趋等于p的前趋
    p->prior->next = s;  //第二步,用p的前趋结点的next指向插入元素s,更改了第一条链
    s->next = p;         //第三步:s的后继指向p
    p->prior = s;        //第四步:p的前趋改为指向s,更改了第二条链
    //return
    return true;
}

删除双向链表中的某个元素

bool ListErase_DuL(DuLinkList &L, const int i)
{
    //移动指针到i处
    DuLnode *p = L->next;
    int j = 1;
    while (p->next && j < i)
    {
        ++j;
        p = p->next;
    }
    if (j < i || j < 1) //如果i在链表范围内,上面的while循环的终止条件就是j<i
    {
        cerr << "out of range" << endl;
        return false;
    }
    //改变链接关系
    p->prior->next = p->next;//p的前趋结点的next等于p的后继
    if ((p->next))//如果删除的不是最后一个元素
    {
        p->next->prior = p->prior;
    }
    //释放p
    delete p;
    //结束
    return true;
}

三个案例分析

案例一:一元多项式运算

案例二:稀疏多项式运算

案例三:图书馆管理系统

案例一:一元多项式的合并 -顺序链表版

void PolyOperate(SqList &L1, SqList &L2, SqList &L3)
{
    for (int i = 0; i < L1.length && i < L2.length; ++i)
    {
        L3.elem[i] = L1.elem[i] + L2.elem[i];
        L3.length += 1;
    }
    if (L1.length <= L2.length)
    {
        for (int j = L1.length; j < L2.length; ++j)
        {
            L3.elem[j] = L2.elem[j];
            L3.length += 1;
        }
    }
    else
    {
        for (int j = L2.length; j < L1.length; ++j)
        {
            L3.elem[j] = L1.elem[j];
            L3.length += 1;
        }
    }
}

稀疏多项式的相加

也即“合并两个有序顺序表”的变形

void SQL_I(SqList &L1, SqList &L2, SqList &L3)
{
    ElemType *p1 = L1.elem;
    ElemType *p1_last = L1.elem + L1.length - 1;
    ElemType *p2 = L2.elem;
    ElemType *p2_last = L2.elem + L2.length - 1;
    ElemType *p3 = L3.elem;
    while (p1 <= p1_last && p2 <= p2_last)
    {
        if (p1->index < p2->index)
        {
            p3->index = p1->index;
            p3->coef = p1->coef;
            ++p1;
            ++p3;
            ++L3.length;
        }
        else if (p1->index > p2->index)
        {
            p3->index = p2->index;
            p3->coef = p2->coef;
            ++p2;
            ++p3;
            ++L3.length;
        }
        else if (p1->index == p2->index)
        {
            p3->index = p1->index;
            p3->coef = p1->coef + p2->coef;
            ++p1;
            ++p2;
            ++p3;
            ++L3.length;
        }
    }
    while (p1 <= p1_last)
    {
        p3->index = p1->index;
        p3->coef = p1->coef;
        ++p1;
        ++p3;
        ++L3.length;
    }
    while (p2 <= p2_last)
    {
        p3->index = p2->index;
        p3->coef = p2->coef;
        ++p2;
        ++p3;
        ++L3.length;
    }
}

稀疏多项式的相加

也即“合并两个有序链表”的变形

void SPO_II(LinkList &La, LinkList &Lb, LinkList &Lc)
{
    Lnode *pa = La->next;
    Lnode *pb = Lb->next;
    Lc = La;
    Lnode *pc = Lc;
    while(pa && pb)
    {
        if(pa->data.index < pb->data.index)
        {
            pc->next = pa;
            pc = pc->next;
            pa = pa->next;
        }
        else if(pa->data.index > pb->data.index)
        {
            pc->next = pb;
            pc = pc->next;
            pb = pb->next;
        }
        else if(pa->data.index == pb->data.index)
        {
            pa->data.coef += pb->data.coef;
            pc->next = pa;
            pc = pc->next;
            pa = pa->next;
            pb = pb->next;
        }
    }
    pc->next = (pa ? pa : pb);
    delete Lb;
}

图书管理系统 - 单链表实现

结构体定义

typedef struct Book
{
    string isbn;
    string name;
    float price;
} ElemType;
struct Lnode
{
    ElemType data;
    Lnode *next;
} *LinkList;

其他操作,例如对图书的添加、删除、查找等操作,和单链表基本上一样的,这里就不赘述了。不过,受到《C++ Primer》的启发,我们可以添加两个这样的函数,简化程序:

//使用read函数向ElemType的对象写入数据
istream &read(istream &in, ElemType &rhs)
{
    in>>rhs.isbn;
    in>>rhs.name;
    in>>rhs.price;
    return in;
}
//使用print函数打印ElemType对象
ostream &print(ostream &out, ElemType &rhs)
{
    out<<rhs.isbn<<" "
        <<rhs.name<<" "
        <<rhs.price<<endl;
    return out;
}

如何使用这两个函数?

//读
read(cin, L->data);
//写
print(cout, L->data);

本篇完~