数据结构------线性表(链表)
我们在上一节了解了线性表的顺序实现,如果还没有看过的小伙伴可以点击这里:
我们今天来看线性表的链式实现:
单链表
单链表(Singly Linked List)是一种基本的数据结构,它采用链式存储的方式来存储一组具有先后关系的数据元素。在单链表中,每一个元素称为节点(Node),包含两部分信息:数据域(Data)和指针域(Next Pointer)。数据域用于存储元素的实际数据,而指针域用于存储指向下一个节点的地址。
单链表的基本操作包括:
- 插入节点:在链表的任意位置插入一个新的节点,包括头部插入、尾部插入和中间插入。
- 删除节点:根据指定的条件(如节点值或位置)删除链表中的一个节点。
- 查找节点:根据指定的条件(如节点值)查找链表中是否存在这样的节点,并返回该节点或其位置。
- 遍历链表:从头节点开始,依次访问每个节点,直到到达尾节点。
- 更新节点:修改链表中某个节点的数据域的值。
单链表的特点:
- 插入和删除操作相对数组来说更为灵活高效,时间复杂度一般为O(1)至O(n),具体取决于操作位置。
- 单链表无法直接随机访问,若想访问第i个节点,需要从头节点开始连续遍历i次才能达到,因此随机访问的时间复杂度为O(n)。
- 只需存储每个节点的后继节点地址,不需要像数组那样预留连续的内存空间,所以在内存利用率上较灵活,但存储相同数量的数据,所需的空间可能大于数组(每个节点都需要额外的指针空间)。
单链表有两种常见的特殊形式:带头结点的单链表和不带头结点的单链表。带头结点的单链表便于实现一些算法,例如在链表头部插入新节点时,无需判断特殊情况。
封装结点类
其实,单链表的实现是依靠一个一个的结点实现的,所以我们可以先把结点封装起来:
这里我们用C++来实现:
cpp
template<class T> //使用模板
struct linknode //结点类的定义
{
linknode()
:_data(T())
,_next(nullptr)
{
}
linknode(const T& data)
:_data(data)
,_next(nullptr)
{
}
//结点数据
T _data;
//存放下一个结点的指针
linknode<T>* _next;
};
单链表的封装
我们通过结点,来封装一个只有一个头结点的单链表:
cpp
template<class T>
class SLink
{
typedef linknode<T> Node; //重命名类型
public:
private:
Node* _head; //头结点
};
这样我们的单链表一来就会有一个结点,我们把这个结点当做头结点。
头插
我们来完成头插:
cpp
//创建结点
Node* createNode(const T& data)
{
Node* newnode = new Node(data);
if(newnode == nullptr)
{
perror("new fail");
return nullptr;
}
return newnode;
}
//头插
void FrontInsert(const T& data)
{
Node* newnode = createNode(data); //创建新结点
if(newnode == nullptr)
{
exit(EXIT_FAILURE);
}
if(_head ->_next == nullptr) //如果只有头结点
{
_head ->_next = newnode;
}
else
{
newnode->_next =_head->_next;
_head ->_next = newnode;
}
}
然后我们实现一个打印函数:
cpp
//打印
void PrintList()
{
Node* cur = _head->_next;
while(cur != nullptr)
{
std::cout<<cur->_data<<" ";
cur = cur -> _next;
}
std::cout<<"END"<<std::endl;
}
在main函数里面执行一下:
cpp
#include"SLink.h"
int main()
{
SLink<int> list;
list.FrontInsert(234);
list.FrontInsert(1);
list.FrontInsert(24);
list.FrontInsert(89);
list.PrintList();
return 0;
}
尾插
因为我们没有设置尾指针,所以我们得移动到最后一个结点,再完成尾插:
cpp
//尾插
void TailInsert(const T& data)
{
Node* newnode = createNode(data); //创建新结点
Node* cur = _head->_next;
while(cur->_next)
{
cur = cur -> _next;
}
cur->_next = newnode;
}
cpp
#include"SLink.h"
int main()
{
SLink<int> list;
list.FrontInsert(234);
list.FrontInsert(1);
list.FrontInsert(24);
list.FrontInsert(89);
list.PrintList();
list.TailInsert(234);
list.TailInsert(1);
list.TailInsert(4);
list.PrintList();
return 0;
}
在任意位置插入
在任意位置插入,我们首先的要知道链表长度,我们可以让头结点储存结点的个数:
我们写一个函数来计算链表长度:
cpp
//链表长度
size_t LenghthOfList()
{
size_t count = 0;
Node* cur = _head -> _next;
while(cur)
{
count++;
}
return count;
}
在pos之后插入
cpp
//在pos之后插入
void InsertPosAfter(const size_t& pos,const T& data)
{
size_t leghth = LenghthOfList();
assert(pos >= 0 && pos <= leghth);
if(pos == 0) //pos等于0,等于头插
{
FrontInsert(data);
return;
}
else if(pos == leghth) //pos等于长度为尾插
{
TailInsert(data);
return;
}
else
{
Node* cur = _head;
size_t cur_pos = 0;
while(cur_pos < pos)
{
cur = cur -> _next;
cur_pos++;
}
Node* after = cur -> _next;
Node* newnode = new Node(data);
newnode->_next = after;
cur->_next = newnode;
}
}
在pos之前插入
cpp
//在pos之前插入
void InsertPosBefore(const size_t& pos,const T& data)
{
size_t length = LenghthOfList();
assert(length > 0);
//前后指针
Node* prve = nullptr;
Node* cur = _head;
size_t cur_pos = 0;
while(cur_pos < pos)
{
prve = cur;
cur = cur->_next;
cur_pos++;
}
Node* newnode = new Node(data);
newnode->_next=cur;
prve->_next=newnode;
}
我们可以测试一下:
cpp
#include"SLink.h"
int main()
{
SLink<int> list;
list.FrontInsert(234);
list.FrontInsert(1);
list.FrontInsert(24);
list.FrontInsert(89);
list.PrintList();
list.TailInsert(234);
list.TailInsert(1);
list.TailInsert(4);
list.PrintList();
std::cout<<"lenghth of list "<<list.LenghthOfList()<<std::endl;
list.InsertPosAfter(2,899);
list.PrintList();
list.InsertPosAfter(0,999);
list.PrintList();
list.InsertPosBefore(1,76);
list.PrintList();
list.InsertPosBefore(2,176);
list.PrintList();
return 0;
}
删除
头删
头删就是删除第一个元素:
cpp
//头删
void FrontErease()
{
Node* deleteNode = _head->_next;
Node* after = deleteNode ->_next; //第二个元素
_head ->_next = after;
delete deleteNode;
}
cpp
#include"SLink.h"
int main()
{
SLink<int> list;
list.FrontInsert(234);
list.FrontInsert(1);
list.FrontInsert(24);
list.FrontInsert(89);
list.PrintList();
list.TailInsert(234);
list.TailInsert(1);
list.TailInsert(4);
list.PrintList();
std::cout<<"lenghth of list "<<list.LenghthOfList()<<std::endl;
list.InsertPosAfter(2,899);
list.PrintList();
list.InsertPosAfter(0,999);
list.PrintList();
list.InsertPosBefore(1,76);
list.PrintList();
list.InsertPosBefore(2,176);
list.PrintList();
list.FrontErease();
list.PrintList();
return 0;
}
尾删
尾删是删除最后一个元素:
cpp
//尾删
void TailErease()
{
//找到倒数第二个结点
Node* cur = _head->_next;
while(cur->_next->_next)
{
cur = cur->_next;
}
Node* deleteNode = cur->_next;
cur->_next = deleteNode->_next;
delete deleteNode; //释放结点
}
cpp
#include"SLink.h"
int main()
{
SLink<int> list;
list.FrontInsert(234);
list.FrontInsert(1);
list.FrontInsert(24);
list.FrontInsert(89);
list.PrintList();
list.TailInsert(234);
list.TailInsert(1);
list.TailInsert(4);
list.PrintList();
std::cout<<"lenghth of list "<<list.LenghthOfList()<<std::endl;
list.InsertPosAfter(2,899);
list.PrintList();
list.InsertPosAfter(0,999);
list.PrintList();
list.InsertPosBefore(1,76);
list.PrintList();
list.InsertPosBefore(2,176);
list.PrintList();
list.FrontErease(); //头删
list.PrintList();
list.TailErease(); //尾删
list.PrintList();
return 0;
}
任意位置删除
任意位置删除我们也可以通过两个指针来完成:
cpp
//任意位置删除
void EreasePos(size_t pos)
{
size_t length = LenghthOfList();
assert(length > 0 && pos <= length && pos > 0);
if(pos == 1)
{
FrontErease();
return;
}
else
{
Node* prve = nullptr;
Node* deletNode = _head;
size_t cur_pos = 0;
while(cur_pos < pos)
{
prve = deletNode;
deletNode = deletNode->_next;
cur_pos++;
}
Node* after = deletNode->_next;
prve->_next = after;
delete deletNode;
}
}
cpp
#include"SLink.h"
int main()
{
SLink<int> list;
list.FrontInsert(234);
list.FrontInsert(1);
list.FrontInsert(24);
list.FrontInsert(89);
list.PrintList();
list.TailInsert(234);
list.TailInsert(1);
list.TailInsert(4);
list.PrintList();
std::cout<<"lenghth of list "<<list.LenghthOfList()<<std::endl;
list.InsertPosAfter(2,899);
list.PrintList();
list.InsertPosAfter(0,999);
list.PrintList();
list.InsertPosBefore(1,76);
list.PrintList();
list.InsertPosBefore(2,176);
list.PrintList();
list.FrontErease(); //头删
list.PrintList();
list.TailErease(); //尾删
list.PrintList();
list.EreasePos(2); //任意位置删除
list.PrintList();
return 0;
}
到这里,单链表的主要操作都差不多写完了,单链表我之前也写过一篇,是用C语言写的,如果看这篇吃力的话可以点击这里,这篇更详细:
单链表我们只是复习,我们主要看单链表的几种变形:带尾指针,循环,双向,双向循环。