1)文笔有限,如果发现博客有书写有误的地方恳请读者直言不讳,我一定会第一时间改正。
2)代码的具体实现可以参考代码中的注释,如果由于注释不清楚而不明白相应原理,可以与作者私聊。码字不易,有兴趣的小伙伴点个赞呗,大家相互学习。
3)本篇博客为数据结构系列第一部分:表,如需了解数据结构的其它部分,欢迎点击链接。
1 概论
表是一种表示数据元素之间存在一个对一个的关系的数据结构,对应Java中的数组(顺序表)、队列、链表、栈。
参考【传送门】
2 数组
2.1 定义
属性 | 特征 |
---|---|
特点 | ① 使用连续分配的内存空间;②一次申请一大段连续的空间, 需要事先声明最大可能要占用的固定内存空间。 |
优点 | 设计简单,读取与修改表中任意一个元素的时间都是固定的。 |
缺点 | ① 容易造成内存的浪费;② 删除或插入数据需要移动大量的数据。 |
总结 | 适合查询操作远多于插入、删除和修改操作的场景,同时是后续其它数据结构的基石。 |
2.2 操作
属性 | 特征 |
---|---|
查(读内存) | 每个元素都有一个数值下标, 可以通过下标瞬间定位到某个元素; |
增(写内存) | 可以在数组的任意位置添加元素,但是插入元素会引起部分元素后移,代价非常高; |
删(写内存) | 去掉元素,引起部分元素前移,代价非常高; |
改(写内存) | 只需要修改对应位置的元素的内容,不需要申请或者删除空间。 |
2.3 稀疏数组
2.3.1 定义
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:
-
记录数组一共有几行几列,有多少个不同的值;
-
把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模。
如下所示,可以将左边稀疏数组转换为右边的数组:
|
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)增加节点
① 在链表末尾增加节点:
- 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
- 创建一个临时节点,初始化为tempNode=headNode;
- 然后利用头节点不断遍历该链表,直到遍历到链表尾部(判断条件:tempNode.next=null)。遍历完链表的tempNode其实就相当于链表本身;
- 将新加入的数据放在链表尾部(tempNode.next=newHeroNode)。
② 在链表中按照序号加入节点:
- 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
- 创建一个临时节点,初始化为tempNode=headNode;
- 然后利用头节点不断遍历该链表,直到遍历到新加入节点序号的前一位(判断条件:tempNode.next.no>newHeroNode.no);
- 按照下图所示插入节点:
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) 查
- 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
- 创建一个临时节点,初始化为 tempNode=headNode;
- 利用临时节点不断遍历列表,然后通过重写 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)