【Java数据结构】顺序表、队列、栈、链表、哈希表

news2024/9/28 1:27:10

顺序表

定义

存放数据使用数组但是可以编写一些额外的操作来强化为线性表,底层依然采用顺序存储实现的线性表,称为顺序表

代码实现

创建类型

先定义一个新的类型

public class ArrayList<E> {
    int capacity = 10; //顺序表的最大容量
    int size = 0; //当前已经存放的元素数量
    private Object[] array = new Object[capacity]; //底层存放的数组
}

在这里插入图片描述

由此可见,顺序表是紧凑的,不能出现空位

插入

但是这样少了条件限制,也就是容量的问题,范围只是[0,size],所以需要判断

public void add(E element, int index) {
    if(index<0 || index>size)
        throw new IndexOutOfBoundsException("invalid, size:0~"+size);
    for(int i=size; i>index; i--) {
        array[i] = array[i-1];
    }
    array[index] = element;
    size++;
}
public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<>();
    list.add(10,1); //是一个可以得到异常的函数
}

此时此刻,就很成功的报错了
在这里插入图片描述

完善 - 扩容

我们可以让我们的操作更加完美,比如,扩容

public void add(E element, int index) {
    if(index<0 || index>size)
        throw new IndexOutOfBoundsException("invalid, size:0~"+size);
    if(capacity == size) {
        int newCapacity = capacity +(capacity >> 1);
        Object[] newArray = new Object[newCapacity];
        System.arraycopy(array, 0, newArray, 0, size);
        array = newArray;
        capacity = newCapacity;
    }
    for(int i=size; i>index; i--) {
        array[i] = array[i-1];
    }
    array[index] = element;
    size++;
}

打印

public String toString() {
    StringBulider bulider = new StringBuilder();
    for(int i=0; i<size; i++) {
        builder.append(array[i].append(" "));
        return builder.toString();
    }
}

删除

@SuppressWarnings("unchecked")
public E remove(int index){
    if(index < 0 || index > size - 1)
        throw new IndexOutOfBoundsException("删除位置非法,合法的插入位置为:0 ~ "+(size - 1));
    E e = (E) array[index];
    for (int i = index; i < size; i++)
        array[i] = array[i + 1];
    size--;
    return e;
}

获取

public E get(int index) {
if(index<0 || index>(size-1)) 
	throw new IndexOutOfBoundsException("invalid: position 0~" +(size-1));
return (E) array[index];
}

public int size() {
	return size;
}

链表

链表通过指针来连接各个分散的结点,形成链状的结构,不需要申请连续的空间,逻辑依然是每个元素相连

链表分带头结点的和不带头结点的

带头结点的就是不存放数据,一般设计都会采用带头结点的

设置

public class LinkedList<E> {
    private final Node<E> head = new Node<>(null);
	private int size = 0;

	private static class Node<E> {
   		E element;
    	Node<E> next;
    	public Node(E element) {
        	this.element = element;
    	}
	}
}

插入

考虑的点:插入位置是否合法

public void add(E element, int index){
    if(index < 0 || index > size)
        throw new IndexOutOfBoundsException("插入位置非法,合法的插入位置为:0 ~ "+size);
    Node<E> prev = head;
    for (int i = 0; i < index; i++)
        prev = prev.next;
    Node<E> node = new Node<>(element);
    node.next = prev.next;
    prev.next = node;
    size++;
}

重写 toString()

@Override
public String toString() {
	StringBuilder builder = new StringBuilder();
    Node<E> node = head.next;
    while(node != null) {
        bulider.append(node.element).append(" ");
        node = node.next;
    }
    return builder.toString();
}

删除

public E remove(int index) {
    if(index<0 || index>size-1) {
        throw new IndexOutOfBoundsException("invalid,position: 0~" + (size - 1));
    }
    Node<E> prev = head;
    for(int i=0; i<index; i++) {
        prev = prev.next;
    }
    E e = prev.next.element;
    size--;
    return e;
}

获取对应位置上的元素

public E get(int index) {
    if(index<0 || index>size-1) {
        throw new IndexOutOfBoundsException("invalid position:0~" + (size-1));
    }
    Node<E> node = head;
    while(index-- >= 0) {
        node = node.next;
    }
    return node.element;
}

public int size() {
    return size;
}

总结

链表在随机访问数据的时候,需要通过遍历来完成,而顺序表则利用数组的特性可以直接访问得到,当我们读取数据多于插入删除操作的时候,使用顺序表会更好;顺序表在进行插入删除操作的时候,因为需要移动后续元素,会很浪费时间,链表则不需要,只需要修改结点的指向就行了。所以在进行频繁的插入删除操作的时候,使用链表必然是更好的

