【蓝桥杯】十四招式冲刺之 “第一招”《链表》

前言

-🏀大家好,我是BXuan,热爱编程与篮球的软件工程大二学生一名
-📚近期在准备4月份的蓝桥省赛,本章与大家一起聊聊有关链表的问题!如文有误,请大家指出并多多包涵。
-🏃‍放弃不难,但坚持一定很酷。


🚩概念

①链表是线性表的链式存取的数据结构,是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

②链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:数据域(数据元素的映象)+ 指针域(指示后继元素存储位置),数据域就是存储数据的存储单元,指针域就是连接每个结点的地址数据。

🚩顺序存储VS链式存储

  • 顺序存储
    在这里插入图片描述

  • 链式存储
    在这里插入图片描述

🚩顺序表VS链表

顺序表

优点:

  1. 无需为表示结点间的逻辑关系而增加额外的存储空间(因为逻辑上相邻的元素其存储的物理位置也是相邻的);
    图片描述

  2. 可方便地随机存取表中的任一元素:由于顺序表每个元素的大小相等,且知道第几个元素就可以通过计算得到任意元素的地址,既可以随机存取任一元素。

缺点:

  1. 插入或删除运算不方便:除表尾的位置外,在表的其它位置上进行插入或删除操作都必须移动大量的结点,其效率较低;
    如在 7、8 之 间插入 X 元素,那我们为了保证其顺序性需要把 8 和 9 向后移动一位,再将 X 放到 8 的位置。
    在这里插入图片描述

    这样的存储方式增加了处理器和 IO 资源的消耗代价,这是我们不愿意看到的,至于删除其原理相同。

  2. 难以匹配存储规模:
    由于顺序表要求占用连续的存储空间,存储分配只能预先进行静态分配,因此当表长变化较大时,难以确定合适的存储规模。

时间复杂度

查找操作为 O(1),插入和删除操作为 O(n)。

链表

优点:

  1. 插入和删除速度快,保留原有的物理顺序,在插入或者删除一个元素的时候,只需要改变指针指向即可;
  2. 没有空间限制, 存储元素无上限, 只与内存空间大小有关;
  3. 动态分配内存空间,不用事先开辟内存;
  4. 使内存的利用率变高。

缺点:

  1. 占用额外的空间以存储指针,比较浪费空间,不连续存储,Malloc 函数开辟空间碎片比较多;
  2. 查找速度比较慢,因为在查找时,需要循环遍历链表。

时间复杂度:

查找操作为 O(n), 插入和删除操作为 O(1)。

🚩知识点

  • 单链表实现原理与应用
  • 循环链表实现原理与应用
  • 双向链表实现原理与应用

一、小王子(单链表)

问题描述

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

要求一:采用单链表解决

输入描述

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

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

输出描述

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

运行限制

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

输入输出案例

输入
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

代码示例

import java.util.Scanner;
//1:无需package
//2: 类名必须Main, 不可修改

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 before = head;
 for(Node T = head.next;T != null;T = T.next){
   if(T.data == x){
     //在删除之前需要将临时结点保存然后方便后续进行插入
     Node temp = T;
     before.next = T.next;
     //结束函数
     return;
   }else{
     before = 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) {
   Scanner scan = new Scanner(System.in);
   //在此输入您的代码...
   // 输入整数M
   int M = scan.nextInt();
   // 构建单链表
   init();
   // 构建一个数组进行存储
   int[] arr = new int[M];
   for(int i = 0;i < arr.length;i++){
     arr[i] = scan.nextInt();
   }
   // 打印
   for(int i = 0;i < M;i++){
     int X = arr[i];
     del(X);
     insert(X);
     show(i);
   }
   scan.close();
 }
}

二、约瑟夫环的问题

问题描述

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

ss

要求一:采用循环链表解决。

要求二:可以使用模拟法,模拟循环链表。

要求三:可以不使用循环链表类的定义使用方式。

输入描述

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

输出描述

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

输入输出案例

输入
3 5 8
输出
3
2
1

运行限制

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

代码示例

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 in = new Scanner(System.in);

        N = in.nextInt();
        K = in.nextInt();
        M = in.nextInt();
        if (M == 1)
        {
            for (int i = 0; i < N; i++) //共计N个人
            {
                int left;
                left = K - 1;
                if (left == 0)
                    left = N;
                System.out.print(left);;
            }
        }
        /*
            如果叫到1就出列,那么就是谁喊谁出列,以上为处理方式
            */
        else
        {

            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;
                //此时x是将出列的节点的前一个节点
                System.out.println(x.next.val);
                x.next = x.next.next;
            }
            System.out.println(x.val);
        }
    }
}

三、小王子(双链表)

问题描述

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

要求一:采用循环链表解决

输入描述

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

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

输出描述

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

输入输出案例

输入
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

代码示例

package E_lanqiao;

import java.util.Scanner;


//1:无需package
//2: 类名必须Main, 不可修改

public class Main {
	//类似于C链表结构体
	static class Node{
		int data;
		Node nextNode;
		Node beforeNode;
		
		Node(int v){
			data = v;
		}
	}
	// 头节点单独进行插入
	static Node head = new Node(1);
	// 对后续双链表进行构造
	private static void init() {
		// TODO Auto-generated method stub
		head.beforeNode = null;
		Node xNode = head;
		Node tNode = head;
		for(int i = 1;i <= 10;i++) {
			xNode = (xNode.nextNode = new Node(i));
			xNode.beforeNode = tNode;
			tNode = xNode;
		}
		xNode.nextNode = null;
	}
	// 删除结点
	private static void del(int x) {
		Node beginNode = head;
		Node beginNode2 = head;
		Node afterNode = beginNode2.nextNode;
		for(Node tNode = head.nextNode;tNode != null;tNode = tNode.nextNode) {
			if(beginNode.data == x) {
				beginNode2.nextNode = beginNode.nextNode;
				afterNode.beforeNode = beginNode2;
				return;
			}else {
				beginNode2 = beginNode;
				beginNode = beginNode.nextNode;
				afterNode = beginNode.nextNode;
			}
		}
	}
	// 重新插入结点
	private static void insert(int x) {
		Node tempNode = new Node(x);
		Node seconNode = head.nextNode;
		tempNode.nextNode = head.nextNode;
		head.nextNode = tempNode;
		tempNode.beforeNode = head;
		seconNode.beforeNode = tempNode;
	}
	// 打印函数
	private static void show() {
		for (Node tNode = head.nextNode; tNode != null; tNode=tNode.nextNode) {
			System.out.print(tNode.data+" ");
		}
		System.out.println();
	}
	 public static void main(String[] args) {
	     Scanner scan = new Scanner(System.in);
	     //在此输入您的代码...
	     // 输入小王子排玩具的次数
	     int M = scan.nextInt();
	     // 构建链表
	     init();
	     // 创建数组保存需要调换的数字
	     int[] arr = new int[M];
	     for (int i = 0; i < M; i++) {
			arr[i] = scan.nextInt();
	     }
	     // 对数组循环一遍调换数字
	     for (int i = 0; i < arr.length; i++) {
			del(arr[i]);
			insert(arr[i]);
			show();
		}
	     scan.close();
	 }
}

👏小结

本次学习了三种最常见的链表的原理与使用方式,可见链表的种类是千变万化的,像是循环链表与双向链表的结合形成的双向循环链表,存储图的十字链表等,我们学好这基础的三种链表,以不变应万变才是正确的面对方式。冲冲冲!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BXuan随笔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值