与数组相似,链表也是一种线性数据结构。这里有一个例子:



  正如你所看到的,链表中的每个元素实际上是一个单独的对象,而所有对象都通过每个元素中的引用字段链接在一起。

  链表有两种类型:单链表和双链表。上面给出的例子是一个单链表,这里有一个双链表的例子:双向链表(DoubleLinkList)

  单链表中的每个结点不仅包含值,还包含链接到下一个结点的引用字段。通过这种方式,单链表将所有结点按顺序组织起来。

下面是一个单链表的例子:



  蓝色箭头显示单个链接列表中的结点是如何组合在一起的。

结点结构

以下是单链表中结点的典型定义:

template<typename T>
class Node
{
public:
T e;
Node<T>*next;
Node():e(0),next(nullptr){}
Node(T& E):e(E),next(nullptr){}
Node(T& E,Node<T>*Next):e(E),next(Next){}
};

  在大多数情况下,我们将使用头结点(第一个结点)来表示整个列表。

操作

  与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按索引来访问元素平均要花费 O(N) 时间,其中 N 是链表的长度。

  例如,在上面的示例中,头结点是 23。访问第 3 个结点的唯一方法是使用头结点中的“next”字段到达第 2 个结点(结点 6); 然后使用结点 6 的“next”字段,我们能够访问第 3 个结点。

添加操作 - 单链表

如果我们想在给定的结点 prev 之后添加新值,我们应该:

  1. 使用给定值初始化新结点 cur

  2. 将 cur 的“next”字段链接到 prev 的下一个结点 next

  3. 将 prev 中的“next”字段链接到 cur

  与数组不同,我们不需要将所有元素移动到插入元素之后。因此,您可以在 O(1) 时间复杂度中将新结点插入到链表中,这非常高效。

示例



  让我们在第二个结点 6 之后插入一个新的值 9。

  我们将首先初始化一个值为 9 的新结点。然后将结点 9 链接到结点 15。最后,将结点 6 链接到结点 9。

插入之后,我们的链表将如下所示:

代码:

template<typename T>
void LinkedList<T>::add(int index, T e) {
if(index >= 0 && index <= size)
{
Node<T>*prev = dummyHead;
for(int i = 0;i<index;++i){
prev = prev->next; //遍历到node为要插入节点的前一节点
}
//第一种写法
// Node<T>*newNode = Node<T>(e); //创建新节点传入直e
// newNode->next = node->next; //新节点的next指向要插入节点
// node->next = newNode; //要插入节点的前一节点的next指向新节点
//第二种写法
prev->next = new Node<T>(e,prev->next); //创建一个节点传入直和让新节点的next指向插入节点,然后要插入节点的前一节点的next指向新节点
++size;
}
}

在开头添加结点

  众所周知,我们使用头结点来代表整个列表。

因此,在列表开头添加新节点时更新头结点 head 至关重要。

  1. 初始化一个新结点 cur
  2. 将新结点链接到我们的原始头结点 head
  3. cur 指定为 head

  例如,让我们在列表的开头添加一个新结点 9。

  我们初始化一个新结点 9 并将其链接到当前头结点 23。



  指定结点 9 为新的头结点。

代码:

template<typename T>
void LinkedList<T>::addFirst(T e) {
//第一种写法
// Node<T>*node = new Node<T>(e); //创建一个节点,把直放入节点
// node->next = head; //让创建的节点的下next指向当前头
// head = node; //头指向新创建的节点
//第二种写法
// head = new Node<T>(e,head); //新创建一个节点传入数据和头让新节点的next指向head,然后head在指向新节点
// ++size;
add(0,e);
}

在末尾添加节点

代码:

template<typename T>
void LinkedList<T>::addLast(int e) {
add(size,e);
}

删除操作 - 单链表

如果我们想从单链表中删除现有结点 cur,可以分两步完成:

  1. 找到 cur 的上一个结点 prev 及其下一个结点 next

  2. 接下来链接 prevcur 的下一个节点 next

  在我们的第一步中,我们需要找出 prevnext。使用 cur 的参考字段很容易找出 next,但是,我们必须从头结点遍历链表,以找出 prev,它的平均时间是 O(N),其中 N 是链表的长度。因此,删除结点的时间复杂度将是 O(N)

  空间复杂度为 O(1),因为我们只需要常量空间来存储指针。

