Bootstrap

C++版数据结构-----线性表

程序 = 数据结构 + 算法

Pascal语言之父-----Nicklaus Wirth,凭借一句话获得图灵奖

基本结构

image-20230105171439129

线性表

顺序表

线性表的顺序表示又称为顺序存储结构或顺序映像

顺序存储定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构

线性表的第1个数据元素a1的存储位置,称作线性表的起始位置或基地址

线性顺序存储占用一片连续的存储空间。知道某个元素的存储位置就可以计算其他元素的存储位置

image-20230105172352819

时间复杂度:查找、插入、删除算法的平均时间复杂度为O(n)

空间复杂度:顺序表操作算法的空间复杂度S(n)=O(1)(没有占用辅助空间)

优点:

存储密度大(结点本身所占存储量/结点结构所占存储量)

可以随机存取表中任意元素

缺点

在插入、删除某一元素时,需要移动大量元素

浪费存储空间

属于静态存储形式,数据元素的个数不能自由扩充

代码实现:

#include <iostream>
using namespace std;
#define Maxsize 100
template <class T>
class SqList//顺序表
{
public:
    SqList(T a[],int n);//构造函数
    ~SqList(){};//析构函数
    void ClearList();//将顺序表清空
    void ListInsert(int i,T e);//在顺序表中第i个位置插入新元素e
    T ListDelete(int i);//删除线性表中第i个位置元素并返回
    bool IsEmpty();//判断线性表是否为空
    int LocateElem(T e);//查找与给定值相等的元素若成功返回序号
    int GetLength();//获得线性表的表长
    T GetElem(int i);//返回线性表中第i个元素
    void printfSqList();//遍历线性表
private:
    T elem[Maxsize];//存储的元素
    int Length;//存储的元素的数量
};
template<class T>
SqList<T>::SqList(T a[],int n)//构造一个新的顺序表
{
    if(n>Maxsize) throw"溢出!";
    else
    {
        for(int i = 0;i<n;i++)
        {
            elem[i] = a[i];
        }
        Length = n;
    }

}
template<class T>
void SqList<T>::ClearList()
{
    Length = 0;
}

template<class T>
void SqList<T>::ListInsert(int i,T e)
{
    if(i<1||i>Length) throw"非法参数";
    if(Length == Maxsize) throw"线性表已满";
    for(int j = Length - 1;j>=i-1;j--)
        elem[j+1] = elem[j];
    elem[i-1] = e;
    Length++;
}

template<class T>
T SqList<T>::ListDelete(int i)
{
    T DE;
    if(i<1||i>Length) throw"非法参数";
    DE = elem[i-1];
    for(int j = i;j<=Length;j++)
        elem[j-1] = elem[j];//被删除的元素之后前移
    Length--;
    return DE;
}

template<class T>
bool SqList<T>::IsEmpty()
{
    if(Length==0) return true;
    else false;
}

//template<class T>
//int SqList<T>::LocateElem(T e)
//{
// int i;
// for(i = 0;i<Length;i++)
// {
//  if(elem[i]==e) return i+1;//查找成功
// }
// return false;//查找失败
//}

template<class T>
int SqList<T>::LocateElem(T e)
{
    int i = 0;
    while(i<Length&&elem[i]!=e) i++;
    if(i<Length) return i+1;
    return false;//查找失败
}

template<class T>
int SqList<T>::GetLength()
{
    return Length;
}

template<class T>
T SqList<T>::GetElem(int i)
{
    if(i<1||i>Length) throw"非法参数!";
    else
        return elem[i-1];
}

template<class T>
void SqList<T>::printfSqList()
{
    for(int i = 0;i<Length;i++)
        cout<<elem[i]<<" ";
    cout<<endl;
}
int main(void) {
    int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    SqList<int> S(a, 10);
    S.printfSqList();
    cout<<S.GetElem(1)<<endl;
    cout<<S.GetLength()<<endl;
    S.ListDelete(10);
    S.printfSqList();
    cout<<S.LocateElem(8)<<endl;
    cout<<S.IsEmpty()<<endl;
    S.ListInsert(2,10);
    S.printfSqList();
    return 0;
}

运行结果:

image-20230105173206200

链表

链式存储结构:结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻

线性表的链式表示又称为非顺序映像或链式映像

用一组物理位置任意的存储单元来存放线性表的数据元素

这组存储单元的位置是随机的

链表中元素的逻辑次序和物理次序不一定相同

各结点由两个域组成:

数据域:存储元素数值数据

指针域:存储直接后继结点的存储位置

image-20230105173645114

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tucVifCP-1672927655442)(null)]

