数据结构系列第一部分:表

1)文笔有限,如果发现博客有书写有误的地方恳请读者直言不讳,我一定会第一时间改正。
2)代码的具体实现可以参考代码中的注释,如果由于注释不清楚而不明白相应原理,可以与作者私聊。码字不易,有兴趣的小伙伴点个赞呗,大家相互学习。
3)本篇博客为数据结构系列第一部分:表,如需了解数据结构的其它部分,欢迎点击链接。

  1. 数据结构系列绪论部分:为什么需要数据结构
  2. 数据结构系列第一部分:
  3. 数据结构系列第二部分:
  4. 数据结构系列第三部分:
  5. 数据结构系列第四部分:散列
  6. 数据结构系列第五部分:递归
  7. 数据结构系列第六部分:排序
  8. 数据结构系列第七部分:查找

1 概论

表是一种表示数据元素之间存在一个对一个的关系的数据结构,对应Java中的数组(顺序表)、队列、链表、栈

参考【传送门】
 

2 数组

2.1 定义

属性 特征
特点 ① 使用连续分配的内存空间;②一次申请一大段连续的空间, 需要事先声明最大可能要占用的固定内存空间。
优点 设计简单,读取与修改表中任意一个元素的时间都是固定的。
缺点 ① 容易造成内存的浪费;② 删除或插入数据需要移动大量的数据。
总结 适合查询操作远多于插入、删除和修改操作的场景,同时是后续其它数据结构的基石。

2.2 操作

属性 特征
(读内存) 每个元素都有一个数值下标, 可以通过下标瞬间定位到某个元素;
(写内存) 可以在数组的任意位置添加元素,但是插入元素会引起部分元素后移,代价非常高;
(写内存) 去掉元素,引起部分元素前移,代价非常高;
(写内存) 只需要修改对应位置的元素的内容,不需要申请或者删除空间。

2.3 稀疏数组

2.3.1 定义

当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。

稀疏数组的处理方法是:

  1. 记录数组一共有几行几列,有多少个不同的值;

  2. 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模。

如下所示,可以将左边稀疏数组转换为右边的数组:
|

2.3.2 代码实现

利用代码实现上述稀疏数组与二维数组的转换。

/**
 * 稀疏数组与二维数组的转换
 */
public class SparseArray {
   
