数据结构—链表—C++

一、链表定义

链表是由一系列连接在一起的结点构成,其中的每个结点都是一个数据结构。
链表的结点通常是动态分配、使用和删除的,允许链表在程序运行时增大或缩小。如果需要将新信息添加到链表中,则程序只需分配另一个结点并将其插入到系列中。如果需要从链表中删除特定的信息块,则程序将删除包含该信息的结点。

二、链表的结构

2.1 单个节点的组成

链表的每个节点,除数据之外还包含一个后继指针指向链表中的下一个节点。
链表单个节点的组成

2.2 链表图解结构

非空链表的第一个结点称为链表的头。要访问链表中的结点,需要有一个指向链表头的指针。从链表头开始,可以按照存储在每个结点中的后继指针访问链表中的其余结点。最后一个结点中的后继指针被设置为 nullptr 以指示链表的结束。
因为指向链表头的指针用于定位链表的头部,所以也可以认为它代表了链表头。同样的指针也可以用来定位整个链表,从头开始,后面跟着后续指针,所以也可以很自然地把它看作是代表了整个链表。
下图给出了一个由 3 个结点组成的链表,其中显示了指向头部的指针,链表的 3 个结点以及表示链表末尾的 nullptr 指针。
链表结构图解

2.3 头指针、头节点和首元节点详解

头节点:可有可无

有时,在链表的第一个节点之前会额外增设一个节点,该节点的数据域一般不存放数据(有些情况下也可以存放链表的长度等信息),此节点被称为头节点
若链表中存在头节点,且头节点的指针域为空(NULL),表明链表是空表。
头节点对于链表来说,不是必须的,换句话说,一个完整的链表中可以不设有头节点。那么,可能有人会问:既然头节点无关紧要,那它有什么作用?在处理某些问题时,给链表添加头节点会使问题变得简单。

首元节点

链表中第一个元素所在的节点,它是头节点后边的第一个节点。
其实,首元节点和链表中存放数据的其他节点没什么不同,只是因为该节点位于链表的头部,所以被称为首元节点。

头指针

链表的头指针永远指向链表中第一个节点的位置,换句话说,如果链表有头节点,头指针指向头节点;否则,头指针指向首元节点。
一个链表可以头节点,但不能没有头指针。

头节点和头指针的区别是:

  • 头指针是一个指针,头指针指向链表的头节点或者首元节点;
  • 头节点是一个实际存在的节点,它包含有数据域和指针域。
    头节点和头指针的区别在程序中的直接体现是:头指针只声明而没有分配存储空间,头节点需要声明并分配一个节点的实际物理内存。

三、链表的种类

在实际中,链表有许多形式,根据单向、双向带头、不带头循环、非循环的排列组合,就有八种形式的链表。
下面举几个常见的链表:

  • 不带头单链表
    在这里插入图片描述
  • 不带头双向链表
    在这里插入图片描述
  • 带头单链表
    在这里插入图片描述
  • 循环单链表
    在这里插入图片描述
  • 带头双向循环链表
    在这里插入图片描述
    虽然有很多的链表结构,但其实,我们最常用的就是两种结构:不带头单链表带头双向循环链表
  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  • 带头双向循环链表:结构最复杂,一股用在单独存储数据。 实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

四、链表和数组的区别

两者的区别

  1. 数组静态分配内存,链表动态分配内存。
  2. 数组在内存中是连续的,链表是不连续的。
  3. 数组利用下标定位,查找的时间复杂度是O(1),链表通过遍历定位元素,查找的时间复杂度是O(N)。
  4. 数组插入和删除需要移动其他元素,时间复杂度是O(N),链表的插入或删除不需要移动其他元素,时间复杂度是O(1)。

数组的优点

  1. 随机访问性比较强,可以通过下标进行快速定位。
  2. 查找速度快

数组的缺点
3. 插入和删除的效率低,需要移动其他元素。
4. 会造成内存的浪费,因为内存是连续的,所以在申请数组的时候就必须规定七内存的大小,如果不合适,就会造成内存的浪费。
5. 内存空间要求高,创建一个数组,必须要有足够的连续内存空间。
6. 数组的大小是固定的,在创建数组的时候就已经规定好,不能动态拓展。

链表的优点
7. 插入和删除的效率高,只需要改变指针的指向就可以进行插入和删除。
8. 内存利用率高,不会浪费内存,可以使用内存中细小的不连续的空间,只有在需要的时候才去创建空间。大小不固定,拓展很灵活。