示例

让我们尝试把结点 6从上面的单链表中删除。

  1. 从头遍历链表,直到我们找到前一个结点 prev,即结点 23

  2. 将 prev(结点 23)与 next(结点 15)链接



    结点 6 现在不在我们的单链表中。

代码:

template<typename T>
T LinkedList<T>::remove(const int index) {
if(index>=0 && index<=size)
{
Node<T>*prev = dummyHead;
for(int i = 0;i<index;++i) //找到要删除节点的前一个节点
{
prev = prev->next;
}
Node<T>*retNode = prev->next; //保存要删除的节点
prev->next = retNode->next; //让前一节点next指向要删除节点的后一节点
retNode->next = nullptr; //要删除节点next指向空
--size;
return retNode->e;
}
}

删除第一个结点

如果我们想删除第一个结点,策略会有所不同。

  正如之前所提到的,我们使用头结点 head 来表示链表。我们的头是下面示例中的黑色结点 23。

  如果想要删除第一个结点,我们可以简单地将下一个结点分配给head。也就是说,删除之后我们的头将会是结点 6。



  链表从头结点开始,因此结点 23 不再在我们的链表中。

代码:

template<typename T>
T LinkedList<T>::removeFirst() {
return remove(0);
}

删除最后一个结点

代码:

template<typename T>
T LinkedList<T>::removeLast() {
return remove(size-1);
}

时间复杂度

代码清单

LinkedList.h

#ifndef C___LINKEDLIST_H
#define C___LINKEDLIST_H
#include <iostream>
template<typename T>
class Node
{
public:
T e;
Node<T>*next;
Node():e(0),next(nullptr){}
Node(T& E):e(E),next(nullptr){}
Node(T& E,Node<T>*Next):e(E),next(Next){}
};
template<typename T>
class LinkedList { public:
LinkedList();
//返回连表大小
int getSize()const;
//判断是否为空
bool isEmpty()const;
//头插入
void addFirst(T e);
//为插入
void addLast(T e);
//插入
void add(int index, T e);
//练习:获取链表第index个位置的元素
T get(const int index);
//获取链表第一个元素
T getFirst();
//获取链表最后一个元素
T getLast();
//练习:修改链表第index个位置的元素
void set(const int index,const T&e);
//查找链表是否有元素e
bool contains(const T&e)const;
//删除元素
T remove(const int index);
//删除头
T removeFirst();
//删除尾
T removeLast();
//打印链表
void print()const;
private:
Node<T>*dummyHead; //虚拟头节点,不存数据
int size; //记录大小
}; template<typename T>
int LinkedList<T>::getSize() const {
return size;
} template<typename T>
bool LinkedList<T>::isEmpty() const {
return size == 0;
} template<typename T>
void LinkedList<T>::addFirst(T e) {
//第一种写法
// Node<T>*node = new Node<T>(e); //创建一个节点,把直放入节点
// node->next = head; //让创建的节点的下next指向当前头
// head = node; //头指向新创建的节点
//第二种写法
// head = new Node<T>(e,head); //新创建一个节点传入数据和头让新节点的next指向head,然后head在指向新节点
// ++size;
add(0,e);
} template<typename T>
void LinkedList<T>::add(int index, T e) {
if(index >= 0 && index <= size)
{
Node<T>*prev = dummyHead;
for(int i = 0;i<index;++i){
prev = prev->next; //遍历到node为要插入节点的前一节点
}
//第一种写法
// Node<T>*newNode = Node<T>(e); //创建新节点传入直e
// newNode->next = node->next; //新节点的next指向要插入节点
// node->next = newNode; //要插入节点的前一节点的next指向新节点
//第二种写法
prev->next = new Node<T>(e,prev->next); //创建一个节点传入直和让新节点的next指向插入节点,然后要插入节点的前一节点的next指向新节点
++size;
}
} template<typename T>
void LinkedList<T>::addLast(T e) {
add(size,e);
} template<typename T>
LinkedList<T>::LinkedList() {
dummyHead = new Node<T>();
size = 0;
} template<typename T>
T LinkedList<T>::get(const int index) {
if(index>=0 && index<=size)
{
Node<T>*cur = dummyHead->next; //把第一个元素的位置给cur
for(int i = 0;i<index;++i)
{
cur = cur->next;
}
return cur->e;//返回第index个节点的元素
}
} template<typename T>
T LinkedList<T>::getFirst() {
return get(0);
} template<typename T>
T LinkedList<T>::getLast() {
return get(size-1);
} template<typename T>
void LinkedList<T>::set(const int index, const T &e) {
if(index>=0 && index<=size)
{
Node<T> *cur = dummyHead->next;
for (int i = 0; i < index; ++i) {
cur = cur->next;
}
cur->e = e;
}
} template<typename T>
bool LinkedList<T>::contains(const T &e) const {
//第一种遍历
// Node<T>*cur = dummyHead->next;
// while(cur!= nullptr)
// {
// if(cur->e == e)
// {
// return true;
// }
// cur = cur->next;
// }
// return false;
//第二种遍历
for(Node<T>*cur = dummyHead->next;cur!= nullptr;cur = cur->next)
{
if(cur->e == e) //如果找到元素返回true
{
return true;
}
}
return false; //否则返回false
} template<typename T>
void LinkedList<T>::print() const {
std::cout << "LinkedList: size = " << size << std::endl;
std::cout << "["; for(Node<T>*cur = dummyHead->next;cur!= nullptr;cur = cur->next)
{
std::cout<<cur->e<<"->";
}
std::cout<<"NULL"<<"]"<<std::endl;
} template<typename T>
T LinkedList<T>::remove(const int index) {
if(index>=0 && index<=size)
{
Node<T>*prev = dummyHead;
for(int i = 0;i<index;++i) //找到要删除节点的前一个节点
{
prev = prev->next;
}
Node<T>*retNode = prev->next; //保存要删除的节点
prev->next = retNode->next; //让前一节点next指向要删除节点的后一节点
retNode->next = nullptr; //要删除节点next指向空
--size;
return retNode->e;
}
} template<typename T>
T LinkedList<T>::removeLast() {
return remove(size-1);
} template<typename T>
T LinkedList<T>::removeFirst() {
return remove(0);
}
#endif

