链表
链表和数组作为算法中的两个基本数据结构,在程序设计过程中经常用到。尽管两种结构都可以用来存储一系列的数据,但又各有各的特点。
数组 VS 链表
1. 数组
- 所申请的内存空间,必须是线性连续,且申请的空间大小必须提前确定。
- 插入和删除操作代价比较大,需要该位置后面的数据都向后移动,留出一个空位进行插入,或者都向前移动,把该空位的数据进行覆盖(也就是删除)。
- 查询代价较小,数组是连续存储的,知道该数组名称,可根据下标直接查询;
- 不利于扩展,数组空间是提前申请的,当存储空间不够时,需要重新申请空间。
2、链表
- 申请内存中存储空间,不要求连续,只需要保存下一个存储空间的内存地址即可。
- 插入和删除操作比较容易,只需要改变指针的指向即可。
- 查询代价较大,不具备随机访问能力,需要从头到尾遍历查找。
- 扩展性较好,不用提前指定大小,插入删除比较随意。
3、时间复杂度
操作 | 数组 | 链表 |
---|---|---|
查询 | O(1) | O(n) |
插入 | O(n) | O(1) |
删除 | O(n) | O(1) |
对于频繁查询的场景,选用数组;对于频繁插入删除的场景,选用链表;频繁查询也频繁增删呢?选用数组链表。
如果你有创造性思维的话,数组链表其实是一种更好的数据结构,集数组和链表优点于一身的数据结构,有兴趣的读者下来自行研究(这绝对是亮点)。
内存空间利用率:每一个数组单元是 100% 存储数据,每一个链表单元是存储数据 + 存储指针,数组对于内存的利用上大于链表(当然了,还需要看是否提前知道要存储数据个数)。
链表
链表所分配的空间在内存上不一定连续,但是链表具有其天然优势。
链表分为 4 种情况:单链表,单循环链表,双链表,双循环链表,一定要反复琢磨,理解清楚我下面画的模型,这些模型是本文核心所在;
各自模型如下:
(1)、单链表
(2)、单循环链表
(3)、双链表
(4)、双循环链表
- 单链表
单链表分为 2 种情况:带头结点链表 + 不带头结点链表。
带头结点链表:第一个节点不存储真实数据
不带头结点链表:第一个节点存储真实数据
链表上常见操作:创建链表、插入链表,删除链表,查找数据,修改数据,销毁链表…
链表的各种操作,只要能在纸上画出模型,掌握好几个指针的指向,就能将代码写出来。
- 双向循环链表
(1)、模型如下:
下面用代码简单实现一个双向链表,其他的链表可以自行实现,只要掌握了一个,其他的都是一样的。
DoubleLink.h
#ifndef _DOUBLELINK_H
#define _DOUBLELINK_H
#include <iostream>
using namespace std;
template < typename Type>
class DoubleLinkedList;
template<typename Type>
class DoubleLinkedListNode
{
friend class DoubleLinkedList<Type>;
public:
DoubleLinkedListNode(Type x = 0)
{
data = x;
prev = next = NULL;
}
~DoubleLinkedListNode()
{
}
private:
Type data; //数据
DoubleLinkedListNode* prev; //指向前一结点指针
DoubleLinkedListNode* next; //指向下一结点指针
};
template<typename Type>
class DoubleLinkedList
{
public:
DoubleLinkedList()
{
DoubleLinkedListNode<Type>* pTemp = new DoubleLinkedListNode<Type>;
first = last = pTemp;
pTemp->next = first;
pTemp->prev = last;
size = 0;
}
~DoubleLinkedList()
{
}
public:
//尾部插入
bool push_back(const Type& temp);
//打印所有
void print_list() const;
//头部插入
bool push_front(const Type& temp);
//按值插入
bool insert_val();
//删除最后一个结点
bool pop_back();
private:
DoubleLinkedListNode<Type> *first; //永远指向链表的头结点
DoubleLinkedListNode<Type> *last; //永远指向链表的尾结点
size_t size; //统计结点个数,不算没有数据的第一个;
};
template<typename Type>
bool DoubleLinkedList<Type>::pop_back()
{
DoubleLinkedListNode<Type> *pTemp1 = last->prev;
DoubleLinkedListNode<Type> *pTemp2 = last;
pTemp1->next = first;
first->prev = pTemp1;
last = pTemp1;
--size;
delete pTemp2;
pTemp2 = NULL;
return true;
}
template<typename Type>
bool DoubleLinkedList<Type>::insert_val()
{
int number1;
int number2;
std::cout << "请输入要插入位置的值 :";
std::cin >> number1;
std::cout << "请输入要插入的数据:";
std::cin >> number2;
DoubleLinkedListNode<Type> *pTemp = first->next;
DoubleLinkedListNode<Type> *pTemp2 = new DoubleLinkedListNode<Type>(number2);
while (pTemp != first)
{
if (pTemp->data == number1)
{
pTemp2->prev = pTemp;
pTemp2->next = pTemp->next;
pTemp->next->prev = pTemp2;
pTemp->next = pTemp2;
++size;
break;
}
pTemp = pTemp->next;
}
return true;
}
template<typename Type>
bool DoubleLinkedList<Type>::push_front(const Type& temp)
{
DoubleLinkedListNode<Type> *pTemp = new DoubleLinkedListNode<Type>(temp);
pTemp->next = first->next;
pTemp->prev = first;
first->next->prev = pTemp;
first->next = pTemp;
++size;
return true;
}
template<typename Type>
void DoubleLinkedList<Type>::print_list() const
{
DoubleLinkedListNode<Type> *pTemp = first->next;
while (pTemp != first)
{
std::cout << pTemp->data << "-->";
pTemp = pTemp->next;
}
std::cout << "End" << std::endl;
}
template<typename Type>
bool DoubleLinkedList<Type>::push_back(const Type& temp)
{
DoubleLinkedListNode<Type> *pTemp = new DoubleLinkedListNode<Type>(temp);
pTemp->next = first;
pTemp->prev = last;
first->prev = pTemp;
last->next = pTemp;
last = pTemp;
++size;
return true;
}
#endif // !_DOUBLELINK_H
main.cpp
#include "DoubleLink.h"
int main()
{
DoubleLinkedList<int> dc;
for (int i = 0; i < 10; i++)
{
dc.push_back(i);
}
dc.push_front(-2);
dc.print_list();
dc.insert_val(); //均是前插
dc.print_list();
dc.pop_back();
cout << "删除最后一个结点时如下: " << endl;
dc.print_list();
return 0;
}
运行结果如下:
对源码感兴趣的话可以看看STL中List的实现