0


[经典约瑟夫环问题]详解单链表和数组的区别

✅作者简介:大家好,我是Philosophy7?让我们一起共同进步吧!🏆
📃个人主页:Philosophy7的csdn博客
🔥系列专栏:数据结构和算法
👑哲学语录: 承认自己的无知,乃是开启智慧的大门
💖如果觉得博主的文章还不错的话,请点赞👍+收藏⭐️+留言📝支持一下博>主哦🤞

在这里插入图片描述

文章目录

数据结构基础

🍌回忆什么是数组?

首先数组的英文单词是:array,是有限个相同类型的数据组成的有序集合,数组中的每一个变量称为元素。

数组是

最简单、最常用

的数据结构,同时数组就属于

线性结构

同时数组的特点是在内存中

顺序存储

,因此逻辑上实现了

顺序表

而内存是由一个个连续的内存单元组成的,每一个内存都有它自己的地址,而数组中的元素的内存地址是连续。

数组的基本操作

数组的基本操作无异于CRUD,现在就带大家回忆一下数组的基本用法。

packagecom.philosophy7.array;publicclassArrayTest{publicstaticvoidmain(String[] args){//1.创建数组//int[] arr = new int[5]; //动态创建数组的方式int[] arr =newint[]{5,3,1,7,9};//静态//1.读取数组中的元素System.out.println(arr[3]);//访问数组中的第四个元素 索引从0开始//2.修改(更新)元素
        arr[3]=0;//修改第四个元素System.out.println("更新之后的数据:"+ arr[3]);//3.插入数据/*
            数组中可能存在这种情况:
                实际存储的元素 小于 数组的大小
            这里介绍数组常见的插入操作:
                1.尾插
                2.从中间插入
                3.超范围插入
         */int[] arr2 =newint[3];
        arr2[0]=1;
        arr2[1]=3;MyArray array =newMyArray(3);
        array.insert(0,5);System.out.println(array.showSize());
        array.insert(1,10);
        array.insert(2,20);System.out.println("===========");
        array.display();}}classMyArray{privateint[] array;privateint size;publicMyArray(){}//初始化数组publicMyArray(int capacity){this.array =newint[capacity];

        size =0;}/**
     * 数组插入元素
     * @param index 插入的位置
     * @param element 插入的元素
     */publicvoidinsert(int index,int element){//判断索引是否越界if(index <0|| index > size){System.out.println("超出范围~!!");return;}//从右向左循环 将元素逐个向右挪一位//相当于尾插for(int i = size -1; i >= index; i--){
            array[i+1]= array[i];}//腾出的位置放入新元素
        array[index]= element;
        size++;}/**
     * 遍历输出数组
     */publicvoiddisplay(){for(int i =0; i < size; i++){System.out.println(array[i]);}}publicintshowSize(){return size;//返回数组的大小}}

对上述代码进行解释说明:

成员变量size是数组实际元素的数量。如果插入元素在数组尾部,传入的下标参数 == size;如果插入元素在中间或者是在头部,则抛出异常。
当数组中的容量已经满了,这个时候仍需要插入元素,就需要涉及到扩容了
packagecom.philosophy7.array;publicclassArrayTest02{publicstaticvoidmain(String[] args){MyArray2 myArray2 =newMyArray2(3);
        myArray2.insert(0,2);
        myArray2.insert(1,4);
        myArray2.insert(2,5);
        myArray2.insert(3,6);

        myArray2.delete(3);
        myArray2.display();}}classMyArray2{privateint[] array;//数组privateint size;//标识元素个数//初始化数组MyArray2(int capacity){
        array =newint[capacity];
        size =0;}//插入数据publicvoidinsert(int index,int element){//1.首先判断是否索引越界if(index <0|| index > size){System.out.println("超出数组实际范围存储");return;}//2.扩容if(size >= array.length){resize();}for(int i = size -1; i >= index; i--){
            array[i+1]= array[i];}}/**
     * arraycopy参数详解:
     *         src 源数组。
     *      * @param srcPos 在源数组中的起始位置。
     *      * @param dest 目标数组。
     *      * @param destPos 目标数据中的起始位置。
     *      * @param length 要复制的数组元素的数量。
     */publicvoidresize(){int[] newArray =newint[array.length *2];//将旧数组复杂到新数组 完成扩容System.arraycopy(array,0,newArray,0,array.length);
        array = newArray;}//输出数组publicvoiddisplay(){for(int i =0; i < size; i++){System.out.println(array[i]);}}publicintdelete(int index){if(index <0|| index >= size){thrownewIndexOutOfBoundsException("索引越界");}int deletedElement = array[index];//元素逐个向左挪一位for(int i = index; i < size-1; i++){
            array[i]= array[i+1];}
        size--;return deletedElement;}}