main.cpp

int main()
{ LinkedList<int> *ll;
ll = new LinkedList<int>();
for(int i = 0;i<10;++i)
{
ll->addFirst(i);
ll->print();
}
ll->add(2,666);
ll->print();
cout<<endl;
cout<<"get(2)"<<ll->get(2)<<endl;
cout<<"getSize()"<<ll->getSize()<<endl;
cout<<"getFirst()"<<ll->getFirst()<<endl;
cout<<"getLast()"<<ll->getLast()<<endl;
cout<<"isEmpty"<<ll->isEmpty()<<endl;
cout<<"contains"<<ll->contains(666)<<endl;
ll->set(3,999);
ll->addLast(000);
ll->print();
cout<<endl;
ll->removeLast();
ll->removeFirst();
ll->remove(1);
ll->print();
return 0;
}

单链表(LinkedList)的更多相关文章

  1. Java数据结构-线性表之单链表LinkedList

    线性表的链式存储结构,也称之为链式表,链表:链表的存储单元能够连续也能够不连续. 链表中的节点包括数据域和指针域.数据域为存储数据元素信息的域,指针域为存储直接后继位置(一般称为指针)的域. 注意一个 ...

  2. 【数据结构和算法】001 单链表 LinkedList

    一.单链表(LinkedList)介绍和内存布局 链表是有序的列表,它在内存中的实际存储结构如下: 看上去虽然无序,但他是靠灭个链表节点元素的地址和next域来分清首尾相连的顺序,如下图所示,由头指针 ...

  3. Python与数据结构[0] -> 链表/LinkedList[0] -> 单链表与带表头单链表的 Python 实现

    单链表 / Linked List 目录 单链表 带表头单链表 链表是一种基本的线性数据结构,在C语言中,这种数据结构通过指针实现,由于存储空间不要求连续性,因此插入和删除操作将变得十分快速.下面将利 ...

  4. 单链表、循环链表的JS实现

    数据结构系列前言: 数据结构作为程序员的基本知识,需要我们每个人牢牢掌握.近期我也展开了对数据结构的二次学习,来弥补当年挖的坑......   当时上课的时候也就是跟着听课,没有亲自实现任何一种数据结 ...

  5. 数组、单链表和双链表介绍 以及 双向链表的C/C++/Java实现

    概要 线性表是一种线性结构,它是具有相同类型的n(n≥0)个数据元素组成的有限序列.本章先介绍线性表的几个基本组成部分:数组.单向链表.双向链表:随后给出双向链表的C.C++和Java三种语言的实现. ...

  6. Java单链表、双端链表、有序链表实现

    单链表: insertFirst:在表头插入一个新的链接点,时间复杂度为O(1) deleteFirst:删除表头的链接点,时间复杂度为O(1) 有了这两个方法,就可以用单链表来实现一个栈了,见htt ...

  7. C#_约束 实现可排序单链表

    using System; using System.Collections.Generic; using System.Linq; using System.Text; /* 使用 约束 实现可排序 ...

  8. 双链表---LinkedList的重写

    重写Linkedlist类,改写为MyLinkedList,未继承Iterable类. public class MyLinkedList<AnyType> { private int t ...

  9. "《算法导论》之‘线性表’":基于数组实现的单链表

    对于单链表,我们大多时候会用指针来实现(可参考基于指针实现的单链表).现在我们就来看看怎么用数组来实现单链表. 1. 定义单链表中结点的数据结构 typedef int ElementType; cl ...

随机推荐

  1. 用JIRA管理你的项目——(一)JIRA环境搭建

    JIRA,大家应该都已经不陌生了! 最初接触这个工具的时候,我还在一味地单纯依靠SVN管理代码,幻想着SVN可以有个邮件通知,至少在项目成员进行代码修改的时候,我可以第一时间通过邮件获得这个消息! 当 ...

  2. CentOS7中下载RPM及其所有的依赖包

    CentOS7中下载RPM及其所有的依赖包 转载beeworkshop 最后发布于2019-09-28 07:43:40 阅读数 1096  收藏 展开 利用 Downloadonly 插件下载 RP ...

  3. Linux如何查看文件的创建、修改时间?

    Linux如何查看文件的创建.修改时间? 利用stat指令查看文件信息 三种时间的介绍 ATime --文件的最近访问时间 只要读取时间,ATime就会更新 MTime --文件的内容最近修改的时间 ...

  4. OpenStack Rally 性能测试

    注意点:在测试nova,在配置文件里面如果不指定网络id,那么默认是外网的网络(该网络是共享的),如果想要指定网络,那么该网络必须是共享的状态,否则将会报错:无法发现网络.如果测试多于50台的虚拟机需 ...

  5. IEEE 网址

    https://ieeexplore.ieee.org/document/506397

  6. Lua的string库函数列表

    基本函数 函数 描述 示例 结果 len 计算字符串长度 string.len("abcd") 4 rep 返回字符串s的n个拷贝 string.rep("abcd&qu ...

  7. linux各文件夹的作用-(转自玉米疯收)

    linux下的文件结构,看看每个文件夹都是干吗用的 /bin 二进制可执行命令 /dev 设备特殊文件 /etc 系统管理和配置文件 /etc/rc.d 启动的配置文件和脚本 /home 用户主目录的 ...

  8. window location href is not a function(Day_36)

    报window location href is not a function错误的解决方案: 原因: JS报错是由于写法问题或浏览器不兼容导致的,具体解决方法如下: 原来报错的写法: window. ...

  9. Spring-Cloud之Feign原理剖析

    Feign 主要是帮助我们方便进行rest api服务间的调用,其大体实现思路就我们通过标记注解在一个接口类上(注解上将包含要调用的接口信息),之后在调用时根据注解信息组装好请求信息,接下来基于rib ...

  10. 【译】.NET 5 中的诊断改进

    基于我们在 .NET Core 3.0 中引入的诊断改进,我们一直在努力进一步改进这个领域.我很高兴介绍下一波诊断改进. 诊断工具不再需要 .NET SDK 直到最近,.NET 诊断工具套件还只能作为 ...