链表的缺点
查找的效率低,因为链表是从第一个节点向后遍历查找。

五、链表的C++实现

5.1 链表实现的前提——节点的定义及初始化
struct ListNode
{
    double value;
    ListNode *next;
    //构造函数
    ListNode(double valuel, ListNode *nextl = nullptr)
    {
        value = value1;
        next = next1;
    }
};

通过该声明,即可使用以下两种不同的方式创建一个结点:

  • 通过仅指定其 value 部分,而后继指针则默认为 nullptr。
  • 通过指定 value 部分和一个指向链表下一个结点的指针。
5.2 链表的创建

例如,使用numberList作为链表头,使用 numberFile 作为输入文件对象,则以下代码将读取存储在某个文本文件中的数字,并将它们排列在链表中:

ListNode *numberList = nullptr;
double number;

while (numberFile >> number)
{
    //创建一个结点以保存该数字
    numberList = new ListNode(number, numberList);
}
5.3 遍历链表

从链表头开始,涉及整个链表,并在每个结点上执行一些处理操作的过程被称为遍历链表。
例如,如果需要打印某个链表中每个结点的内容,则必须遍历该链表。假设某个链表的链表头指针是 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
{
    double value;
    ListNode *next;
    // Constructor
    ListNode(double value1, ListNode *next1 = nullptr)
    {
        value = value1; next = next1;
    }
};
int main()
{
    double number; // Used to read the file
    ListNode *numberList = nullptr; // List of numbers
    // Open the file
    ifstream numberFile("numberFile•dat");
    if (!numberFile)
    {
        cout << "Error in opening the file of numbers.";
        exit (1);
    }
    //Read the file into a linked list
    cout << "The contents of the file are: " << endl;
    while (numberFile >> number)
    {
        cout << number << " ";
        // Create a node to hold this number
        numberList = new ListNode(number, numberList);
    }
    // Traverse the list while printing
    cout << endl << "The contents of the list are: " << endl;
    ListNode *ptr = numberList;
    while (ptr != nullptr)
    {
        cout << ptr->value << " "; // Process node
        ptr = ptr->next; // Move to next node
    }
    return 0;
}
5.4 链表初始化赋值
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
双向链表是一种常见的数据结构,与单向链表相比,它每个节点都有两个指针,分别指向前驱节点和后继节点。这样,双向链表可以实现双向遍历,插入和删除操作也更加方便。 以下是用C语言实现双向链表的代码: ```c #include <stdio.h> #include <stdlib.h> // 双向链表节点结构体 typedef struct ListNode { int val; struct ListNode *prev; struct ListNode *next; } ListNode; // 创建双向链表节点 ListNode *createNode(int val) { ListNode *node = (ListNode *)malloc(sizeof(ListNode)); node->val = val; node->prev = NULL; node->next = NULL; return node; } // 插入节点到双向链表头部 ListNode *insertAtHead(ListNode *head, int val) { ListNode *node = createNode(val); if (head == NULL) { head = node; } else { node->next = head; head->prev = node; head = node; } return head; } // 插入节点到双向链表尾部 ListNode *insertAtTail(ListNode *head, int val) { ListNode *node = createNode(val); if (head == NULL) { head = node; } else { ListNode *cur = head; while (cur->next != NULL) { cur = cur->next; } cur->next = node; node->prev = cur; } return head; } // 删除双向链表中的节点 ListNode *deleteNode(ListNode *head, int val) { if (head == NULL) { return NULL; } ListNode *cur = head; while (cur != NULL && cur->val != val) { cur = cur->next; } if (cur == NULL) { return head; } if (cur == head) { head = head->next; if (head != NULL) { head->prev = NULL; } } else { cur->prev->next = cur->next; if (cur->next != NULL) { cur->next->prev = cur->prev; } } free(cur); return head; } // 打印双向链表 void printList(ListNode *head) { while (head != NULL) { printf("%d ", head->val); head = head->next; } printf("\n"); } // 主函数 int main() { ListNode *head = NULL; head = insertAtHead(head, 1); head = insertAtHead(head, 2); head = insertAtTail(head, 3); printList(head); // 2 1 3 head = deleteNode(head, 2); printList(head); // 1 3 head = deleteNode(head, 3); printList(head); // 1 head = deleteNode(head, 1); printList(head); // return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值