想入门数据结构,却总是拜倒在链表的石榴裙下?

大家好,我是melo,一名大二上软件工程在读生,经历了一年的摸滚,现在已经在工作室里边准备开发后台项目啦
不过这篇文章呢,还是想跟大家聊一聊数据结构与算法,学校也是大二上才开设了数据结构这门课,希望可以一边学习数据结构一边积累后台项目开发经验

写在前边

相信很多小猿人在初入数据结构的时候,或者说是在学习c语言的后期时分,总会遇到一个馋(缠)人的绕来绕去的家伙–就是我们今天要讲的链表。为什么说链表缠人呢,链表的分类,题型的多样性,链表的用途等等都是很多的,限于篇幅本篇着重来讨论一下学习链表以及做题时的思路概览,注意点以及有哪些 小技巧
另外,本博客题解一般会使用c(学校课内要求)和java(个人后台方向需要熟练应用)两种语言

ps:本博客旨在提供一些学习和做题时的小技巧,推荐搭配数据结构算法书籍一同学习和补充知识点
入门的话比较推荐的有《啊哈!算法》,《大话数据结构》,这两本书都偏向图文式教学,也有很多可可爱爱的插画

想入门数据结构,却总是拜倒在链表的石榴裙下?

小哈确实好可爱啊哈哈哈

  • 关于链表的定义,优点相信你们也都在书中看了蛮多了,这里就不多加阐述,我们直接来看形形色色的各种链表,看他们的区别和实现

链表的分类

我们先来看看最简单的单向链表

  • 链表是有序的列表,但是它在内存中是这样存储的

想入门数据结构,却总是拜倒在链表的石榴裙下?
1)链表是以节点的方式来存储,是链式存储
2)每个节点包含data域,next域:指向下一个节点.

3)如图:发现链表的各个节点不一定是连续存储.

4)链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定

带头结点逻辑结构示意图如下

head中不存储实际的数据data,只是用next来指向真正带有数据的第一个节点

想入门数据结构,却总是拜倒在链表的石榴裙下?

不带头结点

第一个节点即存储有实际数据data和next的实际节点

何时要头结点何时不需要呢

  • 有无头结点的区别在于,链表的第一个节点是否存储值。那我们可以先大概下个定义:如果说我们的操作 会影响到第一个节点的(比如说删除链表中的某个值),那是不是我们尽量就用 带头结点的会好一点

例如:删除结点时,如果我们不带头结点的话,如果删除的是第一个实际节点,那我们还需要去更改头结点,让头结点指向第二个实际节点(因为第一个实际节点已经被删除了,自然就不是头结点了)
而带有头结点的话,恰好就能解决这种需要特判的情况

这个区别,还需要我们慢慢去体会,在下文讲到链表删除的时候自然而然就会更加深刻体会到了,这里只是先点一下,而到了后边环形链表的时候,更多的是没带头结点的版本

Typedef(偷懒神器)

  • PNODE就等价于 struct Node* 了!

想入门数据结构,却总是拜倒在链表的石榴裙下?
想入门数据结构,却总是拜倒在链表的石榴裙下?

LinkList就直接等价于struct LNode*,可以省去很多书写,下文中也会体现到

创建链表

思路概览

  • 我们需要两个结点,一个指向当前节点,一个指向我们新创建的节点,每次创建新节点后,让当前节点指向新节点,并移动当前节点到新节点上

记得最后跳出循环的时候,要让当前节点指向null来结束链表!

流程

链表节点定义(typedef妙用,后续可以省去struct)

#include
#include

typedef struct ListNode {
    int val;
    struct ListNode* next;
};

注意c语言需要修改头文件中的cstido为stdio.h

注意让当前pNow=head

可以省去特判i=0

//根据读取的元素创建链表
ListNode* creatLinkedList() {
    ListNode* head = (ListNode*)malloc(sizeof(ListNode));
    ListNode* pNow = head;
    int n,temp;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        ListNode* pNew = (ListNode*)malloc(sizeof(ListNode));
        scanf("%d", &(pNew->val));
        //若是第一个
        /*if (i == 0) {
            head->next = pNew;
        }*/
        //将当前节点指向新节点,并移动当前节点到新节点上
        pNow->next = pNew;
        pNow = pNew;
    }
    //跳出循环后,使得当前节点指向null终止链表
    pNow->next = NULL;
    return head;
}

特判第一个多一步 head->next=pNew

其实可以不用,在不带头结点的链表创建中,才需要去特判头结点

记住最后跳出循环让pNow->next=null

遍历链表

  • 注意让p=head->next,然后遍历p(p=p->next)判断 p !=null 即可
//遍历链表
void traverseLinkedList(ListNode* head) {
    for (ListNode* p = head->next; p != NULL; p=p->next) {
        printf("%d ", p->val);
    }
}

