Java数据结构:稀疏数组的实现与应用

简介: 文章目录1 稀疏数组引入1.1 使用场景1.2 稀疏数组简介2 稀疏数组的实现2.1 案例概述2.2 思路分析2.3 代码实现

1 稀疏数组引入

1.1 使用场景

笔者在课程设计中曾写过一个扫雷小游戏,为了便于讲解,我们来做个简化(实际比这个复杂),只考虑当前位置有雷与无雷两种状况:雷用1表示,非雷用0表示。则将当前状态用二维数组表示如下:

在右侧的二维数组中,很多都是0,即记录了很多没有意义的数据,因此,我们考虑使用稀疏数组进行存储结构的优化。


1.2 稀疏数组简介

当一个数组中的大部分元素都是0(或者为相同的某一值),可以考虑使用稀疏数组来优化保存。

而稀疏数组的基本处理方式为:


记录数组有几行几列,有几个有效值;

把有效值的元素的行、列及值记录在一个小规模的数组中,从而缩小程序的规模。

例如上述的二维数组用稀疏数组表示,可以创建一个n*3列的稀疏数组。其中,n为有效值个数加1,第一行用于存储二维数组的行数、列数及有效值的个数,便于恢复二维数组时使用。

而从第二行开始后的每一行,都记录某个有效值的所在行、列索引与值。

上述二维数组用稀疏数组表示如下:

稀疏数组的第n行 row col val
0 10 10 8
1 0 4 1
2 0 8 1
3 1 5 1
4 1 7 1
5 4 8 1
6 5 5 1
7 7 2 1
8 8 7 1


以索引0那行,10 10 8为例:表示原二维数组有10行10列,共有8个有效值。

以索引1那行,0 4 1为例:即第一个有效值在原数组的第0行第4列(索引为0和4),值为1,以此类推…

2 稀疏数组的实现

2.1 案例概述

1.使用稀疏数组来保存前面1.1样例中的二维数组(见下图);

2.可以由稀疏数组恢复成二维数组。


2.2 思路分析


二维数组转稀疏数组:


  1. 遍历原始的二维数组,得到有效数据的个数 amount;
  2. 根据 amount 可以创建稀疏数组 sparseArray[amount+1][3];
  3. 将二维数组中的有效值存入稀疏数组中。

稀疏数组恢复二维数组:


先读取稀疏数组的第一行,根据第一行的数据,创建原始二维数组;

读取第二行及以后行的数据,按照每行的行、列、值恢复有效值,录入二维数组中。

2.3 代码实现

根据如上思路,逐步实现稀疏数组,详情可见代码注释


参考代码:

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 二维数组转稀疏数组、稀疏数组还原二维数组的实现
 */
public class SparseArray {
    public static void main(String[] args) {
        //先创建原始二维数组
        int[][] originalArray = new int[10][10];
        originalArray[0][4] = 1;
        originalArray[0][8] = 1;
        originalArray[1][5] = 1;
        originalArray[1][7] = 1;
        originalArray[4][8] = 1;
        originalArray[5][5] = 1;
        originalArray[7][2] = 1;
        originalArray[8][7] = 1;
        //输出原始的二维数组
        System.out.println("原始的二维数组:");
        for (int[] row: originalArray) {
            for (int data : row) {
                System.out.printf("%d\t", data);
            }
            System.out.println();
        }
        //将二维数组转成稀疏数组
        //1.遍历二维数组,得到非0的有效数据的个数
        int amount = 0;
        for (int i = 0; i < originalArray.length; i++) {
            for (int j = 0; j < originalArray[i].length; j++) {
                if (originalArray[i][j] != 0){
                    amount++;
                }
            }
        }
        System.out.println("amount = " + amount);
        //2.创建对应的稀疏数组 sparseArray[amount+1][3], 并初始化稀疏数组第一行的数据
        //第一行第一列: 原数组的行数   第一行第二列: 原数组的列数  第一行第三列: 原数组的有效数据个数
        int[][] sparseArray = new int[amount + 1][3];
        sparseArray[0][0] = 10;
        sparseArray[0][1] = 10;
        sparseArray[0][2] = amount;
        //3.遍历二维数组,将非0值存储进稀疏数组
        int count = 0; //用于记录是第几个非零数据
        for (int i = 0; i < originalArray.length; i++) {
            for (int j = 0; j < originalArray[i].length; j++) {
                if (originalArray[i][j] != 0){
                    count++;
                    sparseArray[count][0] = i; //记录所在行
                    sparseArray[count][1] = j; //记录所在列
                    sparseArray[count][2] = originalArray[i][j]; //记录值
                }
            }
        }
        //输出稀疏数组
        System.out.println("稀疏数组:");
        for (int i = 0; i < sparseArray.length; i++) {
                System.out.printf("%d\t%d\t%d\n", sparseArray[i][0], sparseArray[i][1], sparseArray[i][2]);
        }
        //将稀疏数组恢复成为二维数组
        //1.根据稀疏数组的第一行初始化二维数组
        int[][] recoveredArray = new int[sparseArray[0][0]][sparseArray[0][1]];
        //2.读取稀疏数组的后面行数据,赋值给原始二维数组
        for (int i = 1; i < sparseArray.length; i++) {
            recoveredArray[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];
        }
        //输出二维数组
        System.out.println("恢复后的二维数组:");
        for (int[] row: recoveredArray) {
            for (int data : row) {
                System.out.printf("%d\t", data);
            }
            System.out.println();
        }
    }
}

运行结果

原始的二维数组:
0 0 0 0 1 0 0 0 1 0 
0 0 0 0 0 1 0 1 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 1 0 
0 0 0 0 0 1 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 0 0 0 
amount = 8
稀疏数组:
10  10  8
0 4 1
0 8 1
1 5 1
1 7 1
4 8 1
5 5 1
7 2 1
8 7 1
恢复后的二维数组:
0 0 0 0 1 0 0 0 1 0 
0 0 0 0 0 1 0 1 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 1 0 
0 0 0 0 0 1 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 1 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 0 0 0 

比对结果如下:


经过比较,恢复后的二维数组与原二维数组保持一致!

分析可知:原二维数组需要的空间为:10 * 10 *(int),而稀疏数组只需要 9 * 3 * (int),相对节省了73(int)的空间!

相关文章
|
17天前
|
人工智能 安全 Java
Java和Python在企业中的应用情况
Java和Python在企业中的应用情况
45 7
|
12天前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
37 5
|
10天前
|
监控 Java 数据库连接
Java线程管理:守护线程与用户线程的区分与应用
在Java多线程编程中,线程可以分为守护线程(Daemon Thread)和用户线程(User Thread)。这两种线程在行为和用途上有着明显的区别,了解它们的差异对于编写高效、稳定的并发程序至关重要。
19 2
|
20天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
42 6
|
17天前
|
关系型数据库 MySQL Java
MySQL索引优化与Java应用实践
【11月更文挑战第25天】在大数据量和高并发的业务场景下,MySQL数据库的索引优化是提升查询性能的关键。本文将深入探讨MySQL索引的多种类型、优化策略及其在Java应用中的实践,通过历史背景、业务场景、底层原理的介绍,并结合Java示例代码,帮助Java架构师更好地理解并应用这些技术。
22 2
|
19天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
20天前
|
Java 测试技术 API
Java 反射机制:深入解析与应用实践
《Java反射机制:深入解析与应用实践》全面解析Java反射API,探讨其内部运作原理、应用场景及最佳实践,帮助开发者掌握利用反射增强程序灵活性与可扩展性的技巧。
53 4
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
140 9
|
26天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
24 1
|
13天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
32 5