小结:

通过上述,我们对数组进行了增、删、改、查操作,现在我们来说一下数组的插入和删除操作的时间复杂度时间分别是多少

首先插入操作:

数组扩容的时间复杂度是O(n),插入并移动的时间复杂度也是O(n),综述数组的插入操作时间复杂度是O(n)

删除操作:

这里只涉及到了元素的移动,所以时间复杂度也是O(n)

🍌什么是链表

我们必须首先知道数据存储一般分为线性存储链式存储两种。线性存储是一种顺序的存储的方式,就入刚才我们上述介绍的数组一样。

现在我们正式开始介绍链表:

我们首先来看一下单向链表的结构

在这里插入图片描述

链表与数组不同,数组是顺序存储的,而链表是一种在物理上非连续、非顺序的数据结构,由若干个节点(node)组成。

单向链表中的每个节点就包含两部分,一部分是存放数据的变量data,还有一部分是指向另一个节点的指针next

在这里插入图片描述

🍌单向链表的基本操作

基本操作也是一样的 无异于增、删、改、查

节点类

classLinkedNode{privateObject data;//数据privateLinkedNode next;//指针publicLinkedNode(Object data){this.data = data;}publicObjectgetData(){return data;}publicvoidsetData(Object data){this.data = data;}publicLinkedNodegetNext(){return next;}publicvoidsetNext(LinkedNode next){this.next = next;}}

单向链表

增加节点就有:头插法、尾插法、中间插入
publicclassSingleLinkedList{privateLinkedNode head;//头节点/**
     * 头插法
     * 将数据插入到链表的头部
     * @param data
     */publicvoidaddHeader(Object data){//创建节点LinkedNode temp =newLinkedNode(data);if(head ==null){//头部为空 == 链表是为空的
            head = temp;//传进来的该节点就为头节点return;}//链表不为空 将新的节点放到链表的头部
        temp.setNext(head);// 指针指向头节点this.head = temp;//将头节点更换为temp}/**
     * 尾插法:
     * 将节点放于链表的尾部
     * 尾部的指针指向Null
     * @param data
     */publicvoidaddTail(Object data){//创建一个节点LinkedNode temp =newLinkedNode(data);if(head ==null){
            head = temp;return;}//非空 找到最后一个节点LinkedNode cur = head;//从头节点开始往后面找 直到找到最后一个节点//最后一个节点的指针 指向 nullwhile(cur.getNext()!=null){
            cur = cur.getNext();}
        cur.setNext(temp);//将找到最后一个节点的指针指向与我们要插入到尾部链表中去}//获取链表的长度publicintsize(){int size =0;for(LinkedNode node =this.head; node !=null; node = node.getNext()){
            size++;}return size;}//获取index位置节点publicLinkedNodegetIndex(int index){LinkedNode cur =this.head;for(int i =0; i < index; i++){
            cur = cur.getNext();}return cur;}/**
     * 将数据插入到指定的位置
     * @param index
     * @param data
     */publicvoidaddIndex(int index,Object data){//创建节点LinkedNode node =newLinkedNode(data);//校验传进的index是否合法int len =size();if(index <0|| index > len){System.out.println("不合法的操作");return;//结束操作}//头插if(index ==0){addHeader(data);}//尾插if(index == len){addTail(data);}//插入到指定的位置 需要去找到插入位置的上一个节点LinkedNode prev =getIndex(index -1);//找到上一个节点
        node.setNext(prev.getNext());//设置新插入节点信息的指针指向于 前一个指针原指向的下一个指针
        prev.setNext(node);//前一个节点指针 指向于 新插入的节点信息}//遍历节点publicvoidprintInfo(){System.out.print("[");for(LinkedNode node =this.head; node !=null; node = node.getNext()){System.out.print(node.getData());if(node.getNext()!=null){System.out.print(",");}}System.out.println("]");}//删除节点publicvoidremove(int index){//找到删除节点的前一个节点if(head ==null){System.out.println("链表中没有节点可以删除");return;}//删除的节点是否为头节点LinkedNode removeNode =null;if(index ==0){
            removeNode = head;
            head = head.getNext();}//删除中间节点LinkedNode prev =getIndex(index -1);
        prev.setNext(prev.getNext().getNext());}publicLinkedNodeget(int index){if(index <0|| index >=size()){System.out.println("您要查找的节点信息不存在");returnnull;}LinkedNode temp = head;for(int i =0; i < index;i++){
            temp = temp.getNext();}return temp;}}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