设置头结点的好处:

  1. 便于首元结点的处理

​ 首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其他位置一致,无须进行特殊处理

  1. 便于空表和非空表的统一处理

​ 无论链表是否为空,头指针都是指向头结点的非空指针,因此空表和非空表的处理也就统一了

头结点不能计入链表长度值

查找:

​ 线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为O(n)

插入和删除:

​ 线性链表不需要移动元素,只需要修改指针,一般情况下时间复杂度为O(1)

​ 在单链表中进行前插或删除操作,由于要从头查找前驱结点,所耗时间复杂度为O(n)

程序示例

#include <iostream>
using namespace std;
template <class T>
struct Node//结点类
{
    T data;//数据域
    Node<T> *next;//指针域
};
template <class T>
class LinkList//链表类
{
public:
    LinkList();//无参构造函数
    LinkList(T a[],int n);//头插法建立链表
    LinkList(int n,T a[]);//尾插法建立链表
    bool ListEmpty();//判断链表是否为空
    void DestroyList();//销毁单链表
    int ListLength();//求单链表的表长
    T GetElem(int i);//按位查找
    Node<T> *LocateElem(T e);//在线性表中查找值为e的元素
    int LocateElem_V(T e);//按值查找返回序号
    void ListInsert(int i,T e);//在i位置插入数据e
    T ListDelete(int i);//删除第i个结点并返回
    void PrintList();//遍历整个链表
private:
    Node<T> *first;//头指针
};

template <class T>
LinkList<T>::LinkList() {
    first = new Node<T>;
    first->next = NULL;
}

template <class T>
bool LinkList<T>::ListEmpty() {
    if(first->next == NULL) return true;
    else return false;
}

template <class T>
void LinkList<T>::DestroyList() {
    Node<T> *p = first->next,*q;//p用于移动,q用于删除
    while(p)
    {
        q = p;//p的当前地址给q
        p = p->next;//p向后移动一个位置
        delete q;//删除p的前趋结点
    }
    first->next = NULL;
}

template <class T>
int LinkList<T>::ListLength() {
    Node<T> *p;//用于移动的工作指针
    int j = 0;
    p = first->next;//p指向第一个结点
    while(p)
    {
        j++;
        p = p->next;
    }
    return j;
}

template <class T>
T LinkList<T>::GetElem(int i) {
    Node<T> *p = first ->next;//初始化
    Node<T> e;
    int j = 0;
    while(p!=NULL&&j<i)
    {
        p = p->next;
        ++j;
    }
    if(p==NULL||j>i) throw"第i个元素不存在";
    e = p->data;
    return e;
}

template <class T>
Node<T> *LinkList<T>::LocateElem(T e) {
    Node<T> *p = first->next;
    while(p!=NULL&&p->data!=e)
    {
        p = p->next;
    }
    return p;
}

template <class T>
int LinkList<T>::LocateElem_V(T e) {
    Node<T> *p = first->next;
    int j = 1;
    while(p!=NULL&&p->data!=e)
    {
        p = p->next;
        j++;
    }
    if(p!=NULL) return j;
    else return 0;
}

template <class T>
void LinkList<T>::ListInsert(int i, T e) {
    Node<T>* p = first,*s;
    int j = 0;
    while(p&&j<i-1){p = p->next;++j;}//寻找第i-1个结点,p指向i-1结点
    if(!p||j>i-1) throw "插入位置非法";
    else
    {
        s = new Node<T>;
        s->data = e;
        s->next = p->next;//将新插入的结点与第原来i个结点连接起来
        p->next = s;//将第i-1个结点与新结点连接起来
    }

}

template <class T>
T LinkList<T>::ListDelete(int i) {
    Node<T>* p = first,*q;
    T e;
    int j = 0;
    while((p->next!=NULL)&&j<i-1){p = p->next;j++;}//寻找第i个结点,并另p指向其前驱,p实际指向的是i-1,p->next才是i
    if((p->next==NULL)||j>i-1) throw"非法位置!";
    q = p->next;//临时保存被删结点的地址以备释放
    p->next = q->next;//改变删除结点前驱结点的指针域
    e = q->data;
    delete q;
}

template <class T>
LinkList<T>::LinkList(T *a, int n) {
    Node<T> *s;//指向待插入结点
    first = new Node<T>;//创建头结点
    first->next = NULL;
    for(int i = 0;i<n;i++)
    {
        s = new Node<T>;
        s->data = a[i];
        s->next = first->next;//将新结点与原来第一个结点相连接
        first->next = s;//将新插入的结点与头结点相连接
    }
}

