类结构
在链表中用序号访问的范围是[1,n]
节点结构
单链表的节点结构应该由两个属性,分别存储着这个节点的数据和指向下一个节点的指针。
template<typename T> class LNode{
private:
//数据域
T data;
//指针域
class LNode<T>* next;
}
指向链表的壳子
指向链表的壳子中有三个属性,两条指针分别指向了头节点,尾节点,此外还记录了链表的长度
头节点与首元节点是不同的,头节点不存储数据,只为了统一操作逻辑而存在,其指针域指向首元节点。而头指针会指向链表的第一个节点,若有头节点则指向头节点,若无头节点则指向首元节点。对于空表来说,指向头节点(含有头节点的链表)或者指向空。
template<typename T> class CLinkList{
//属性
private:
//长度
int length{};
//头节点
LNode<T>* data;
//尾节点
LNode<T>* tail;
构造和析构
template<typename T>
LNode<T>::~LNode() {
if(this->next!= nullptr){
delete this->next;
}
}
节点析构时需要将后续的节点一并析构,否则后续的节点会泄露。
template<typename T>
CLinkList<T>::CLinkList() {
this->length=0;
this->data = new LNode<T>();
this->tail = data;
}
template<typename T>
CLinkList<T>::~CLinkList() {
delete this->data;
}
此处是带有头节点的版本,在构造壳子时会同时将头节点一同构造出来。析构时需要将指针指向的下一个
支持的一些方法
//头插法
template<typename T>
bool CLinkList<T>::HeadInsert(T element) {
LNode<T> *lNode = new LNode<T>();
if(this->length==0){
this->tail=lNode;
}
lNode->setData(element);
lNode->setNext(this->data->getNext());
this->data->setNext(lNode);
this->length++;
return true;
}
//尾插法
template<typename T>
bool CLinkList<T>::TailInsert(T element) {
auto *lNode = new LNode<T>();
lNode->setData(element);
lNode->setNext(this->tail->getNext());
this->tail->setNext(lNode);
this->length++;
this->tail = lNode;
return true ;
}
//打印列表
template<typename T>
void CLinkList<T>::Print() {
if(this->length == 0){
std::cout<<"[]"<<std::endl;
return;
}
cout<<"[";
LNode<T>* p = this->data->getNext();
while(p->getNext()!= nullptr){
cout<<p->getData()<<",";
p = p->getNext();
}
cout<<p->getData()<<"]"<<endl;
}
//按序号查找
template<typename T>
T& CLinkList<T>::operator[](int position) {
if(position<1||position>this->length){
cout<<"索引值无效"<<endl;
}
int pos = 1;
auto p = this->data->getNext();
while(pos<position){
p = p->getNext();
pos++;
}
return p->getData();
}
//按值查找
template<typename T>
LNode<T> *CLinkList<T>::locateElem(T e) {
auto p = this->data->getNext();
while(p!= nullptr){
if(p->getData()==e){
break;
}
p = p->getNext();
}
return p;
}
//在序号位置插入
template<typename T>
bool CLinkList<T>::ListInsert(int i, T e) {
int pos = 1;
if(i<1||i>this->length+1){
std::cout<<"插入位置无效"<<std::endl;
return false;
}
auto p = this->data->getNext();
while(pos<i-1){
p = p->getNext();
pos++;
}
auto lNode = new LNode<T>();
lNode->setData(e);
lNode->setNext(p->getNext());
p->setNext(lNode);
this->length++;
return true;
}
//删除序号位置的节点
template<typename T>
bool CLinkList<T>::ListDelete(int i, T &e) {
if(i<1||i>this->length){
std::cout<<"删除位置无效"<<std::endl;
return false;
}
int pos = 1;
auto p = this->data->getNext();
while(pos<i-1){
p = p->getNext();
pos++;
}
auto d = p->getNext();
p->setNext(p->getNext()->getNext());
d->setNext(nullptr);
e = d->getData();
delete d;
this->length--;
return true;
}
总结
- 链表是顺序表的链式存储。它的每一个节点在逻辑上是相邻的,但是在物理位置上则毫无关系。
- 由于其物理位置没有关系,故不可以随机存取,其读取速度较顺序表来说要慢,同时空间利用率也不如顺序表。但是顺序表需要一大片连续的空间,当空间不够时需要重新分配并拷贝内容,在空间分配上链表要更加的灵活。
- 在插入和删除操作中,时间复杂度虽然相同,但顺序表的移动元素要比链表的比较操作更加耗时,故在插入和删除中,链表比顺序表更好一些。