链表插入

在指定位置插入

想入门数据结构,却总是拜倒在链表的石榴裙下?

思路概览

  • 首先我们第一感觉就是邀先移动到指定位置的前一位p,然后去新开辟一个节点pNew,让p指向pNew,pNew指向原来p的下一节点

问题

  • 如果我们原原本本按照思路来,有没有发现,其实让pNew指向原来p的下一节点,但是此时p的下一节点是什么呢,其实已经是pNew了

所以我们应该先用另一个指针,存储一下原来p都下一个节点
或者说注意一下顺序,先让pNew的next=p的next
然后再让p都next=pNew

注意

**校验合法性

  • 用while 和if 两个条件刚好相反 可以 *省去获取链表长度的操作
//链表插入(三种情况都综合成一种)
bool insertLinkedList(ListNode* head, int position, int data) {
    int i = 0;
    ListNode* p = head;
    ListNode* pNew = (ListNode*)malloc(sizeof(ListNode));
    //移动p直到目标位置的前一位,条件是p不为null
    while (i < position - 1 && p!=NULL) {
        i++;
        p = p->next;
    }
    //若移动不到,返回false
    if (i != position - 1 || p==NULL) {
        return false;
    }
    pNew->val = data;
    //注意顺序
    //先让新节点指向上一个节点的下一节点
    //然后再让上一个节点指向新节点
    pNew->next = p->next;
    p->next = pNew;
    return true;
}

链表删除

(力扣)203移除链表元素(删除所有满足指定值的节点)

思路概览

  • 我们需要定义两个指针,一个last记录前一个节点,一个记录当前节点now,怎么删除呢?让 last(图中的p)->next=now(图中的q)->next就可以了

想入门数据结构,却总是拜倒在链表的石榴裙下?

***递归还未看

https://leetcode-cn.com/problems/remove-linked-list-elements/

想入门数据结构,却总是拜倒在链表的石榴裙下?

特判头指针法

struct ListNode* removeElements(struct ListNode* head, int val){
    //前指针
    struct ListNode* last=head;
    //当前指针
    struct ListNode* now=head;
    //now肯定要移动,而last就不一定要移动了!!!(此处注意)
    for(;now!=NULL;now=now->next){
        if(now->val==val){
            //特判删除头节点
            if(now==head){
                head=now->next;
            }
            //一般情况
            else{
                last->next=now->next;
            }
        }
        //不删,则移动last,若删了就不用移动!!!

        else{
            last=now;
        }
    }
    return head;
}

注意last需不需要移动!!!,且last默认先给头节点

虚拟头结点法(注意最后return)

struct ListNode_ dummyHead= (struct ListNode_)malloc(sizeof(struct ListNode));

struct ListNode* removeElements(struct ListNode* head, int val){
    //创建虚拟头节点,并指向第一个节点
struct ListNode* dummyHead= (struct ListNode*)malloc(sizeof(struct ListNode));
    dummyHead->next=head;
    //前指针
    struct ListNode* last=dummyHead;
    //当前指针
    struct ListNode* now=head;
    //now肯定要移动,而last就不一定要移动了!!!(此处注意)
    for(;now!=NULL;now=now->next){
        if(now->val==val){
            //特判删除头节点
            last->next=now->next;
        }
        //不删,则移动last,若删了就不用移动!!!

        else{
            last=now;
        }
    }
    return dummyHead->next;
}

(最优)while+虚拟头

delete temp是c++中的,此处java会自动回收无需释放空间

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        //创建虚拟头结点
        ListNode dummyHead = new ListNode(0);
        //让虚拟头结点指向给定的head结点
        dummyHead.next = head;
        //定义一个临时结点来遍历,默认从虚拟头结点开始
        ListNode temp = dummyHead;
        while (temp.next != null) {
            if (temp.next.val == val) {
                //找到了则删除
                temp.next = temp.next.next;
            } else {
                //找不到继续移动
                temp = temp.next;
            }
        }
        return dummyHead.next;
    }
}

**移除链表元素(指定位置)

题目

想入门数据结构,却总是拜倒在链表的石榴裙下?

题解

#include "allinclude.h"  //DO NOT edit this line
Status Delete_L(LinkList L, int i, ElemType &e)
{   // Add your code here
  LNode* p = L;
  int j=0;
  //注意跟新增一个节点不一样,此处得p->next!=NULL
  while(jnext!=NULL){
    p=p->next;
    j++;
  }
  //若参数不合法
  if(j!=i-1||p->next==NULL){
    return ERROR;
  }
  e= (p->next)->data;
  //LNode* temp = p->next;
  p->next = p->next->next;
  //free(temp);
  return OK;

}

**注意

  • 跟新增一个节点不一样,此处得p->next!=NULL,而不是p!=NULL

