链表的概念及结构
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
链表的分类
链表可以按三种方式分类
这三种分类方式可以组合,也就是说,一共有8种不同的链表
常用的两种链表
无头单项非循环链表
结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等
代码实现
#ifndef CLION_SLIST_HPP
#define CLION_SLIST_HPP
#include <iostream>
namespace ns_SList{
template<class T>
class SListNode{
public:
T data;
SListNode* next;
};
template<class T>
class SList{
private:
typedef SListNode<T> Node;
typedef SListNode<T>* PNode;
PNode pHead;
public:
SList():pHead(nullptr)
{}
private:
PNode BuySListNode(const T& val){
PNode newNode = new Node;
if(newNode == nullptr){
std::cout<<"BuyNode Error"<<std::endl;
}
newNode->data = val;
newNode->next = nullptr;
return newNode;
}
public:
void SListPrint(){
PNode cur = pHead;
while(cur != nullptr){
std::cout<<cur->data<<"->";
cur = cur->next;
}
std::cout<< "nullptr" <<std::endl;
}
void push_back(const T& val){
PNode newNode = BuySListNode(val);
if(pHead == nullptr){
pHead = newNode;
}
else{
PNode tail = pHead;
while(tail->next != nullptr){
tail = tail->next;
}
tail->next = newNode;
}
}
void push_front(const T& val){
PNode newNode = BuySListNode(val);
newNode->next = pHead;
pHead = newNode;
}
void pop_back(){
if(pHead == nullptr){
return;
}
else if(pHead->next == nullptr){
delete pHead;
pHead = nullptr;
}
else{
PNode tail = pHead->next;
PNode prev = pHead;
while(tail->next != nullptr){
prev = tail;
tail = tail->next;
}
delete tail;
tail = nullptr;
prev->next = nullptr;
}
}
void pop_front(){
if(pHead == nullptr){
return;
}
else{
PNode oldHead = pHead;
pHead = oldHead->next;
delete oldHead;
oldHead = nullptr;
}
}
void insertAfter(PNode pos,const T& val){
assert(pos);
PNode newNode = BuySListNode(val);
newNode->next = pos->next;
pos->next = newNode;
}
void eraseAfter(PNode pos){
assert(pos);
if(pHead == nullptr){
return;
}
PNode nextNode = pos->next;
pos->next = nextNode->next;
delete nextNode;
nextNode = nullptr;
}
PNode find(const T& val){
PNode cur = pHead;
while(cur != nullptr){
if(cur->data == val){
return cur;
}
cur = cur->next;
}
return nullptr;
}
~SList(){
delete pHead;
pHead = nullptr;
}
};
}
#endif
带头双向循环链表
结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了.
代码实现
#ifndef CLION_DLIST_HPP
#define CLION_DLIST_HPP
#include <iostream>
namespace ns_DList{
template <class T>
class ListNode{
public:
ListNode* prev;
ListNode* next;
T val;
public:
ListNode(const T& val = T())
:prev(nullptr)
,next(nullptr)
,val(val)
{}
};
template <class T>
class List{
private:
typedef ListNode<T> Node;
typedef ListNode<T>* pNode;
pNode pHead;
public:
List()
:pHead(BuyListNode())
{
pHead->next = pHead;
pHead->prev = pHead;
}
public:
pNode BuyListNode(const T& val = T()){
pNode newNode = new Node(val);
return newNode;
}
void printList(){
pNode cur = pHead->next;
while(cur != pHead){
std::cout<< cur->val << "->" ;
cur = cur->next;
}
std::cout<<"nullptr"<<std::endl;
}
void insert(pNode pos, const T& val){
pNode newNode = BuyListNode(val);
pNode prev = pos->prev;
prev->next = newNode;
newNode->prev = prev;
newNode->next = pos;
pos->prev = newNode;
}
void push_back(const T& val){
insert(pHead,val);
}
void push_front(const T& val){
insert(pHead->next,val);
}
void pop_back(){
erase(pHead->prev);
}
void pop_front(){
erase(pHead->next);
}
void erase(pNode pos){
assert(pHead);
assert(pHead->next != pHead);
pNode prev = pos->prev;
pNode next = pos->next;
delete pos;
prev->next = next;
next->prev = prev;
}
pNode find(const T& val){
assert(pHead);
pNode cur = pHead->next;
while(cur != pHead){
if(cur->val == val){
return cur;
}
cur = cur->next;
}
return nullptr;
}
int size(){
int sz = 0;
pNode cur = pHead->next;
while(cur != pHead){
sz++;
cur = cur->next;
}
return sz;
}
~List(){
int sz = size();
for(int i = 0; i < sz, i++){
erase(pHead->next);
}
delete pHead;
pHead = nullptr;
}
};
}
#endif
顺序表和链表的区别
不同点 | 顺序表 | 链表 |
---|
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 | 随机访问 | 支持O(1) | 不支持O(N) | 任意位置插入或删除元素 | 可能需要搬移元素,效率低O(N) | 只需修改指针指向 | 插入 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念 | 应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 | 缓存利用率 | 高 | 低 |
|