理论基础 —— 线性表 —— 单链表

【实现类】

单链表的基本思想就是用指针表示结点之间的逻辑关系,因此要正确的对指针变量、指针、指针所指结点、结点的值进行区分。

设 p 是一个指针变量,则 p 的值是一个指针,若指针 p 指向某个 Node 类型的结点,则该结点用 *p 来表示,并称 *p 为结点变量,有时为了叙述方便,常将 " 指针变量 " 称为 " 指针 ",将 " 指针 p 所指的结点 " 称为 " 结点 p "。

在单链表中,结点 p 由两个域组成,其中,存放数据元素的部分用 p->data 表示,其值是一个数据元素;存放后继结点地址的指针部分用 p->next 表示,其值是一个指针。

template <class T>
struct Node{//结点
    T data;//数据域
    Node *next;//指针域
};

template <class T>
class linkList{
private:
    Node<T> *first;//头指针
public:
    linkList();//无参构造函数
    linkList(T a[],int n);//有参构造函数
    ~linkList();//析构函数
    int getLength();//获取单链表的长度
    T getElement(int i);//获取单链表的第i个元素
    int getPosition(T x);//获取单链表中值为x的元素序号
    void insertElement(int i,T x);//在单链表中第i个位置插入值为x的元素
    T deleteElement(int i);//删除单链表的第i的元素
    void print();//按序号依次输出单链表各元素
};

【构造函数】

1.带头结点的构造函数

1)无参构造函数

带头结点的无参构造函数,只需要生成一个头结点,让头指针指向头结点,并将头结点的指针域置空。

template <class T>
linkList<T>::linkList(){//无参构造函数
    first=new Node<T>;//头指针指向头结点
    first->next=NULL;//头结点的指针域置为空
}

2)头插法的有参构造函数

头插法就是每次将新申请的结点插到头结点的后面,即始终让新结点在第一的位置。

使用头插法建立有参构造函数,首先应建立一个头结点,让头指针指向头结点,同时将头结点的指针域置空(与无参构造函数相同),此后,对于 n 个数组元素:

  • 建立一个结点,为结点的数据域赋值
  • 将新结点的指针域指向头结点的指针域所指向的地址
  • 令头结点的指针域指向新结点

template <class T>
linkList<T>::linkList(T a[],int n){//头插法的有参构造函数
    first=new Node<T>;
    first->next=NULL;

    for(int i=0;i<n;i++){
        Node<T> *s=new Node<T>;//建立一个新结点
        s->data=a[i];//为新结点的数据域赋值
        s->next=first->next;//将新结点的指针域指向头结点的指针域所指向的位置
        first->next=s;//令头结点的指针域指向新结点
    }
}

3)尾插法的有参构造函数

尾插法就是每次将新申请的结点插到终端结点的后面,即始终让新结点在最后的位置。

使用尾插法建立有参构造函数,首先应建立一个头结点,然后再建立一个尾指针,代表单链表中最后一个数据元素的位置。此后,对于 n 个数据元素:

  • 建立一个结点,为结点的数据域赋值
  • 将尾指针指针域指向新建立的结点
  • 更改尾指针地址为新结点地址

最后,当单链表建立完毕时,将尾指针的指针域置空

template <class T>
linkList<T>::linkList(T a[],int n){//尾插法的有参构造函数
    first=new Node<T>;
    Node<T> *r=first;//尾指针初始化
    for(int i=0;i<n;i++){
        Node<T> *s=new Node<T>;//建立一个新结点
        s->data=a[i];为新结点数据域赋值
        r->next=s;//尾指针指针域指向新结点
        r=s;//更改尾指针地址为新结点地址
    }
    r->next=NULL;//单链表建立完毕,将尾指针指针域置空
}

2.不带头结点的构造函数

1)无参构造函数

不带头结点的无参构造函数,只需让头指针指向为空。

template <class T>
linkList<T>::linkList(){//无参构造函数
    first=NULL;//头指针置为空
}

2)头插法的有参构造函数

头插法就是每次将新申请的结点插到头结点的后面,即始终让新结点在第一的位置。

使用头插法建立有参构造函数,首先令头指针置为空(与无参构造函数相同),此后,对于 n 个数组元素:

  • 建立一个结点,为结点的数据域赋值
  • 将新结点的指针域指向头指针所指向的地址
  • 更改头指针地址为新结点地址