栈-先进后出

**FILO: First in, Last out **

是一种特殊的线性表,只能在表尾进行插入删除操作;有点像堆积木,最后堆上去的往往是最先拿下来的

底部称为栈底,顶部称为栈顶;所有的操作只能在栈顶进行

栈可以使用顺序表实现,也可以使用链表实现,使用链表会更加方便。直接将头结点指向栈顶结点,而栈顶结点连接后续的栈内结点

链表实现

public class LinkedStack<E> {
    private final Node<E> head = new Node<>(null);
    private static class Node<E> {
        E element;
        Node<E> next;
        public Node(E element) {
            this.element = element;
        }
    }
}

入栈操作

public pop() {
    if(head.next == null) {
        throw new NoSuchElementException("Stack is empty");
    }
    E e = head.next.element;
    return e;
}

测试

public static void main(String[] args) {
    LinkedStack<String> stack = new LinkedStack<>();
    stack.push("AAA");
    stack.push("BBB");
    stack.push("CCC");
    System.out.println(stack.pop());
    System.out.println(stack.pop());
    System.out.println(stack.pop());
}

队列

FIFO First in First out

类似于在超市买东西,先进先出
队列有队头和队首
实现方式:链表、顺序表
利用链表则不需要考虑容量的问题,我们需要同时保存队首和队尾两个指针

创建队列

public class LinkedQueue<E> {
	private final Node <E> head = new Node<>(null);
//入队操作
	public void offer(E element) {
		Node<E> last = head;
		while(last.next != null) {
			last = last.next;
		}
		last.next = new Node<>(element);
	}
//出队操作
	public E pull() {
		if(head.next == null) 
			throw new NoSuchElementException(Queue is empty”);
		E e = head.next.element;
		head.next = head.next.next;
		return e;
	}