    public static void main(String[] args) {
   
        /*
        1 从二维数组转化到稀疏数组
         */
        //1.1 首先定义一个二维数组
        //11行,12列
        int[][] chessArr = new int[6][7];
        chessArr[0][3] = 22;
        chessArr[0][6] = 15;
        chessArr[1][1] = 11;
        chessArr[1][5] = 17;
        chessArr[2][3] = -6;
        chessArr[3][5] = 4;
        chessArr[4][0] = 91;
        chessArr[5][2] = 28;
        //1.2 控制台输出这个二维数组,按照数组的形式输出
        //因为,我不需要知道数组中每一个元素对应的位置坐标,所以可以直接使用foreach
        System.out.println();
        System.out.println("这里输出的是原来的二维数组:");
        for (int[] row : chessArr) {
   
            for (int element : row) {
   
                //printf表示格式化输出
                System.out.printf("%d\t", element);
            }
            System.out.println();//换行
        }
        //1.3首先我要遍历得到二维数组中,哪些元素的值为非0
        //二维数组的大小,二维数组中非0值的数量,二维数组中非0值的位置与值的大小
        int length = chessArr[0].length;
        int height = chessArr.length;
        int num = 0;
        //应该是先输出height,即表示行的意思
        for (int i = 0; i < height; i++) {
   
            for (int j = 0; j < length; j++) {
   
                if (chessArr[i][j] != 0) {
   
                    num++;
                }
            }
        }
        //1.4 根据上步得到的值创建稀疏数组
        int[][] sparseArr = new int[num + 1][3];
        sparseArr[0][0] = height;
        sparseArr[0][1] = length;
        sparseArr[0][2] = num;
        int count = 0;
        for (int i = 0; i < height; i++) {
   
            for (int j = 0; j < length; j++) {
   
                if (chessArr[i][j] != 0) {
   
                    count = count + 1;
                    sparseArr[count][0] = i;
                    sparseArr[count][1] = j;
                    sparseArr[count][2] = chessArr[i][j];
                }
            }
        }
        //1.5 控制台输出这个稀疏数组。另一种方法输出数组
        System.out.println();
        System.out.println("接下来输出稀疏数组:");
        for (int[] ints : sparseArr) {
   
            System.out.printf("%d\t%d\t%d\t\n", ints[0], ints[1], ints[2]);
        }
        System.out.println();


        /*
        第一步完成,接下来进行第二步
        2 将稀疏数组重新转化为原来的二维数组
         */
        //2.1 根据稀疏数组的第一行建立一个规定大小的二维数组
        int[][] newChessArr = new int[sparseArr[0][0]][sparseArr[0][1]];
        //先行后列
        for (int i = 1; i < sparseArr.length; i++) {
   
            newChessArr[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
        }
        //2.2 控制台输出稀疏数组
        System.out.println("接下来输出的是转化为原来的二维数组:");
        for (int[] ints : newChessArr) {
   
            for (int j = 0; j < newChessArr[0].length; j++) {
   
                System.out.printf("%d\t", ints[j]);
            }
            System.out.println();
        }
    }
}

 

3 链表

3.1 单向链表

3.1.1 概念

链表是一种物理存储结构上非连续、非顺序,然而在逻辑结构上是头接着尾、连续的数据结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表由一系列节点组成,节点可以在运行时动态生成。
在这里插入图片描述
每个结点包括两个部分:一个是存储数据元素的数据域,即 data 域;另一个是存储下一个结点地址的指针域,即 next 域

由于 Java 中不存在指针的概念,所以用 Java 实现链表时, next 域申明的类型是节点的类型,有点类似于递归,无限套娃。

3.1.2 实现原理和应用实例

利用一个应用实例来掌握链表的原理以及链表是如何实现的,具体操作如下图所示:
在这里插入图片描述
应用实例:使用带头节点的单向链表实现水浒英雄排行榜管理的增、删、改、查操作。

1)增加节点

① 在链表末尾增加节点:

  1. 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
  2. 创建一个临时节点,初始化为tempNode=headNode;
  3. 然后利用头节点不断遍历该链表,直到遍历到链表尾部(判断条件:tempNode.next=null)。遍历完链表的tempNode其实就相当于链表本身;
  4. 将新加入的数据放在链表尾部(tempNode.next=newHeroNode)。

② 在链表中按照序号加入节点:

  1. 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
  2. 创建一个临时节点,初始化为tempNode=headNode;
  3. 然后利用头节点不断遍历该链表,直到遍历到新加入节点序号的前一位(判断条件:tempNode.next.no>newHeroNode.no);
  4. 按照下图所示插入节点:
    在这里插入图片描述

2)删
5. 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
6. 创建一个临时节点,初始化为tempNode = headNode;
7. 然后利用头节点不断遍历该链表,直到遍历到需要删除节点的前一个节点。(tempNode.next = heroNode);
8. 利用 tempNode.next = tempNode.next.next 将该节点删除,该语法有效的前提是Java的自动垃圾回收机制。

3)改
9. 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
10. 创建一个临时节点,初始化为 tempNode = headNode;
11. 然后利用头节点不断遍历该链表,直到遍历到需要删除节点的前一个节点;
12. 改的原则就是四个域中,只需修改 name 域与 nickname 域,no 不必修改。

4) 查

  1. 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
  2. 创建一个临时节点,初始化为 tempNode=headNode;
  3. 利用临时节点不断遍历列表,然后通过重写 toString 方法来获得对象的全部属性到控制台。

