链表学习与应用

这篇博客介绍了链表作为数据结构的基础概念,包括链表的定义、单链表、循环链表和双向链表的实现。通过小王子排列玩具的例子,展示了链表在动态调整元素顺序上的高效性,对比了数组操作的劣势。此外,还探讨了链表在约瑟夫环问题中的应用,说明了链表在解决这类问题上的优势。博客还提供了Java代码示例,演示了如何使用链表进行插入、删除等操作。
摘要由CSDN通过智能技术生成

链表是线性表的链式存取的数据结构,是一种链式存取的数据结构,是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:数据域(数据元素的映象)+ 指针域(指示后继元素存储位置),数据域就是存储数据的存储单元,指针域就是连接每个结点的地址数据。 相比于线性表顺序结构,操作复杂。

 

线性表的数据存储方式的内存地址是顺序的,而链式存储的内存地址的是随机分配的,他们每个节点地址之间是没有任何关联的。而且在每个新的节点在产生之前,我们都是不知道他的地址的。

 小王子的链表

小王子有一天迷上了排队的游戏,桌子上有标号为 1-10的 10 个玩具,现在小王子将他们排成一列,可小王子还是太小了,他不确定他到底想把那个玩具摆在哪里,直到最后才能排成一条直线,求玩具的编号。已知他排了 M 次,每次都是选取标号为 X 个放到最前面,求每次排完后玩具的编号序列。

要求一:采用单链表解决

输入描述
第一行是一个整数 M,表示小王子排玩具的次数。

随后 M 行每行包含一个整数 X,表示小王子要把编号为 X 的玩具放在最前面。

输出描述
共 M 行,第 i 行输出小王子第 i 次排完序后玩具的编号序列。

输入输出样例
示例 1
输入

5
3
2
3
4
2

输出

3 1 2 4 5 6 7 8 9 10
2 3 1 4 5 6 7 8 9 10
3 2 1 4 5 6 7 8 9 10
4 3 2 1 5 6 7 8 9 10
2 4 3 1 5 6 7 8 9 10

运行限制
最大运行时间:1s
最大运行内存: 128M

如果用数组来实现,比较耗费时间。我们需要 先查到目标,再把目标前的数依次后移,最后把目标挪至前方。

 假如目标为9,需要查找 9 ,循环了 9 次 ,移动花费了 9 次 此时序列为 9,1,2,3,4,5,6,7,8,10     假如目标为3,需要查找 3 ,循环了 4 次 ,移动花费了 4 次 此时序列为 3,9,1,2,4,5,6,7,8,10

如果使用链表,以x=9为例:

        照例查找目标,找到后直接删除(给 9 前面结点的指针赋值为 9 的指针,再将 9 删除。)再执行插入操作:新建一个结点, 数据域部分为 9 ,将结点插入链表的首部

相比之下链表要快很多

使用单链表实现:

首先要实现一个头节点有头有尾:

 创建一个新的节点(x),将其next指向null,head.next指向x。

一定要先将x.next指向null,不能将head.next直接指向x,不然x.next指向的还是一个随机数(随即地址)

同理创建一个单链表:

 接着插入、抽离(删):

        插入:(以插入最前处为例)

         

 把head.nextd的值赋给temp.next,并把temp赋给head.next。

        抽离(删):

 其实删除的不是某一个节点,而是该节点的前驱节点指向它的指针以及它指向它的后驱节点的指针,从而让存放该内容的地址找不到。

因为单链表是单向遍历的,我们无法从下一个找到上一个,所以需要事先给该节点的前驱一个存放位置。

 完整代码如下:

import java.util.Scanner;

public class Main {
    static class Node {
        int data;
        Node next;

        Node(int v) {
            data = v;
        }
    }//成员类,代表节点

    static Node head = new Node(1);//头节点单列出来

    static void init() {
        Node x = head;
        for (int i = 1; i <= 10; i++) {
            x = (x.next = new Node(i));//建立单向链表
        }
        x.next = null;
    }

    static   void del(int x) {

        Node Befor = head;   //用于存放当前节点的前驱,因为单链表单向遍历,我们不能从下一个找到上一个
        for (Node T = head.next; T != null; T = T.next){ //链表的遍历常用写法
            if (T.data == x){ //找到要的那个数了
                Befor.next = T.next; //将节点从链表上摘除

                return; //删除结束后,结束函数。
            }
            Befor = T; //前驱改变
        }
    }

    static void insert(int x) {
        Node temp = new Node(x);

        temp.next = head.next;
        head.next = temp;
    }

    static void show(int i) {

        for (Node T = head.next; T != null; T = T.next) //链表的遍历常用写法
        {
            System.out.print(T.data + " ");
        }
        System.out.println(" ");
    }

    public static void main(String[] args) {
        int N;
        Scanner in = new Scanner(System.in);
        init();
        N = in.nextInt();

        for (int i = 0; i < N; i++) {
            int x = in.nextInt();
            del(x);
            insert(x);
            show(i);
        }
    }
}

