线性表数据结构—数组和链表

线性表( Linear List )就是数据排成像一条线一样的结构,数据只有前后两个方向

1 数组

概念
数组( Array )是 有限 相同类型 的变量所组成的 有序 集合,数组中的每一个变量被称为元素。数组是最为简单、最为常用的数据结构。
数组下标从零开始 (Why)
存储原理
数组用一组 连续的内存空间 来存储一组具有 相同类型 的数据
( 模拟内存存储 )
灰色格子:被使用的内存
橙色格子:空闲的内存
红色格子:数组占用的内存
数组可以根据下标随机访问数据
比如一个整型数据 int[] 长度为 5
假设首地址是: 1000
int 4 字节( 32 位) , 实际内存存储是位
随机元素寻址
a[i]_address=a[0]_address+i*4
该公式解释了三个方面
  • 连续性分配
  • 相同的类型
  • 下标从0开始
操作
  • 读取元素
   根据下标读取元素的方式叫作随机读取
int n=nums[2]
  • 更新元素
nums[3]= 10;
  注意不要数组越界
  读取和更新都可以随机访问,时间复杂度为O(1)
  • 插入元素
   有三种情况:
   尾部插入
   在数据的实际元素数量小于数组长度的情况下:
   直接把插入的元素放在数组尾部的空闲位置即可,等同于更新元素的操作
   
a[6]=10
    中间插入
    在数据的实际元素数量小于数组长度的情况下:
    由于数组的每一个元素都有其固定下标,所以首先把插入位置及后面的元素向后移动,
    腾出地方,再把要插入的元素放到对应的数组位置上。
   
   超范围插入
   假如现在有一个数组,已经装满了元素,这时还想插入一个新元素,或者插入位置是越界的
   这时就要对原数组进行扩容:可以创建一个新数组,长度是旧数组的 2 倍,再把旧数组中的元素     统统复制 过去,这样就实现了数组的扩容。
int[] numsNew=new int[nums.length*2];
System.arraycopy(nums,0,numsNew,0,nums.length);
// 原数组就丢掉了,资源浪费
nums=numsNew;
  • 删除元素
    数组的删除操作和插入操作的过程相反,如果删除的元素位于数组中间,其后的元素都需要向前挪动1 位。
for(int i=p;i<nums.length;i++){
    nums[i-1]=nums[i];
}
  完整的代码:
package com.fx.linear;

public class ArrayDemo1 {
    int[] nums = new int[8];
    public ArrayDemo1() {
        nums[0] = 3;
        nums[1] = 1;
        nums[2] = 2;
        nums[3] = 5;
        nums[4] = 4;
        nums[5] = 9;
    }
    public int get(int i) {
        return nums[i];
    }
    public void update(int i, int n) {
        nums[i] = n;
    }
    public void insertTail(int n) {
        nums[6] = n;
    }
    public void insertMiddle(int p, int n) {
        for (int i = nums.length-1; i >= p-1; i--) {
            //能取得值
            if (nums[i] != 0) {
                nums[i+1]=nums[i];
            }
        }
        nums[p-1]=n;
    }
    /**
     * 旧数组复制到新数组
     */
    public void resize(){
        int[] numsNew=new int[nums.length*2];
        System.arraycopy(nums,0,numsNew,0,nums.length);
        nums=numsNew;
    }
    public void insertOutOfBounds(int p,int n){
        //数组扩容
        resize();
        nums[p-1]=n;
    }
    public void deleteMiddle(int p){
        for(int i=p;i<nums.length;i++){
            nums[i-1]=nums[i];
        }
    }
    public void display() {
        for (int n : nums) {
            System.out.println(n);
        }
    }
    public void display2() {
        for (int i = nums.length - 1; i >= 0; i--) {
            System.out.println(nums[i]);
        }
    }

    public static void main(String[] args) {
        ArrayDemo1 demo1 = new ArrayDemo1();
        demo1.deleteMiddle(3);
        demo1.display();
    }

}
时间复杂度
读取和更新都是随机访问,所以是 O(1)
插入数组扩容的时间复杂度是 O(n) ,插入并移动元素的时间复杂度也是 O(n) ,综合起来插入操作的时间
复杂度是 O(n)
删除操作,只涉及元素的移动,时间复杂度也是 O(n)
优缺点
  • 优点:
        数组拥有非常高效的随机访问能力,只要给出下标,就可以用常量时间找到对应元素
  • 缺点:
        插入和删除元素方面。由于数组元素连续紧密地存储在内存中,插入、删除元素都会导致大量元素被迫 移动,影响效率。 (ArrayList LinkedList )
        申请的空间必须是连续的,也就是说即使有空间也可能因为没有足够的连续空间而创建失败
如果超出范围,需要重新申请内存进行存储,原空间就浪费了
应用
数组是基础的数据结构,应用太广泛了, ArrayList Redis 、消息队列等等。
数据结构和算法的可视化网站: https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