在实际代码的时候是 System.out.println(tel); 输出 包名.类名@哈希码。这不是最重要的,重要的是,我们可以重写toString()方法来获得对象的全部属性,重写好之后的toString的作用就是控制台输出对象的全部属性 。

3.1.3 代码实现

/**
 * 1、复现出单链表的实现(描述梁山好汉)
 * 2、实现单链表的增、删、改、查
 * 3、利用面向对象的编程思想来写
 * 4、按照设计模式来写的话,这三个类应该要放在不同的包内,但是为了简单起见,我把它们放在一个文件中
 */
public class SingleLinkedListView {
   
    public static void main(String[] args) {
   
        //首先可以先建立一些节点
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(2, "林冲", "豹子头");

        //这里不能将上面的英雄直接赋值给singlelinkedlist,因为我没有在singlelinkedlist类中设置构造函数
        SingleLinkedList singlelinkedlist = new SingleLinkedList();

        //从尾部加入节点
        singlelinkedlist.addTailNode(hero1);
        singlelinkedlist.addTailNode(hero3);

        //从任意位置加入节
        //正常加入
        singlelinkedlist.addMiddleNode(hero2);
//            singlelinkedlist.replaceNode(hero4);
        //测试自己的Exception类
//            singlelinkedlist.addMiddleNode(hero3);
        singlelinkedlist.deleteNode(1);



        //显示链表
        System.out.println("显示链表的信息……");
        singlelinkedlist.shownode();
    }

}

/**
 * 用来存放基本类,这是一种面向对象的编程思想
 */
class SingleLinkedList {
   
    //首先定义一个头节点:头节点的作用仅仅是表示单链表的头
    public HeroNode headNode = new HeroNode(0, "", "");


    //增、删、改、查
    //首先书写增。增有两种情况,一种是在链表末尾增加节点,另一种是在链表中增加节点。
    /**
     * 1.1 在链表末尾增加节点
     * 通过一个辅助变量tempnode来遍历,帮助遍历整个链表
     *
     * @param heroNode 加入的新节点
     */
    //直接将头节点赋值给临时节点,这样就类似于数组的直接赋值一样。
    public void addTailNode(HeroNode heroNode) {
   
        HeroNode tempNode = headNode;
        while (true) {
   
            if (tempNode.next == null) {
   
                break;
            }
            //就相当于是循环语法中的n=n+1
            tempNode = tempNode.next;
        }
        tempNode.next = heroNode;
    }

    /**
     * 1.2 在链表中间,根据节点的序号来插入节点,这样的话,上述在链表尾部增加节点可以看成是这种情况的特例
     *
     * @param heroNode 加入的节点
     */
    public void addMiddleNode(HeroNode heroNode) {
   
        //flag来判断该节点的序号在链表中是否存在
        //要考虑三种情况:
        //1 新加入节点的序号在尾节点
        //2 新加入的节点的序号在链表中已经存在,所以,要做一个报错处理
        //3 新加入的节点的序号在链表中不存在,允许加入
        boolean flag = false;
        HeroNode tempNode = headNode;
        while (true) {
   
            if(tempNode.next==null){
   
                break;
            }
            if (tempNode.next.no==heroNode.no){
   
                flag=true;
                break;
            }
            else if (tempNode.next.no>heroNode.no){
   
                break;
            }
            tempNode=tempNode.next;
        }
        if (flag) {
   
            System.out.println("该链表中已经存在该序号的节点\n");
        } else {
   
            heroNode.next=tempNode.next;
            tempNode.next=heroNode;
        }

    }

    /**
     * 2 删。因此tempNode初始定义为headNode,所以,它之后就代表了这条链表
     * 很好,没有补位,只是将这个节点删除了。
     */
    public void deleteNode(int no){
   
        boolean flag=false;
        HeroNode tempNode = headNode;
        while(true){
   
            if(tempNode.next==null){
   
                break;
            }
            if(tempNode.next.no==no)
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值