浅析那些初学者头疼的数据结构——链表

浅析那些另初学者头疼的数据结构——链表

前情摘要:

本文主要介绍基础数据结构链表的基本概念以及与链表相关的一些简单的操作,如链表的反转,合并两个有序链表,求链表的中间节点,删除链表的倒数第K个节点。

对读者的要求:

1、不要看到本文写的是链表,就觉得很难,就直接退出,花一点点时间,不敢说让你上来就搞懂链表,但至少给你一种思路,就像当初在黑暗中的我看到那微弱的亮光,因为我就是从初学者刚刚过来的,所以还记得初学者那些奇奇怪怪的问题。

2、本文基于java语言的链表,但是其实无论哪一种语言的链表都是大同小异的,只有细枝末节上的一点差别,如C语言中用的是“指针”,像java这种面向对象的语言用的是“引用”。

3、不希望读者在本文中一遍过,不要走马观花,要用心体会一下,即使我文章写的不怎么好。

链表的基本介绍:

链表是一种最基础的数据结构,首先不要把链表看的那么难,而是就把链表当作最普通的数组那样看待。链表总共包含两部分内容,一部分就是本身存储的数据,另一部分就是指向下一个节点的“引用”,默认情况下不经过赋值的情况下,该节点指向null。

public static Node
{
	String val;
	Node next;
}//定义了一个链表的嵌套类,嵌套就是Node类里面定义了另一个Node类。

废话不多说,直接上图,纯属手绘,见谅:
在这里插入图片描述

上图其实就是最简单的链表,总共有四个节点,每个节点分别命名为first,second,third,forth。

第一个节点指向第二个节点,第二个节点指向第三个,第三个节点指向第四个,第四个节点指向null。

first.val = "to",first.next = second;
first.next.val = "be",first.next.next = third;

以上是对链表的基本应用方法,用节点的名字点上一个val或者next。第一个节点的内容是first.val = “to”,first.next = next;其他节点也同理。

至此,我们已经理解了链表的最基本的组成,以及节点元素的访问方法。接下来让我们更加深入一点。

链表的基本操作之增删改查:

现在让我们从头开始用代码实打实的构造出一条链表来完成小题目中的基本操作。

1、定义节点:

Node first = new Node();
Node second = new Node();
Node third = new Node();

现在我们已经有了三个空的节点,接下来的操作就是赋值和穿针引线,也就是将每个节点连接起来。

2、为节点的val变量赋值:

first.val = "to";
second.val = "be";
third.val = "or";

现在我们已经有了三个节点,况且每个节点的val变量都有了相应的值。

3、连接链表:

first.next = second;
second.next = third;//读者可能会好奇为什么third不用只想谁,因为默认都为null。

这样我们就有了一条完整的链表可以进行我们想做的基本操作了。
在这里插入图片描述

4、基本操作:
4.1:在表头插入链表:

首先我们应该先申请一个Node类oldfirst,将first保存到oldfirst中,然后申请一个新的first节点,再将该节点指向oldfirst。

Node oldfirst = first;

在这里插入图片描述

此时oldfirst和first都指向第一个节点,然后我们重新申请一个first节点,那么原来的first节点就已经消失了。就只剩oldfirst指向第一个节点了。

接下来我们申请一个first节点并赋值。

Node first = new Node();
first.val = "not";

此时我们的链表是这样的:

在这里插入图片描述

然后在将新的first节点指向oldfirst节点就行了。

first.next = oldfirst;

在这里插入图片描述

好了,到现在为止我们已经完成所有的在表头插入链表的节点这一操作了。

这样画图太伤肝,也太啰嗦了,使文章整体显得没有气质,下面我会在必要的时候画图,简单的地方读者们可以自行想象。

4.2、在表头删除节点:
first = first.next;//就是这么简单,也就是将第二个节点second直接赋值给first,你说我还有必要画图吗?
4.3、在表尾插入节点:
Node oldlast = last;//保存老的最后一个节点。
Node last = new Node();//申请需要在表尾插入的节点。
last.val = "not";//为新节点赋值。
oldlast.next = last;//将老节点指向新的节点。

修改链表的话就更简单了,直接定位到你想修改的节点,然后就直接改就行了。比如修改first节点的内容:

first.val = "lalalala";//修改完毕。

到现在为止我们已经学完了链表最基本的构造,以及最基本的操作增删改查。下面让我们更加深入一些吧。

