【实现类】
单链表的基本思想就是用指针表示结点之间的逻辑关系,因此要正确的对指针变量、指针、指针所指结点、结点的值进行区分。
设 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; | Node<T> *s=new node<T>; first=s; |
插入一个结点,使其成为首元结点 | Node<T> *s=new node<T>; s->next=first->next; | Node<T> *s=new node<T>; s->next=first; |
在某一结点 p 后插入结点 s | Node<T> *s=new node<T>; s->next=p->next; | Node<T> *s=new node<T>; s->next=p->next; |
【析构函数】
单链表中的结点是用 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;//指向下一结点
}
}