	private static class Node<E> {
		E element;
		Node<E> next;
		public Node(E element) {
			this.element = element;
		}
}

使用

public static void main(String[] Args) {
	LinkedQueue<String> stack = new LinkedQueue<>();
	stack.offer(“AAA”);
	stack.offer(“BBB”);
	stack.offer(“CCC”);
	System.out.println(stack.pull());
	System.out.println(stack.pull());
	System.out.println(stack.pull());

哈希表

散列(Hashing)通过散列函数(哈希函数)将要参与检索的数据与散列值(哈希值)关联,生成一种便于搜索的数据结构,称其为散列表(哈希表)

我们需要将一堆数据保存起来,这些数据会通过哈希函数进行计算,得到与其对应的哈希值,下次需要查找的时候,只需要再次计算哈希值就行了

哈希函数在现实生活中应用十分广泛,目前应用最广泛的是SHA-1、MD5;比如我们下载的IDEA,她的校验文件,就是安装包文件通过哈希算法计算得到的

我们可以将这些元素保存到哈希表,保存的位置与其对应的哈希值有关,哈希值是通过哈希函数计算得到的,我们只需要将对应元素的key提供给哈希函数就可以进行计算了。一般比较简单的哈希函数就是取模操作。哈希表长度(最好是素数)多少,模就是多少

保存到数据是无序的,我们并不清楚计算完哈希值之后会被放到那个位置,哈希表在查找时只需要进行一次哈希函数计算就能直接找到对应元素的存储位置,效率极高

实现简单的哈希表和哈希函数,通过哈希表,可以将数据的查找时间复杂度提升到常数阶

public class HashTable<E> {
    private final int TABLE_SIZE = 10;
    private final Object[] TABLE = new Object[TABLE_SIZE];
    
    public void insert(E element) {
        int index = hash(element);
        TABLE[index] = element;
    }
    
    public boolean contains(E element) {
        int index = hash(element);
        return TABLE[index] == element;
    }
    
    private int hash(Object object) {
        int hashCode = object.hashCode(); //每一个对象都有一个独一无二的哈希值,可以通过hashCode方法得到,只有极小概率会重复
        return hashCode % TABLE_SIZE;
    }
}

会出现哈希碰撞的情况,也就是不同的元素计算出来的哈希值是一样的,这种情况是不可避免的,我们只能通过使用更加高级的哈希函数来尽可能避免这种情况

常见的哈希冲突解决方案是链地址法 ,当出现哈希冲突的时候,我们依然将其保存在对应的位置上,可以将其连接为一个链表的形式

当元素太多的时候,我们一般将其横过来看
在这里插入图片描述

但是当链表变得很长的时候,查找效率也就会变得很低下,所以我们可以考虑在链表长度达到一定值的时候,使用其他数据结构,例如平衡二叉树、红黑树,这样就可以一定程度上缓解查找的压力

public class HashTable<E> {
    private final int TABLE_SIZE = 10;
    private final Node<E>[] TABLE = new Node[TABLE_SIZE];
    
    public HashTable() {
        for(int i=0; i<TABLE_SIZE; i++) {
            TABLE[i] = new Node<>(null);
        }
    }
    
    public void insert(E element) {
        int index = hash(element);
        Node<E> prev = TABLE[index];
        while(prev.next != null) {
            prev = prev.next;
        }
        prev.next = new Node<>(element);
    }
    
    public boolean contains(E element) {
        int index = hash(element);
        Node<E> node = TABLE[index].next;
        while(node != null) {
            if(node.element == element) 
                return true;
            node = node.next;
        }
        return false;
    }
    
    private int hash(Object object) {
        int hashCode = object.hashCode();
        return hashCode % TABLE_SIZE;
    }
    
    private static class Node<E> {
        private final E element;
        private Node<E> next;
        
        private Node(E element) {
            this.element = element;
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/487324.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UNIX环境高级编程——信号

10.1 引言 信号是软件中断&#xff1b;信号提供了一种处理异步事件的方法。 10.2 信号概念 每个信号都有一个名字&#xff0c;这些名字都以3个字符SIG开头&#xff1b;在头文件<signal.h>中&#xff0c;信号名都被定义为正整数常量&#xff08;信号编号&#xff09;&a…

架构设计-高性能篇

大家好&#xff0c;我是易安&#xff01;今天我们谈一谈架构设计中的高性能架构涉及到的底层思想。本文分为缓存架构&#xff0c;单服务器高性能模型&#xff0c;集群下的高性能模型三个部分&#xff0c;内容很干&#xff0c;希望你仔细阅读。 高性能缓存架构 在某些复杂的业务…

代码审计笔记之java多环境变量设置

在做java代码审计时&#xff0c;为了要成功运行目标环境&#xff0c;时长要对于jdk版进行切换&#xff0c;且在装多个jdk时还时长会遇到安装配置后环境变量不生效的情况&#xff0c;下文介绍&#xff1b; 1、为什么安装了新的jdk&#xff0c;有的时候环境变量中的jdk版本确还是…

如何设计出好的测试用例?

软件测试培训之如何设计出好的测试用例? 一句话概括&#xff1a;对被测软件的需求有深入的理解。 深入理解被测软件需求的最好方法是&#xff0c;测试工程师在需求分析和设计阶段就开始介入&#xff0c;因为这个阶段是理解和掌握软件的原始业务需求的最好时机。 只有真正理解了…

【VAR模型 | 时间序列】帮助文档:VAR模型的引入和Python实践(含源代码)

向量自回归 (VAR) 是一种随机过程模型&#xff0c;用于捕获多个时间序列之间的线性相互依赖性。 VAR 模型通过允许多个进化变量来概括单变量自回归模型&#xff08;AR 模型&#xff09;。 VAR 中的所有变量都以相同的方式进入模型&#xff1a;每个变量都有一个方程式&#xff…

轻松掌握在已有K8s环境上安装KubeSphere

官方文档地址&#xff1a;https://kubesphere.io/zh/docs/v3.3/quick-start/minimal-kubesphere-on-k8s/ 1、基于已有K8s环境上安装KubeSphere 1、前置环境 1、安装nfs及动态存储类PV/PVC 安装默认存储类型&#xff0c;这里使用nfs&#xff0c;关于nfs的安装在PV/PVC的文章…

出道的第八年,依然AI着......

今天&#xff0c;是数说故事8周岁的生日 8年&#xff0c;和您一起走过2,922天 8年&#xff0c;我们对AI的探索从未停止 8年&#xff0c;我们将数据的热爱进行到底 因为热“AI” 我们与您的故事有了连接 8年的连接&#xff0c;我们与您也擦出了无数花火 我们将每一个闪烁的…

Optional参数类使用

目录 介绍 使用 常用方法 是否为空 对象比较 Optional 是一个对象容器&#xff0c;具有以下两个特点&#xff1a; 使用 1. 创建 2. 获取&#xff1a; 3. 判断&#xff1a; 4. 过滤&#xff1a; 5. 映射&#xff1a; 介绍 在使用值判断的时候使用方便 使用 import j…

linux系统TP-ti,tsc2046外设调试

一、整体调试思路 tp外设属于比较常见且比较简单的外设&#xff0c;今天以ti,tsc2046这款为例简述下tp外设的调试。 整体思路 1、配置设备树----驱动调试的device部分 2、tp驱动编译及匹配—driver部分 3、驱动整体调试 二、配置设备树 对于ti,tsc2046我们可以参考内核Docum…

复杂美科技多项区块链技术产品被纳入《2021-2022区块链产业图谱》区块链蓝皮书

2022年9月3日&#xff0c;由中国社会科学院社会科学文献出版社、北京金融科技产业联盟指导&#xff0c;北京区块链技术应用协会&#xff08;BBAA&#xff09;主办的 “Web 3.0发展趋势高峰论坛暨2022元宇宙、区块链、金融科技蓝皮书发布会” 在服贸会上成功举办。 大会隆重发布…

身份鉴别解读与技术实现分析(1)

6.1.4.1 身份鉴别 本项要求包括: a) 应对登录的用户进行身份标识和鉴别,身份标识具有唯一性,身份鉴别信息具有复杂度要求并定期更换; b) 应具有登录失败处理功能,应配置并启用结束会话、限制非法登录次数和当登录连接超时自动退出等相关措施 在等级保护体系中,级别越高…

数字时代下网络安全的重要性

在数字时代&#xff0c;网络安全比以往任何时候都更加重要。 随着我们越来越依赖技术来存储和传输敏感信息&#xff0c;网络攻击的风险也在增加。网络攻击可能来自世界任何地方&#xff0c;对个人和企业都可能是毁灭性的。 AkamaiTechnologies首席安全官BoazGelbord在最近的一…

【YOLO系列】YOLOv7论文超详细解读(翻译 +学习笔记)

前言 终于读到传说中的YOLOv7了~≖‿≖✧ 这篇是在美团的v6出来不到一个月就高调登场&#xff0c;作者还是我们熟悉的AB大神&#xff08;对&#xff0c;就是v4那个&#xff09;&#xff0c;读起来又是“熟悉”的感觉&#xff08;贯穿了我的整个五一假期&#xff08;╯&#x…

Vue组件设计-多列表拖拽交换排序

在前端开发中&#xff0c;拖拽排序是一种提升用户体验非常好的方式&#xff0c;常见的场景有单列表拖拽排序&#xff0c;多列表拖拽交换排序&#xff0c;比如以下这种效果&#xff1a; 下面将以这种效果为例&#xff0c;设计一个组件。 1. 安装所需依赖 npm install vuedragg…

多模态的过渡态——latent modal

背景&#xff1a; 随着大模型的推进&#xff0c;单模态的大模型已经无法很好的满足现实工作的需要。很多科研团队和机构开始多模态的研究&#xff0c;多模态的几种机构在前面的文章已经介绍过&#xff0c;这部分不做过多介绍。最理想的多模态应该就是没有模态&#xff0c;单一…

持续集成/持续交付——JenkinsFile详细使用教程

JenkinsFile详细使用教程 一、BlueOcean1、BlueOcean 概念2、BlueOcean 特性3、BlueOcean 安装 二、Pipeline 简介1、Jenkins Pipeline 概念2、Jenkinsfile 语法类型&#xff1a;3、采用Jenkins 任务页面输入a. Jenkins中创建一个 pipeline 任务b. Definition 中选择 Pipeline …

电脑提示msvcp140.dll丢失的解决方法,msvcp140.dll丢失修复教程

msvcp140.dll是Microsoft Visual C Redistributable所需的一个动态链接库文件&#xff0c;它包含了Visual C运行库中的一些函数和类库。这个文件通常出现在Windows操作系统中&#xff0c;用于支持使用Visual C编写的程序的正常运行。如果系统缺少或损坏了这个文件&#xff0c;可…

计算机组成原理 4.2.1存储芯片连接

连接原理 主存储器 通过数据总线、地址总线和控制总线和CPU相连数据总线的位数正比于数据传输率地址总线的位数决定可寻址的最大地址空间控制总线(读/写)指出总线周期的类型和本次输入/输出完成的时刻 但是实际中存储芯片往往很小难以满足地址和数据的位数需求&#xff0c;此…

如何在云服务器上搭建ChatGLM

摘录&#xff1a;ChatGPT重新点燃了AI&#xff0c;然后OpenAI却没有向我们开放ChatGPT&#xff0c;虽然有些人通过了一下手段注册了账号&#xff0c;但是不久就被OpenAI拉入了黑名单。3月份我国的百度也推出了和ChatGPT对标的文言一心&#xff0c;随后阿里也推出了自己的文本对…

李雨浛:在数据、网络与民意之间——用计算社会科学方法探讨数字媒体与可持续未来 | 提升之路系列(八)...

导读 为了发挥清华大学多学科优势&#xff0c;搭建跨学科交叉融合平台&#xff0c;创新跨学科交叉培养模式&#xff0c;培养具有大数据思维和应用创新的“π”型人才&#xff0c;由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…