提高篇:链表相关的习题
链表的反转(LeetCode206)

在这里插入图片描述

因为是初学,我们就先不讲递归了,我们先将简单的迭代。

整体思路:我们用两个头结点的引用,cur和pre,cur用来储存当前节点,pre用来储存cur前面的节点,然后将cur.next指向pre。然后将cur和pre整体向后移一位,也就是pre = cur,cur = cur.next如此循环往复,直到cur为null,pre也就指向了最后一个节点,但因为链表已经反转,pre就是反转链表的头节点。代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
 //上面的代码是LeetCode中定义的Node类。
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = head;
        ListNode pre = null;
        ListNode tmp = null;//tmp用来储存临时变量cur.next,因为如果我们不记录下来,如果我们将cur.next指向pre,cur据没有办法按照原来的链表往后移位了。
        while(cur!=null){
            tmp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
}

代码分析:

我们在反转之前定义了如下变量:

在这里插入图片描述

执行完下面的语句之后:

tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;

在这里插入图片描述

经过数次循环,直到:在这里插入图片描述

然后我们就得到了以pre为头节点的反转之后的链表(肝疼)。

合并两个有序链表(LeetCode#21):
在这里插入图片描述

整体思路:我们现在已经有了两个有序链表,然后题目要求我们合并他们,我们需要一根针,然后将两个链表中的每个元素两两比较,将针穿到val较小的那一位上,然后整体往后移一位,继续比较。这里我们用到了在链表中将常用到方法,定义一个哨兵,这样我们就可以不用处理头节点了。

代码实现:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode cur = dummy;
        while(l1!=null&&l2!=null){
            if(l1.val<=l2.val){
                cur.next = l1;
                l1 = l1.next;
            }else{
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        cur.next=l1== null? l2:l1;
        return dummy.next;
    }
}

代码分析:

定义完哨兵语句之后我们的链表图是这样的:

在这里插入图片描述

然后比较l1.val和l2.val,cur.next指向数值较小或相等的那一位。指向谁以后谁就向后移动一位,然后将cur也指向现在原来cur.next指向的那一位,因为cur是针,必须向后走。

在这里插入图片描述

经过多次循环,然后就变成现在的样子了。

在这里插入图片描述

此时有可能会剩下一个节点或者一个不剩,如果两条链表的总结点为奇数就不会剩下,为偶数就会剩下一个节点,考虑到这一点,我们直接将cur.next指向剩余的那个节点就行了。

号外:cur.next=l1== null? l2:l1 的用法判断式子?true:false其实就是一个if和else,不用多想。等价于:

if(l1==null){
	cur.next = l2;
}else{
	cur,next = l1;
}

然后我们整体合并两个有序链表就完成了,说一下那个返回值,为什么返回dummy.next。因为我们自己定义了一个哨兵节点,如果返回dummy将会连头节点也带上,所以返回dummy.next。

求链表的中间节点(LeetCode#876):

在这里插入图片描述

整体思路:我们先用头结点的引用,循环遍历整个节点,然后得出整个链表的节点个数,然后用count计数,让count/2就得到了中间的节点,然后循环遍历到此中间节点。

代码实现:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode fir = head;
        ListNode mid = head;
        int count = 0;
        while(fir!=null){
            fir = fir.next;
            count++;
        }
        count=count/2;
        while(count!=0){
            mid = mid.next;
            count--;
        }
        return mid;
    }
}

**代码分析:**整体代码挺简单的,没有什么可以分析的。

总结:

到这里我们的链表基本上就完成了,当然了我只是说了冰山一角,更多的内容等待着亲爱的你们去发掘,其实链表不是很难,我刚开始学的时候总是觉得代码难得要死,其实经历过来了也就是这么回事,很简单。

掌握链表或者说学习的最好方法就是多学多练,当你直到了链表的基本之后,就去做题吧,把那几道关于链表的简单题弄明白,上午做一遍,下午做一遍,奇迹就会出现,我亲身经历过的。

本人良弓,初来乍到,请多关照。~

如果觉得我的文章对你们有帮助,希望你们可以点赞加评论,只有评论我,我才知道自己的不足,我才会去改正,这样我们就可以一起进步。**希望看到这篇文章的人都能够评论和点赞。**另外我也是初学者,暑假一直在学习数据结构和算法。希望有小伙伴加我qq,一起学习,一起进步。加油。本人qq:2268714417

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值