template <class T>
linkList<T>::linkList(T a[],int n){//头插法的有参构造函数
    first=NULL;
    for(int i=0;i<n;i++){
        Node<T> *s=new Node<T>;//建立一个新结点
        s->data=a[i];//为新结点的数据域赋值
        s->next=first;//将新结点的指针域指向头指针所指向的地址
        first=s;//更改头指针地址为新结点地址
    }
}

 3)尾插法的有参构造函数

尾插法就是每次将新申请的结点插到终端结点的后面,即始终让新结点在最后的位置。

使用尾插法建立有参构造函数,首先令头指针置为空(与无参构造函数相同),再建立一个尾指针,代表单链表中最后一个数据元素的位置,然后建立一个初始结点,将其作为头结点:

  • 初始结点的数据域赋值为第 1 个数据元素的值
  • 初始结点的指针域指向头指针指向的地址
  • 更改头指针地址为初始结点地址
  • 更改尾指针地址为初始结点地址。

此后,对于 n 个数据元素:

  • 建立一个结点,为结点的数据域赋值
  • 将尾指针指针域指向新建立的结点
  • 更改尾指针地址为新结点地址
template <class T>
linkList<T>::linkList(T a[],int n){//尾插法的有参构造函数
    first=null;
    Node<T> *r=first;//尾指针初始化
    Node<T> *s=new Node<T>;//建立一个初始结点
    
    s->data=a[0];//为初始结点数据域赋值
    s->next=first;//初始结点指针域指向头指针的地址
    first=s;/更改头指针地址为初始结点地址
    r=first;//更改尾指针地址为新结点地址

    for(int i=1;i<n;i++){
        s=new Node<T>;//建立一个新结点
        s->data=a[i];为新结点数据域赋值
        r->next=s;//尾指针指针域指向新结点
        r=s;//更改尾指针地址为新结点地址
    }
}

 3.带头结点和不带头结点的单链表的比较

操作带头结点不带头结点
空表first=new node<T>;
first->next=NULL;
first=NULL
在空表中插入一个结点 s

Node<T> *s=new node<T>;

s->next=first->next;
first->next=s

Node<T> *s=new node<T>;

first=s;

插入一个结点,使其成为首元结点

Node<T> *s=new node<T>;

s->next=first->next;
first->next=s

Node<T> *s=new node<T>;

s->next=first;
first=s;

在某一结点 p 后插入结点 s

Node<T> *s=new node<T>;

s->next=p->next;
p->next=s

Node<T> *s=new node<T>;

s->next=p->next;
p->next=s;

【析构函数】

单链表中的结点是用 new 申请的,在释放单链表的对象时无法自动释放这些结点的存储空间,因此析构函数应将单链表中的结点的存储空间释放

template<class T>
linkList<T>::~linkList(){
    while(first!=NULL){
        Node<T> *q=first;//暂存要被释放的结点
        first=first->next;//first指向要被释放的结点的下一个结点
        delete q;//删除结点
    }
}

【获取线性表长度】

求单链表的长度时,在将单链表扫描同时建立一个累加器,直到扫描到链表尾,时间复杂度为 O(n)

template<class T>
int linkList<T>::getLength(){
    int count=0;//累加器
    Node<T> p=first->next;//工作指针
    while(p!=NULL){//p不为空
        p=p->next;//指向下一结点
        count++;//累加器+1
    }
    return count;
}

【查找元素】

1.按位查找

按位查找时,不能像顺序表按序号访问,只能从头指针出发顺着 next 域逐个结点向下搜索

  • 工作指针 p、位置累加器初始化
  • 直到 p 为空或指向第 i 个节点:工作指针后移、位置累加器+1
  • 若 p 为空,则第 i 个元素不存在,抛出位置异常;否则查找成功,返回节点 p 的数据元素
template<class T>
T linkList<T>::getElement(int i){
    int pos=1;//位置累加器
    Node<T> p=first->next;//工作指针
    while(p!=NULL&&pos<i){//p不为空且位置小于i
        p=p->next;//指向下一结点
        pos++;//累加器+1
    }
    if(p==NULL)
        throw "查找位置异常";
    else
        return p->data;//返回数据元素
}

2.按值查找

