此文是笔者第一次写博客,多有疏忽,尽情谅解,如有问题,也希望大佬能多和我交流。
提示:如果想直接了解链表,请直接跳到第二点,第一点只是讲了一点笔者认为数据结构的初学者可能需要简单了解的地方。
一、数据结构的简单理解
作为数据结构的初学者,笔者一开始只知道数组,所以让我们从数组出发,初步了解数据结构是什么。
一个C++的数组,可以存储与用户定义的数组类型相同类型数据项的变量。但是,如果我们想要存储不同类型的数据项该怎么办呢?
所以,结构体应运而生,结构体struct是C++中的一种用户自定义的可用数据类型,它方便了用户存储不同类型的数据项。
简单来说,用户自定义的结构体即是一个全新的数据类型,用于表示一条记录。比方说,身份证上拥有我们的个人信息,当定义一个新身份证时,我们可能需要跟踪多个属性:
name 个人名字
sex 性别
number 身份证号
那么此时可以定义一个结构体struct:
struct People{
string name;
string sex;
int number;
People(string name,string sex,int number)
:(string name,string sex,int number){}//构造函数
};
如果您简单学过C++,您可将结构体视为只有构造函数以及所有属性均为public权限的简单类。
二、链表是什么?
与数组不同,链表是非连续、非顺序的存储结构。与数组相同的是,它也由一些节点组成,但由于链表不是连续、顺序的存储结构,因此每一个链表节点都有两个部分组成,一个是数据域,一个是指针域。
为了简单易懂起见,我们先讲C++的STL库中已经定义好的单链表。STL库中链表定义方式如下:
struct ListNode{
int val;//节点存储元素
ListNode* next;//指向下一个节点的指针
ListNode(int val=0,int next=nullptr):val(val),next(next){}//构造函数
};
从上述代码可以看出,单链表有两个部分组成,一个是数据域(存储节点的数据),一个是指针域(存放指向下一个节点的指针),链表的最后一个节点的指针指向nullptr(NULL也可,都是空指针的意思)。
注:NULL和nullptr本质还是有区别的,详情可以查看:C++中NULL和nullptr的区别
一个简单单链表如下图所示:
链表的头结点我们一般称为head,尾结点一般称为tail(尾结点在双向链表中常用到)。
三、链表类型
1.单链表
上面刚讲的就是单链表
2.双向链表
顾名思义,相比于单链表,双向链表多了一个指向前一个节点的指针prev。一般双向链表都需要用户自己定义。
3.循环链表
顾名思义,单链表的尾结点tail->next=NULL,而循环链表的尾结点tail->next=head; 即循环链表首尾相连。
四、链表操作
1.链表遍历
对链表进行删除、查找等操作,本质上就是对链表进行遍历,在遍历过程中,进行不同的操作。
遍历链表并输出数据代码如下:
ListNode* cur=new ListNode(-1,head);
//在做算法题时,常设置虚拟头结点,以便对链表所有结点一视同仁以便操作。
while(cur->next)
{
cur=cur->next;
//当符合一定条件时,遍历的过程中进行特殊操作,如删除等;
cout<<cur->val<<endl;
}
2.链表删除操作
假设我们要删除链表中的cur节点,如下图所示
要删除cur,我们只需要找到cur节点的前一个节点prev,和cur的下一个节点Next,然后将prev的next指针指向Next,即prev->next=Next。这样链表在之后的使用过程中,将不再遍历cur节点,相当于将cur节点删除出链表。
一般来说,在删除节点的过程中,如果链表头结点head,也可能被删除的话,为了方便,我们就得人为定义一个head结点的前一个节点,也就是虚拟头结点dummy,这样链表中所有的结点均可一视同仁,进行相同的操作。
上述两种删除操作涉及两种链表的操作方式:
1. 直接对链表进行操作;
2. 设计虚拟头结点(哑结点)dummy=new ListNode(-1,head)再对链表进行操作;
方法1详情可看Leetcode 83.删除链表重复元素
方法2详情可看Leetcode 剑指offer 18.删除链表的节点
3.反转链表
反转链表有两种简单的方法,详情可看LeetCode 206.反转链表
总结
本文只是简单的介绍了一下链表,由于笔者仍在学习数据结构与算法,有关链表的具体操作,以及经典Leetcode例题的题解,笔者会一点一点总结,并不断完善。