其他图可以参考这些

现在我们来对比一下数组和链表
查找更新插入删除数组O(1)O(1)O(n)O(n)链表O(n)O(n)O(1)O(1)
通过该表信息就可以发现,数组的优势在于能够快速定位元素,而对于读操作多、写操作少的场景来说,用数组更合适,之所以数组能够拥有快速查找的优势,就是基于数组当中是有索引(下标)。而链表当中是没有下标的

相反,链表的优势就在于能够快速的插入和删除操作,如果需要在尾部频繁插入、删除元素,链表的优势就体现出来了。

约瑟夫环问题

约瑟夫环(约瑟夫问题)是一个数学的应用问题:

已知n个人(以编号1,2,3…n分别表 示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人 又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出 列。

packagecom.philosophy7.linked;importjava.util.ArrayList;importjava.util.List;importjava.util.Scanner;/**
 * @author Philosophy7
 */publicclassYue{publicstaticvoidmain(String[] args){Scanner scanner =newScanner(System.in);System.out.print("请输入总人数:");int totalNum = scanner.nextInt();System.out.print("请输入报数的大小:");int cycleNum = scanner.nextInt();yuesefu(totalNum, cycleNum);
        scanner.close();}publicstaticvoidyuesefu(int totalNum,int countNum){// 初始化人数List<Integer> start =newArrayList<Integer>();for(int i =1; i <= totalNum; i++){
            start.add(i);}// 从第K个开始计数int k =0;while(start.size()>0){
            k = k + countNum;// 第m人的索引位置
            k = k %(start.size())-1;// 判断是否到队尾if(k <0){System.out.println(start.get(start.size()-1));
                start.remove(start.size()-1);
                k =0;}else{System.out.println(start.get(k));
                start.remove(k);}}}}

判断两个单链表是否相交?若相交,求出交点。(2020年腾讯一面原题)

思路:由于两个链表是不一样长度的,所以可以先求出两个链表的长度,然后用长度长的链表减去长度短的链表,得出一个长度k,然后先让链表长度长的链表先走k步,然后两个链表在一起走,只要链表的指针相等,那么这个时候指针指向的结点就是两个链表的交点了。


本文转载自: https://blog.csdn.net/ChengXuTeng/article/details/124647755
版权归原作者 Philosophy7 所有, 如有侵权,请联系我们删除。

“[经典约瑟夫环问题]详解单链表和数组的区别”的评论:

还没有评论