链表是由一系列连接在一起的结点构成,其中的每个结点都是一个数据结构。
链表的结点通常是动态分配、使用和删除的,允许链表在程序运行时增大或缩小。如果需要将新信息添加到链表中,则程序只需分配另一个结点并将其插入到系列中。如果需要从链表中删除特定的信息块,则程序将删除包含该信息的结点。
链表对数组和矢量的优点
尽管链表的编码和管理比数组更复杂,但它们有一些明显的优势。首先,链表可以容易地扩大或缩小。实际上,程序员并不需要知道链表中有多少个结点。它们只是根据需要在内存中创建。
有人可能会争辩说,链表并不优于矢量(可在标准模板库中找到),因为它们也可以扩展或缩小。然而,链表对于矢量的优势是结点可以插入链表或从链表中删除的速度。要将值插入矢量的中间,需要将插入点之后的所有元素朝矢量的末尾移动一个位置,从而为新值腾出空间。同样,从矢量中删除一个值需要将删除点之后的所有元素都朝矢量的开始方向移动一个位置。而当一个结点插入链表或从链表中删除结点时,其他结点都不必移动。
链表的结构
链表中的每个结点都包含一个或多个保存数据的成员。例如,存储在结点中的数据可以是库存记录;或者它可以是由客户的姓名、地址和电话号码等组成的客户信息记录。除了数据之外,每个结点还包含一个后继指针指向链表中的下一个结点。图 1 给出了单个结点的组成。
![](https://img-blog.csdnimg.cn/img_convert/280597a15de4e760dd4aeed8a93e8fd1.gif)
非空链表的第一个结点称为链表的头。要访问链表中的结点,需要有一个指向链表头的指针。从链表头开始,可以按照存储在每个结点中的后继指针访问链表中的其余结点。最后一个结点中的后继指针被设置为 nullptr 以指示链表的结束。
因为指向链表头的指针用于定位链表的头部,所以也可以认为它代表了链表头。同样的指针也可以用来定位整个链表,从头开始,后面跟着后续指针,所以也可以很自然地把它看作是代表了整个链表。
图 2 给出了一个由 3 个结点组成的链表,其中显示了指向头部的指针,链表的 3 个结点以及表示链表末尾的 nullptr 指针。
![](https://img-blog.csdnimg.cn/img_convert/ecdd5aca2fa8575dc6177ca484e87b24.gif)
注意,图 2 中绘制的链表结点彼此非常接近,排列整齐。实际上,链表结点可能散布在内存的各个部分。
链表的C++表示-结构体
为了在 C++ 中表示链表,需要有一个表示链表中单个结点的数据类型。通过图 1 可以很自然地发现,这样一个数据类型不但需要包含要存储的数据结构,还要有一个指向另一个相同类型结点的指针。假设每个结点将存储一个类型为 double 的数据项,则可以声明以下类型来存放结点
struct ListNode{doublevalue;ListNode*next;};
在以上代码中,ListNode 就是要存储在链表中的结点的类型,结构成员 value 是结点的数据部分,而另一个结构成员 next 则被声明为 ListNode 的指针,它是指向下一个结点的后继指针。
ListNode 结构有一个有趣的属性,它包含一个指向相同类型数据结构的指针,因此可以说是一个包含对自身引用的类型。像这样的类型称为自引用数据类型或自引用数据结构。
在已经声明了一个数据类型来表示结点之后,即可定义一个初始为空的链表,方法是定义一个用作链表头的指针并将其初始化为 nullptr,示例如下:
ListNode *head = nullptr;
现在可以创建一个链表,其中包含一个结点,存储值为 12.5,如下所示
head = new ListNode; //分配新结点head->value=12.5; //存储值head->next= nullptr; //表示链表的结尾
接下来再看一看如何创建一个新结点,在其中存储 13.5 的值,并将其作为链表中的第二个结点。可以使用第二个指针来指向新分配的结点(其中将存储 13.5 的值),示例如下:
ListNode*secondPtr = new ListNode;secondPtr->value=13.5;secondPtr->next= nullptr; //第二个结点是链表的结尾head->next= secondPtr; //第一个结点指向第二个
请注意,以上语句通过将其后继指针 secondPtr->next 设置为 nullptr,可以使第二个结点成为链表的结尾,通过 head->next = secondPtr; 语句将链表头的后继指针改为指向第二个结点。
下面的程序说明了如何创建一个简单的链表:
// This program illustrates the creation || of linked lists.#include <iostream>using namespace std;struct ListNode{doublevalue;ListNode*next;};int main(){ListNode*head = nullptr;// Create first node with12.5head = new ListNode; //Allocate new nodehead->value=12.5; // Store the valuehead->next= nullptr; // Signify endof list// Create second node with13.5ListNode*secondPtr = new ListNode;secondPtr->value=13.5;secondPtr->next= nullptr; // Second node isendof listhead->next= secondPtr; //First node points to second// Print the listcout <<"First item is "<< head->value<< endl;cout <<"Second item is "<< head->next->value<< endl;return0;}
程序输出结果:
First item is 12.5
Second item is 13.5
使用构造函数初始化结点
我们知道,C++ 结构体可以有构造函数。对于定义链表结点类型的结构来说,如果能给它提供一个或多个构造函数,那将会带来很大的方便,因为这样将使得结点在创建时即可初始化。前文还曾经提到过,构造函数可以像常规函数一样,使用默认形参来定义,而为结点的后继指针提供一个默认的 nullptr 形参是很常见的。
以下是 ListNode 结构的另一个定义:
struct ListNode
{
double value;
ListNode *next;
//构造函数
ListNode(double valuel, ListNode *nextl = nullptr)
{
value = value1;
next = next1;
}
};
通过该声明,即可使用以下两种不同的方式创建一个结点:
通过仅指定其 value 部分,而后继指针则默认为 nullptr。
通过指定 value 部分和一个指向链表下一个结点的指针。
当需要创建一个结点放在链表的末尾时,第一种方法是很有用的;而当新创建的结点将被插入链表中间某个有后继结点的地方时,第二种方法是很有用的。
通过这个新的结点声明,即可使用比以前的示例要短得多的代码,创建一个存储值 12.5 的结点,而它的后面则是一个存储值 13.5 的结点,示例如下:
ListNode *secondPtr = new ListNode(13.5);
ListNode *head = new ListNode(12.5, secondPtr);
实际上,还可以放弃第二个指针,将上面的代码改写为以下形式:
ListNode *head = new ListNode(13.5);
head = new ListNode(12.5, head);
该语句之所以能和它前面的语句等效,就是因为以下赋值语句:
head = new ListNode(12.5, head);
该语句将从右到左评估,首先在构造函数中使用 head 的旧值,然后从 new 运算符返回的地址将被分配给 head,成为它的新值。
创建链表
使用 ListNode 的构造函数版本,可以很轻松地创建一个链表,方法是读取文件中的值并将每个新读取的值添加到已经累积的值链表的开头。
例如,使用numberList作为链表头,使用 numberFile 作为输入文件对象,则以下代码将读取存储在某个文本文件中的数字,并将它们排列在链表中:
ListNode*numberList = nullptr;doublenumber;while (numberFile >> number){//创建一个结点以保存该数字numberList = new ListNode(number, numberList);}
遍历链表
从链表头开始,涉及整个链表,并在每个结点上执行一些处理操作的过程被称为遍历链表。
例如,如果需要打印某个链表中每个结点的内容,则必须遍历该链表。假设某个链表的链表头指针是 numberList,要遍历该链表,则需要使用另一个指针 ptr 指向链表的开头:
ListNode *ptr = numberList;
然后就可以通过使用表达式 *ptr 或者使用结构指针操作符 -> 来处理由 ptr 指向的结点。例如,如果需要打印在结点上的值,则可以编写以下代码:
cout << ptr->value;
一旦在该结点的处理完成,即可将指针移动到下一个结点(如果有的话),其语句如下:
ptr = ptr->next;
以上语句使用指向结点后继的指针来替换了指向该结点的指针,实现了结点之间的移动。因此,要打印整个链表,可以使用如下代码:
ListNode*ptr = numberList;while (ptr != nullptr){cout << ptr->value<<" "; //处理结点(显示结点内容)ptr = ptr->next; //移动到下一个结点}
下面的程序演示了上面所介绍的各种技巧,即读取文件中的数字,将数字排列在链表中,然后通过遍历链表将数字显示在屏幕上:
// This program illustrates the building//and traversal of a linked list.#include <iostream>#include <fstream>using namespace std;struct ListNode{doublevalue;ListNode*next;// ConstructorListNode(double value1, ListNode *next1= nullptr){value =value1; next=next1;}};int main(){doublenumber; // Used toread the fileListNode*numberList = nullptr; // List of numbers//Open the fileifstream numberFile("numberFile•dat");if (!numberFile){cout <<"Error in opening the file of numbers.";exit (1);}//Read the fileinto a linked listcout <<"The contents of the file are: "<< endl;while (numberFile >> number){cout <<number<<" ";// Create a node to hold this numbernumberList = new ListNode(number, numberList);}// Traverse the list while printingcout << endl <<"The contents of the list are: "<< endl;ListNode*ptr = numberList;while (ptr != nullptr){cout << ptr->value<<" "; // Process nodeptr = ptr->next; //Movetonext node}return0;}
程序输出结果:
The contents of the file are:
10 20 30 40
The contents of the list are:
40 30 20 10
文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览37224 人正在系统学习中
![](https://img-blog.csdnimg.cn/img_convert/2858043d8400d8ad06790fadb4c2f03a.png)
![](https://img-blog.csdnimg.cn/img_convert/0c4eeb4347cf9a1d1a2a9302ab04cb88.png)
0
![](https://img-blog.csdnimg.cn/img_convert/e6ff3d5cc3e1d6916481aa601eda91f2.png)
![](https://img-blog.csdnimg.cn/img_convert/de4c28de0db030026a9d1e0f54c4a93b.png)
![](https://img-blog.csdnimg.cn/img_convert/4d0f3d238caa2a83558fba93c93f350d.png)
0
![](https://img-blog.csdnimg.cn/img_convert/980206384c46eccf97c9ea9a2539e503.png)
专栏目录
09-04
c++链表的反转,创建链表,插入链表,链表反转,可下载直接运行。
11-07
c++实现链表完整代码,可直接使用,经测试可在c++环境下正常编译运行。
C++ 链表详解_c++链表_嘉定世外的JinJiayang的博客
1-30
链表排序下面我们使用的是快速排序的办法,快速排序是一种线性对数 O(nlogn) 的算法,速度快,但是是一种不稳定的排序方法,C++的sort函数就是通过快速排序实现的,它使用一种分治的思想来实现。如果没有学过快速排序的话,你可以去百度上...
![](https://img-blog.csdnimg.cn/img_convert/d93c473c96f79e7b40131465d3648e21.png)
17万+
在自学C++的时候,看到好多人都在提链表,于是就自学了一下,看了很多别人的文章,加入了一些自己的理解,做了一下总结
![](https://img-blog.csdnimg.cn/img_convert/474adb2357c5658a7beca0fdbe1fa92b.png)
266
关于的C++ 链表,是比较有趣的。其中要用到指针与结构体。现在带你快速学会链表。
![](https://img-blog.csdnimg.cn/img_convert/a45e8106ed1e7275c7e03d2fbe39cf99.png)
09-04
主要介绍了C++链表倒序实现方法,是一个很经典的C++链表操作实例,需要的朋友可以参考下
![](https://img-blog.csdnimg.cn/img_convert/2e639a1a5d113cdb442c327127d31f18.png)
8649
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、链表定义与函数声明二、链表创建三、删除节点四、插入节点总结 前言 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。 一、链表定义与函数声明 首先,本文主要将链表的操作分为三个部分,即创建链表,删除节点以及增加节点。其中链表的结构体定义以及函数声明与主函数如下: typedef struct L.
![](https://img-blog.csdnimg.cn/img_convert/e29964e231d2750b9217552fd302afa9.png)
2938
一、链表的特点 1.链表是一种非连续的存储结构(相对于数组) 2.链表是由一系列的结点组成,每个结点包括两个部分:数据和指针。 3.链表通过节点中的指针来确定逻辑顺序 二、链表和数组的比较 1.数组通过数组的名字以及[ ]中的下标来进行访问数据,链表通过指针找到数据 2.数组无法动态增删元素(只能改),链表可以动态增删改结点 3.数组的定义、使用和回收空间操作简单,列表复杂 三、创建链表 ...
![](https://img-blog.csdnimg.cn/img_convert/5ae04bab8cf275427e8690fd3b6bcb31.png)
6418
链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用。链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。链表都有一个头指针,一般以head来表示,存放的是一个地址。链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。说到这里你应该就明白了,链表就如同车链子一样,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再
![](https://img-blog.csdnimg.cn/img_convert/93f5f411ea6ad01f47126650e786a850.png)
7404
最近在学习严蔚敏教授的《数据结构》,有一些感想,在这里写下来,留做笔记,希望能对以后学习有所帮助。 我们知道,线性表就是n个数据元素的有限序列,它有两种实现方式,其一为顺序表,即数组+伪指针的方式实现,另一为链表,采用的是指针的方式实现。由于顺序表的知识偏向基础且其应用灵活性不高,故在此不再介绍,本文主要说明几种常用的链表是如何实现的以及其基本操作。 1.单链表: 单...
11-21
使用C++实现双向循环链表的增删改查排序等操作,可查看个人博客的【[数据结构和算法]C/C++双向循环链表实现(增删改查排序)】--链接https://blog.csdn.net/slimmm/article/details/84317806
3465
前言 链表是一种非常非常基础的数据结构,本文首先讲解链表的基础知识,然后使用C++的模板实现了一个链表类,并简单实现了常见的插入、删除、查找等算法。 阅读本文需要对C/C++的指针具有一定的了解。 基础知识 链表是一种逻辑上连续,内存上分散的线性表数据结构,其基本单位为结点,每个结点分数据区和指针区,数据区用于存放数据,指针区则用于指向其他结点,通过指针每个结点就被串接成了一条“链子”。
![](https://img-blog.csdnimg.cn/img_convert/5fd0ca5e3d200adc45b14db552dae254.png)
1万+
1.链表基础知识: 链表分类:单链表、双链表、循环链表 链表的存储:散乱分布,通过指针域的指针链接在一起 链表的定义:此处给出单链表的c++定义 struct ListNode { int val; // 节点上存储的元素 ListNode *next; // 指向下一个节点的指针 ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数 }; 链表初始化及调用: ListNode* head = new ListNode(5);
![](https://img-blog.csdnimg.cn/img_convert/597992c931ec4e28b9c47749d8f4e807.png)
2916
![](https://img-blog.csdnimg.cn/img_convert/0152a7e9535ef1e0283b646f120c3715.png)
225
[Python]数据结构–链表 ‘Festinatione facit vastum’ 链表的概念 单链表的实现 循环单链表的实现 链表 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另
![](https://img-blog.csdnimg.cn/img_convert/c603fee5ec0fb97eb5035a3194bf5afe.png)
1万+
链表是一种动态数据结构,因为在创建链表的时候,无需知道链表的长度。链表的每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 struct node { int value; node* next; }; 当插入一个结点的时候,我们只需要为新结点分配内存,然后调节结点的指针指向,新结点就被(逻辑上)链接到链表里。 /* 链表尾加入新元素*/
![](https://img-blog.csdnimg.cn/img_convert/9e89d37e14d79d14b4c752971d4d04d4.png)
579
头文件:定义节点和链表数据类型。实现构造、插入、删除等操作。 #pragma once #include <cstdlib> #include <io