若是p!=NULL的话,下边我们又要保存p->next, 可能就非法访问了!

*从第i元素起的所有元素从链表移除

题目

想入门数据结构,却总是拜倒在链表的石榴裙下?

还记得上文的typedef吗,此处定义了一个LinkList就等价于LNode
相当于Typedef struct LNode LinkList

题解

#include "allinclude.h"  //DO NOT edit this line
Status Split_L(LinkList L, LinkList &Li, int i)
{   // Add your code here
  int cnt=0;
  LNode* p = L ;
  LinkList temp;
  //移动到前一位
  while(cntnext!=NULL){
    p=p->next;
    cnt++;
  }
  //校验参数合法性
  if(cnt!=i-1||p->next==NULL){
    Li=NULL;
    return ERROR;
  }
  temp=(LinkList)malloc(sizeof(LNode));
  if(temp==NULL) return ERROR;
  Li=temp;
  Li->next=p->next;
  //销毁i元素后
  p->next=NULL;
  return OK;
}

链表反转

(力扣)206反转链表

https://leetcode-cn.com/problems/reverse-linked-list/

想入门数据结构,却总是拜倒在链表的石榴裙下?

思路概览

  • 让后一个指向前一个,同时要记得要用另外一个指针 保存下一个节点位置用于正常遍历,总共需要三个指针

官方题解(迭代+三指针)

c语言

/**
 * Definition for singly-linked list.

 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* prev = NULL;
    struct ListNode* curr = head;
    while (curr) {
        //先记录curr的下一位,后续才能修改curr的下一位(总结就是要修改哪个值,就先保存下来)
        struct ListNode* next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}

java版

区别就在于java没有指针而是引用,直接ListNode就好,没有那个*(写法方便一些)

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}

要改变什么值,就先用一个临时值来存储

链表反转中,要改变next,又怕影响 正常的遍历,所以先用一个临时指针来存储就好了,这样就还能正常的遍历下去

合并两个有序链表

(力扣)21合并两个有序链表

https://leetcode-cn.com/problems/merge-two-sorted-lists/

想入门数据结构,却总是拜倒在链表的石榴裙下?

思路

  • 同时遍历两个链表l1和l2,直到有一个为null
  • 每次遍历时,判断是哪个比较小,然后加到新链表中,移动l1指针

直接if单独比较版本

一次只能一个,但是就不用嵌套while(while还要再次判断是不是空)

struct ListNode* mergeTwoLists2(struct ListNode* l1, struct ListNode* l2) {
    struct ListNode* drummyHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* Node = drummyHead;
    while (l1 != NULL && l2 != NULL) {
        if( l1->val val) {
            Node->next = l1;
            l1 = l1->next;
        }
        else{
            Node->next = l2;
            l2 = l2->next;
        }
        Node = Node->next;
    }
    //最后出循环必有一个为null(而且不会同时为null)则让非空的剩余部分连上去即可
    Node->next = l1 == NULL ? l2 : l1;
    /*ListNode* p = drummyHead->next;
    while (p) {
        printf("%d", p->val);
        p = p->next;
    }*/
    return drummyHead->next;
}

while中嵌套while版本

注意

内部while可能会破坏外部大while的条件,需要再次判断

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
    struct ListNode* drummyHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* Node = drummyHead;
    while (l1 != NULL && l2 != NULL) {
        while (l1 != NULL && l2 != NULL && l1->val val) {
            Node->next = l1;
            Node = Node->next;
            l1 = l1->next;
        }
        //可能l1已经为NULL了,所以也还得判断l1
        while (l2 != NULL && l1 != NULL && l2->val val) {
            Node->next = l2;
            Node = Node->next;
            l2 = l2->next;
        }
    }
    //最后出循环必有一个为null(而且不会同时为null)则让非空的剩余部分连上去即可
    Node->next = l1 == NULL ? l2 : l1;
    /*ListNode* p = drummyHead->next;
    while (p) {
        printf("%d", p->val);
        p = p->next;
    }*/
    return drummyHead->next;
}

总结

巧用typedef偷懒神器

有时候题目给的是不带头结点的,尽量自己去构造一个虚拟头指针,可以省去一些特判的操作

要改变什么值,就先用一个临时值来存储

链表反转中,要改变next,又怕影响 正常的遍历,所以先用一个临时指针来存储就好了,这样就还能正常的遍历下去

最后

  • 有关其他链表知识,环形链表,约瑟夫等经典问题,melo最近比较忙,忙于学习 多线程知识接管一下项目,等到以后学校课程(小声bb学校刚讲到创建和遍历链表)跟进了再后续补充,后续也会更新一些多线程的知识以及设计模式等内容,希望大家多多体谅和包涵