使用循环链表解决约瑟夫环问题

将单链表或者双链表的头尾结点链接起来,就是一个循环链表。能访问到表中其他结点。

循环链表的组成:

 特点:

  • 首尾相接的链表。
  • 可以从任一节点出发,访问链表中的所有节点。
  • 判断循环链表中尾结点的特点:q.next==first

可以发现,循环链表与单链表的差别就是最后的指针一个为空一个与 First 相等,其他的都没有什么变化,也就是多了一个循环遍历的过程。

约瑟夫环问题

设有 n 个人围坐在圆桌周围,现从某个位置 k(1≤k≤n) 上的人开始报数,报数到 m 的人就站出来。下一个人,即原来的第 m+1 个位置上的人,又从 1 开始报数,再报数到 m 的人站出来。依次重复下去,直到全部的人都站出来为止。试设计一个程序求出这 n 个人的出列顺序。

 要求一:采用循环链表解决
要求二:可以使用模拟法,模拟循环链表
要求三:可以不使用循环链表类的定义使用方式

输入描述

输入只有一行且为用空格隔开的三个正整数n,k,m,其含义如上所述

输出描述

共n行,表示这n个人的出列顺序

题意:n个人,k位置开始报数,报到m出列;

 (大致过程)

 所以题目的关键在于找出起始位置k;出列位置m。

先定义节点类,以及此题需要获取从键盘输入的:n,k,m。

形成循环链表:同上述单链表差不多,只需将 x.next=null 改成 x,next=t (此时的t的值是头节点)

以此确保可以形成循环链表。

找起始点k:

循环结束后,x是起始节点

出列位置m:

依据题意,出队操作在链表只剩一个节点的时候应该停止。

(只剩一个节点时,其next指向自己)=> x==x.next

​​​​​​​

 可以发现k,m的for循环中方法体都是 x = x.next;都是k的最终得到的x就是第k个,而m的x是第m-1个。因为:k中的x一开始是头节点,而m中的x一开始是k。

完整代码如下:

import java.util.Scanner;
 
public class Main
{
    static class Node
    {
        int val;
        Node next;
        Node(int v)
        {
            val = v;
        }
    } //成员类,代表节点,类似于C++语言中的结构体
 
    public static void main(String[] args)
    {
 
        int N, M, K; //n个人从k位置开始报数,数到m出列
 
        Scanner input = new Scanner(System.in);
 
        N = input.nextInt();
 
        K = input.nextInt();
        M = input.nextInt();
        
     
        Node t = new Node(1); //头节点单列出来,方便形成循环链表
        Node x = t;
 
        for (int i = 2; i <= N; i++){
            x = (x.next = new Node(i)); //建立单向链表
        }
        x.next = t;  //最后一个节点的next指向第一个节点,形成循环链表
 
        for (int i = 1; i <= K - 1; i++) //寻找报数的起点
            x = x.next;
 
        while (x != x.next)
        { //只剩下一个结点的时候停止
            for (int i = 1; i < M; i++) {
                x = x.next;
                // System.out.print(x.val+" ");
            }
            //此时x是将出列的节点的前一个节点
            System.out.println(x.next.val + " ");
            x.next = x.next.next;
        }
        System.out.println(x.val);
       
    }
}

双向链表再求解小王子问题

在上述的单链表和循环链表中,每次删除一个元素,都要保存它的前驱,在循环双链表中就不用

先生成头节点,再建立双向链表:

插入删除都与单链表的操作出入不大,只需要注意before指针的指向 

完整代码如下:

iimport java.util.Scanner;

public class Main
{
    static class Node
    {
        int data;
        Node next;
        Node before;
        Node(int v)
        {
            data = v;
        }
    } //成员类,代表节点

    static Node head = new Node(1); //头节点单列出来

    static void init()
    {
        Node x = head;
        for (int i = 1; i<= 10; i++)
        {
            x.next = new Node(i); //建立双向链表
            x.next.before = x;
            x = x.next;
        }
        x.next = null;
    }

    static void del(int x)
    {

        for (Node T = head.next; T != null; T = T.next) //链表的遍历常用写法
        {
            if (T.data == x) //找到要的那个数了
            {
                T.before.next = T.next; //将节点从链表上摘除

                T.next.before=T.before;
                return;                 //删除结束后,结束函数。
            }
        }
    }

    static void insert(int x)
    {
        Node temp = new Node(x);

        temp.next = head.next;
        temp.next.before = temp;
        head.next = temp;
        temp.before=head;
    }

    static void show(int i)
    {
        for (Node T = head.next; T != null; T = T.next) //链表的遍历常用写法
        {
            System.out.print(T.data + " ");
        }
        System.out.println(" ");
    }

    public
    static void main(String[] args)
    {

        int N; //n个人从k位置开始报数,数到m出列

        Scanner in = new Scanner(System.in);
        init();
        N = in.nextInt();

        for (int i = 0; i < N; i++)
        {
            int x = in.nextInt();
            del(x);
            insert(x);
            show(i);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值