1.介绍
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。
单链表中每个结点的结构如下:
数据域保存了存储在该地址的数据,但指针域的指针却存放的不是该地址,而是下一个元素的地址。
因此,整个单链表的结构如下:
最后一个元素的指针域的指针设为nullptr,表示单链表到此结束。
一般情况下,为了操作统一,设置了一个在所有数据结点之前的一个结点,称为头结点。
可以利用该结点的数据部分存储单链表的个数。
而指向头节点的指针称为头指针。
2.操作
假设单链表长度为n,头指针为head,每个数据结点数据变量为data,指针变量为next(表示指向下一个地址的指针)。
(1)插入
因为单链表结构的原因,插入我们一般会找到要插入位置的前一个结点,假设为pre。假设要插入的结点为p,之后只需要这样的操作:
p->next=pre->next;
pre->next=p;
即如下图:
不过插入尾部时,pre->next不是上图所表示的结点,而是nullptr,但上面代码仍可适用。
顺序不能颠倒,一旦颠倒,我们就将找不回原来pre->next保存的地址,pre之后的结点将永远也找不回来,这样会造成内存泄漏。
而有无头节点的区别之一,就在于插入一个在所有单链表数据节点之前的位置的结点。
1.假如有头节点,那么我们插入的每一个位置之前都必定会有一个直接前驱结点,那么上面的操作对于有头节点的单链表所有插入位置都适用。
2.假如没有头节点,那么我们插入结点p在第一个位序位置时,需要特殊的进行下面的操作:
p->next=head;
head=p;
即如下图:
插入的时间复杂度和查找到要插入位置前一个结点的操作有关。
插入第一个位置,只需要找到头节点,时间复杂度O(1)。
插入尾部,需要找到最后一个元素,时间复杂度O(n)。
平均情况下,分别有n+1种情况,分别为找到头节点,第一个数据结点...最后一个结点。
因此,平均要找(0+1+2+...+n)/(n+1)=n/2次,因此平均时间复杂度为O(n)。
上面的都是找到前一个结点A,然后将结点B插入在结点A的后面,如果我们要在一个结点A的前面插入一个结点B呢?
比较笨的方法就是找到结点A的前一个结点,进行后插。
更好的是,先插入一个结点B在结点A的后面,然后将数据部分互换。
(2)删除
删除一个结点,首先确保有结点可以删。
其次,我们需要找到前面的结点pre,假设要删除的结点为p,之后就可以这样操作:
pre->next=p->next;
delete p;
即如下图:
只不过删除最后一个结点,我们的p->next是nullptr,而不是上图表示的结点,但无伤大雅。
这也是有无头节点的另一个区别。
1.假如有头节点,那么我们删除的每一个结点之前都必定会有一个直接前驱结点,那么上面的操作对于有头节点的单链表所有结点都适用。
2.假如,没有头节点,假设要删除的第一个结点为p,此时p=head,那么需要下面的操作:
head=head->next;
delete p;
因此,有头节点的单链表实在是操作简便的多。因为我们插入删除就不需要再来单独考虑第一个数据元素。
应用:
单链表经典的应用就是计算一个含有未知量的数学表达式。
例如:5*x+25*x²+2*x³
可以将node的数据域记录下每个未知量前面的系数、次方数(也可以不用保存,但需要单链表按次方数顺序排列,系数为0的结点不可省略)。
设计成员函数,改变x值,计算表达式。
还可以两个表达式之间合并,合并之前根据对存储的次方大小让两个链表排序,
注意当对应次方前面的常量相加为0时,就不用再耗费内存来保存。
下面的代码我们采用有头节点的单链表的方式,其他一些操作代码如下:
#pragma once
#include<iostream>
template<typename T>
class LinkList
{
public:
struct node
{
T data;
node* next;
public:
node(T d=0,node* n=nullptr):data(d),next(n){}
~node() { };
};
LinkList();
~LinkList();
void insert(int i,const T& val);
void remove(int i);
node* search(const T& val)const;//查找,返回对应的结点指针
void change(int i, const T& val);
void print()const;//打印单链表
int size()const;
void tailInsert();//尾插法创建单链表
void headInsert();//头插法创建单链表
private:
node* head;
node* moveTo(int i);//将第i个结点指针返回
};
//时间复杂度O(n)
template<typename T>
typename LinkList<T>::node* LinkList<T>::moveTo(int i)
{
//如果i为-1,会返回头指针,如果为0,则返回第一个数据节点,错误返回nullptr
if (i <-1)
return nullptr;
node* p = head;
for (int u = 0; u < i + 1; u++)
{
p = p->next;
if (!p)//防止p已经为nullptr,继续下一个循环的操作。
break;
}
return p;
}
//空间复杂度O(1)
template<typename T>
LinkList<T>::LinkList()
{
head = new node;
}
//时间复杂度O(n)
template<typename T>
LinkList<T>::~LinkList()
{
node* p = head;
head = nullptr;
while (p)
{
node* q = p;
p = p->next;
delete q;
}
}
//时间复杂度O(n)
template<typename T>
void LinkList<T>::insert(int i, const T& val)
{
//如果i出了范围moveTo函数会发出错误
node* pre = moveTo(i - 1);
if (!pre)
{
std::cout << "error" << std::endl;
return;
}
node* p = new node(val,pre->next);
pre->next = p;
}
//时间复杂度O(n)
template<typename T>
void LinkList<T>::remove(int i)
{
//如果i出了范围moveTo函数会发出错误
node* pre = moveTo(i - 1);
if (!pre)
{
std::cout << "error" << std::endl;
return;
}
node* p = pre->next;//防止pre是尾元素
if (!p)
{
std::cout << "error" << std::endl;
return;
}
pre->next = p->next;
delete p;
}
//时间复杂度为O(n)
template<typename T>
typename LinkList<T>::node* LinkList<T>::search(const T& val)const
{
node* p = head->next;
while (p)
{
if (p->val != val)
p = p->next;
}
//如果没有找到,会返回nullptr
return p;
}
//时间复杂度为O(n)
template<typename T>
void LinkList<T>::change(int i, const T& val)
{
node* p = moveTo(i);
p->val = val;
}
//时间复杂度为O(n)
template<typename T>
void LinkList<T>::print()const
{
node* p = head->next;
while (p)
{
std::cout << p->data << " ";
p = p->next;
}
std::cout << std::endl;
}
//时间复杂度为O(1)
template<typename T>
int LinkList<T>::size()const
{
node* p = head->next;
int count = 0;
while (p)
{
count++;
p = p->next;
}
return count;
}
//时间复杂度为O(n)
template<typename T>
void LinkList<T>::tailInsert()
{
T temp=0;
node* p = head;
while (std::cin >> temp)
{
node* q = new node(temp);
p->next = q;
p = q;
}
}
//时间复杂度为O(n)
template<typename T>
void LinkList<T>::headInsert()
{
T temp=0;
while (std::cin >> temp)
{
node* p = new node(temp,head->next);
head->next = p;
}
}