最近学校讲到栈和队列了,过段时间应该也会继续更进完善栈和队列相关的博客

Original: https://www.cnblogs.com/melojun/p/15246017.html
Author: Melo~
Title: 想入门数据结构,却总是拜倒在链表的石榴裙下?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/572234/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • [Java/Jdbc]ResultSet对象的setFetchSize对于大批量数据读取的显著提速作用

    在笔者一段程序中有这样的代码: 这段代码及配套代码处理1300万行含21字段10字段需脱敏的表需要约20分。 当加入rs.setFetchSize(10000)后代码是这样: 只加…

    Java 2023年5月29日
    071
  • Delphi线程简介—Create及其参数、Resume、Suspend和Terminate(转载)

    原文地址: TThread在Classes单元里的声明如下 先说一下TThread的Create的参数 当TThread的Create()被调用的时候,需要传递一个布尔型的参数Cr…

    Java 2023年5月29日
    059
  • 基于Redis&MySQL接口幂等性设计

    基于Redis&MySQL接口幂等性设计 欲把相思说似谁,浅情人不知。 幂等性即多次调用接口或方法不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。 前端重复提…

    Java 2023年6月5日
    055
  • Java(15)Object类

    Object类是Java中所有类的始祖,在Java中每个类都扩展了Object。如果没有明确地指出超类,Object就被认为是这个类的超类。由于在Java中每个类都是由Object…

    Java 2023年6月9日
    052
  • quartz框架(六)-ThreadPool

    本篇博文,博主将介绍Quartz框架中ThreadPool线程池相关的内容。线程池顾名思义,就是一个可以帮助我们来进行线程资源管理的对象。在web开发中,常见的就有数据库连接池,h…

    Java 2023年6月7日
    070
  • 日志导致线程Block的这些坑

    日志导致线程Block的这些坑,你不得不防 https://mp.weixin.qq.com/s/nowNpIOHBFHD0pctcKr2UA Original: https://…

    Java 2023年5月30日
    080
  • JDBC基础学习

    JDBC JDBC(Java DataBase Connectivity)是Java和数据库之间的一个桥梁,是一个规范而不是一个实现,能够执行SQL语句。它由一组用Java语言编写…

    Java 2023年6月16日
    058
  • ArrayList源码

    ArrayList源码 目录 一、ArrayList 1.1 包含的属性 1.2 源码分析 1.2.1 add源码分析 1.2.2 grow源码 一、ArrayList Array…

    Java 2023年6月5日
    066
  • 运算符(2)

    运算符 算术运算符补充 +的作用: 表示正数(省略不写) 表示相加操作 进行字符串的拼接:+号左右两侧的任意一侧有字符串,那么这个加号就是字符串拼接的作用,结果一定是字符串 【2】…

    Java 2023年6月5日
    070
  • Java 知识积累方便以后随时查看

    一、Java数据类型 8种基本数据类型:字符型char,布尔型boolean,数值型(整型和浮点型) 其中整型包括(byte,short,int,long),浮点型(float,d…

    Java 2023年5月29日
    055
  • 分享实用小工具:JAVA版本位运算工具类

    将二进制数中的每位数字1或0代表着某种开关标记,1为是,0为否,则一个数字可以代表N位的开关标记值,可有效减少过多的变量定义 或 过多的表字段,同时也能在一些复杂的组合判断场景下利…

    Java 2023年5月29日
    058
  • 类,对象,局部变量和成员变量

    package com.gao.test.Test1; import java.beans.FeatureDescriptor; /** * @Auther:gao * 创建类:人…

    Java 2023年6月5日
    075
  • WIN DLL劫持提权

    WIN DLL劫持提权 原理: Windows程序启动的时候需要DLL。如果这些DLL 不存在,则可以通过在应用程序要查找的位置放置恶意DLL来提权。通常,Windows应用程序有…

    Java 2023年6月6日
    082
  • 字节码&ASM-基础

    在接触ASM之前不得不去了解一些字节码相关的前置知识,掌握字节码的类型描述、JVM代码执行模型是开启ASM大门的钥匙,掌握这把钥匙开启大门才能走上一条康庄大道通往ASM,否则一路摸…

    Java 2023年6月7日
    055
  • 循序渐进nginx(一):介绍、安装、hello world、Location匹配

    前言: Nginx是什么 使用场景: 官方文档说明 安装 windows下: linux(CentOS7)下: docker下: 目录结构 Hello World 1.展示一下默认…

    Java 2023年5月30日
    061
  • RabbitMQ 之 HelloWorld(生产者-消费者)

    RabbitMQ 之 HelloWorld(生产者-消费者) 安装rabbitmq环境 见上一篇文章rabbitmq安装 pom.xml 4.0.0 com.atguigu.rab…

    Java 2023年6月9日
    061
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球