2 链表

概念
链表( linked list )是一种在物理上非连续、非顺序的数据结构,由若干节点( node )所组成。
链表中数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。(百度百科)
常见的链表包括:单链表、双向链表、循环链表
  • 单链表
单向链表的每一个节点又包含两部分,一部分是存放数据的变量 data ,另一部分是指向下一个节
点的指针 next
Node{
    int data;
    Node next;
}
  • 双向链表
双向链表的每一个节点除了拥有 data next 指针,还拥有指向前置节点的 prev 指针。
Node{
    int data;
    Node next;
    Node prev;
}
  • 循环链表
链表的尾节点指向头节点形成一个环,称为循环链表
存储原理
数组在内存中的存储方式是顺序存储(连续存储),链表在内存中的存储方式则是随机存储(链式存储)。
链表的每一个节点分布在内存的不同位置,依靠 next 指针关联起来。这样可以灵活有效地利用零散的碎片空间。
链表的第 1 个节点被称为头节点(3),没有任何节点的 next 指针指向它,或者说它的前置节点为空
头结点用来记录链表的基地址。有了它,我们就可以遍历得到整条链表
链表的最后 1 个节点被称为尾节点(2),它指向的 next 为空
操作
  • 查找节点
在查找元素时,链表只能从头节点开始向后一个一个节点逐一查找。
  • 更新节点
找到要更新的节点,然后把旧数据替换成新数据
  • 插入节点
      尾部插入
     把最后一个节点的 next 指针指向新插入的节点即可
    头部插入
    第 1 步,把新节点的 next 指针指向原先的头节点
    第 2 步,把新节点变为链表的头节点
     中间插入
     第 1 步,新节点的 next 指针,指向插入位置的节点
     第 2 步,插入位置前置节点的 next 指针,指向新节点
只要内存空间允许,能够插入链表的元素是无限的,不需要像数组那样考虑扩容的问题
  • 删除节点
      尾部删除
      把倒数第 2 个节点的next指针指向空即可
      头部删除
      把链表的头节点设为原先头节点的 next 指针即可
      中间删除
      把要删除节点的前置节点的 next 指针,指向要删除元素的下一个节点即可
完整代码
package com.fx.linear;

/**
 * @ Author fx
 * @ Date 2024/8/20 9:46
 */
public class Node {
    int id;
    String name;
    //下一个节点
    Node next;
    public Node(int id, String name) {
        this.id = id;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Node{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
package com.fx.linear;

/**
 * 单链表
 */
public class SingleLinkedList {

    //初始化头节点
    private Node head = new Node(0, "");
    /**
     * 添加节点:从头插入
     *
     * @param node
     */
    public void addNode(Node node) {
        //从头插入
        Node tmp = head;
        while (true) {
            //到尾节点
            if (tmp.next == null) {
                break;
            }
            //后移一个节点
            tmp = tmp.next;
        }
        tmp.next = node;
    }

    public void addByIdOrder(Node node){
        //从头插入
        Node tmp = head;
        while (true) {
            //到尾节点
            if (tmp.next == null) {
                break;
            }
            //节点存在
            if (tmp.next.id == node.id) {
                break;
            }
            if (tmp.next.id > node.id) {
                break;
            }
            tmp = tmp.next;
        }
        //交换位置
        node.next = tmp.next;
        tmp.next = node;
    }

    //遍历链表
    public void showList() {
        //空链表
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        Node temp = head.next;
        while (true) {
            if (temp == null) {
                return;
            }
            System.out.println(temp);
            //指针下移
            temp = temp.next;
        }
    }

    public static void main(String[] args) {
        Node n1=new Node(1,"张飞");
        Node n2=new Node(2,"关羽");
        Node n3=new Node(3,"赵云");
        Node n4=new Node(4,"黄忠");
        Node n5=new Node(5,"马超");
        SingleLinkedList sll=new SingleLinkedList();
        sll.addByIdOrder(n4);
        sll.addByIdOrder(n5);
        sll.addByIdOrder(n1);
        sll.addByIdOrder(n2);
        sll.addByIdOrder(n3);
        sll.showList();
    }
}
时间复杂度
查找节点 : O(n)
插入节点: O(1)
更新节点: O(1)
删除节点: O(1)
优缺点
  • 优势
        插入、删除、更新效率高
        省空间
  • 劣势
        查询效率较低,不能随机访问
应用
链表的应用也非常广泛,比如树、图、 Redis 的列表、 LRU 算法实现、消息队列等
数组与链表的对比
数据结构没有绝对的好与坏,数组和链表各有千秋。
数组的优势在于能够快速定位元素,对于读操作多、写操作少的场景来说,用数组更合适一些
链表的优势在于能够灵活地进行插入和删除操作,如果需要在尾部频繁插入、删除元素,用链表更合适 一些
数组和链表是线性数据存储的物理存储结构:即顺序存储和链式存储。
  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值