按值查找时,需要对单链表中的元素依次进行比较,时间复杂度为 O(n)

  • 工作指针 p、位置累加器初始化
  • 直到 p 为空:工作指针后移、位置累加器+1
  • 若在循环中找到元素,则返回序号,即位置累加器的值;若退出循环,则查找失败,单链表中没有相关值
template<class T>
int linkList<T>::getPosition(T x){
    int pos=1;//位置累加器
    Node<T> p=first->next;//工作指针
    while(p!=NULL){//p不为空
        if(p->data==x)//找到值
            return pos;//返回位置
        p=p->next;//指向下一结点
        pos++;//累加器+1
    }
    return 0;//查找失败
}

【插入操作】

1.带头结点的插入

在单链表中的第 i 个位置插入元素,需要先找到第 i-1 个元素,若找不到则抛出位置异常,若找到则将新结点插入位置 i

  • 工作指针 p 、位置计数器初始化
  • 查找第 i-1 个节点,并使工作指针 p 指向该节点
  • 若查找不成功(P==NULL),说明位置错误,抛出位置异常,否则生成一个元素值为 x 的新节点 s,并将 s 插入到 p 之后
template<class T>
void linkList<T>::insertElement(int i,T x){
    int pos=0;//位置累加器
    Node<T> *p=first->next;//工作指针
    while(p!=NULL&&pos<i-1){//p不为空且位置小于i-1
        p=p->next;//指向下一结点
        pos++;//累加器+1
    }
    if(p==NULL)
        throw "插入位置异常";
    else{
        Node<T> *s=new Node<T>;//新申请一个结点
        s->data=x;//新结点数据域赋值为x
        s->next=p->next;//新结点s指向工作结点p之后的结点
        p->next=s;//工作结点p指向新结点s
    }
}

2.不带头结点的插入

不带头结点的插入与带头结点的插入十分相似,区别仅在于当插入位置是第一个位置时

template<class T>
void linkList<T>::insertElement(int i,T x){
    if(i==1){//若在第一个位置插入
        Node<T> *s=new Node<T>;//新申请一个结点
        s->next=first;//新结点的指针域指向头指针所指向的结点
        first=s;//令头指针指向新结点
        return;
    }

    int pos=1;//位置累加器
    Node<T> *p=first;//工作指针
    while(p!=NULL&&pos<i-1){//p不为空且位置小于i-1
        p=p->next;//指向下一结点
        pos++;//累加器+1
    }
    if(p==NULL)
        throw "插入位置异常";
    else{
        Node<T> *s=new Node<T>;//新申请一个结点
        s->data=x;//新结点数据域赋值为x
        s->next=p->next;//新结点s指向工作结点p之后的结点
        p->next=s;//工作结点p指向新结点s
    }
}

【删除操作】

在单链表中删除第 i 个位置的元素,需要先找到第 i-1 个元素,若找不到则抛出位置异常,若找到将第 i-1 个结点指针域所指向的结点删除

  • 工作指针 p 、位置计数器初始化
  • 查找第 i-1 个节点,并使工作指针 p 指向该节点
  • 若查找不成功(P==NULL),说明位置错误,抛出位置异常,否则将被删结点从链表中摘下并释放存储空间

template<class T>
T linkList<T>::deleteElement(int i){
    int pos=0;//位置累加器
    Node<T> *p=first;//工作指针
    while(p!=NULL&&pos<i-1){//p不为空且位置小于i-1
        p=p->next;//指向下一结点
        pos++;//累加器+1
    }

    if(p==NULL||p->next!=NULL)//p不存在或p的后继结点不存在
        throw "删除位置异常";
    else{
        Node<T> *s=p->next;//暂存被删结点
        T x=p->data;
        p->next=s->next;//摘链
        delete s;//释放空间
        return x;
    }
}

【遍历输出】

遍历输出即按照单链表的指针指向依次输出线性表各元素

  • 建立一个临时结点,令临时结点地址为头指针指向的结点
  • 当临时结点不为空时,输出数据域,并指向下一结点
template <class T>  
void LinkList<T>:: print(){//依次输出线性表各元素
    Node<T> *p;//新建一个临时结点
    p=first->next;//令临时结点地址为头指针所指向的结点
    while(p!=NULL){//当临时结点不为空时
        cout<<(p->data)<<endl;//输出数据域
        p=p->next;//指向下一结点
    }
}

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值