template <class T>
LinkList<T>::LinkList(int n, T *a) {
    first = new Node<T>;//创建头结点
    Node<T> *rear = first,*s;
    for(int i = 0;i<n;i++)
    {
        s = new Node<T>;//创建头结点
        s->data = a[i];
        rear->next = s;//连接最后一个结点和新插入的结点
        rear = s;//rear指向最后一个结点
    }
}

template <class T>
void LinkList<T>::PrintList() {
    Node<T>* p = first->next;
    while(p!=NULL)
    {
        cout<<p->data<<" ";
        p = p->next;
    }
    cout<<endl;
}
int main() {
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    LinkList<int> L1(a,10);
    L1.PrintList();
    LinkList<int>L2(10,a);
    L2.PrintList();
    L1.ListDelete(1);
    L1.PrintList();
    L1.ListInsert(2,10);
    //L1.PrintList();
    L1.ListDelete(1);
    L1.PrintList();
    return 0;
}

运行结果:

image-20230105203059159

循环链表

是一种头尾相接的链表(即:表中最后一个结点的指针域指向头结点,整个链表形成一个环)

image-20230105203325495

优点:从表中任一结点触发均可找到表中其他结点

循环链表中没有NULL指针,涉及遍历操作时,其终止条件不再是判断p或p->next是否为空,而是判断它们是否等于头指针

程序:

#include <iostream>
using namespace std;
template<class T>
struct Node{
    T data;
    Node<T> *next;
};
template<class T>
class CirLinkList//带尾指针的循环链表
{
public:
    CirLinkList(){
        rear = new Node<T>;
        rear->next = rear;
    }
private:
    Node<T> *rear;
};
template<class T>
void Connect(CirLinkList<T> *Ta,CirLinkList<T> *Tb)
{
    Node<T> *p = Ta->rear->next;//p存表头Ta结点
    Ta->rear->next = Tb->rear->next->next;//Tb表头连接Ta表尾
    delete Tb->rear->next;//释放Tb表头结点
    Tb->rear->next = p;//修改指针
}
int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}
双向链表

为了克服单链表查找后继结点容易,找前驱结点困难的问题

双向链表:在单链表的每个结点里再增加一个指向其直接前驱的指针域prior,这样链表中就形成了有两个不同方向的链,故称为双向链表

image-20230105215144596

双向循环链表:

image-20230105215213948

程序:

#include <iostream>
using namespace std;
template<class T>
struct Node{
    T data;
    Node<T> *prior;//指向前驱的指针
    Node<T> *next;//指向后继的指针
};
template<class T>
class DoubleLinkList{
public:
    DoubleLinkList(){
        first = new Node<T>;
        first->prior = NULL;
        first->next = NULL;
    }
    void ListInsert(int i, T e)
    {
        Node<T> *p,*s;//p原i位置
        if(p)
        {
            s = new Node<T>;
            s->data = e;
            s->prior = p->prior;
            p->prior->next = s;
            s->next = p;
            p->prior = s;
            
        }
    }
    T ListDelete(int i)
    {
        T e;
        Node<T> *p;
        if(p)
        {
            e = p->data;
            p->prior->next = p->next;
            p->next->prior = p->prior;
            delete p;
        }
        return e;
    }
private:
    Node<T> *first;
};
int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

存储密度:

image-20230105220156801

链式存储结构的优点:

​ 结点空间可以动态申请和释放;

​ 数据元素和逻辑次序靠结点的指针来指示,插入和删除时不需要移动数据元素

链式存储结构的缺点:

​ 存储密度小,每个结点的指针域需额外占用存储空间。每个结点的数据域所占字节不多时,指针域所占存储空间的比重显得很大。

​ 链式存储结构是非随机存储结构。对任一结点的操作都要从头指针依指针链查找到该结点,这增加了算法的复杂度

      e = p->data;
        p->prior->next = p->next;
        p->next->prior = p->prior;
        delete p;
    }
    return e;
}

private:
Node *first;
};
int main() {
std::cout << “Hello, World!” << std::endl;
return 0;
}


存储密度:

[外链图片转存中...(img-rQkabrbg-1672927652818)]

链式存储结构的优点:

​		结点空间可以动态申请和释放;

​		数据元素和逻辑次序靠结点的指针来指示,插入和删除时不需要移动数据元素

链式存储结构的缺点:

​		存储密度小,每个结点的指针域需额外占用存储空间。每个结点的数据域所占字节不多时,指针域所占存储空间的比重显得很大。

​		链式存储结构是非随机存储结构。对任一结点的操作都要从头指针依指针链查找到该结点,这增加了算法的复杂度

;