1.稀疏数组
定义
当一个数组大部分的元素为0,或为同一值时,可以采用稀疏数组来保存该数组
语法
1.先记录(原数组)一共有几行几列,有多少个不同的值
2.再将不同元素的行和列及值记录在一个小规模的数组中,从而缩小程序的规模
提问:当数组大部分为同一值时应该怎么做
应用需求
1.使用稀疏数组来保留类似棋盘,地图的二维数组
2.把稀疏数组存盘,并且可以重新恢复到原来的二维数组
二维数组转稀疏数组
1.遍历二维数组,读取二维数组中有效数据的个数sum;
2.根据sum可以创建稀疏数组 int [ sum + 1] [ 3];
3.将二维数组的有效数据存入到稀疏数组
稀疏数组装二维数组
1.根据稀疏数组的第一行,创建原始的二维数组
2.再读取稀疏数组后几行的数据,并赋予原始的二维数组
public class SparseArray {
public static void main(String[] args) {
// 创建一个原始的二维数组 11 * 11
// 0: 表示没有棋子, 1 表示 黑子 2 表蓝子
int chessArr1[][] = new int[11][11];
chessArr1[1][2] = 1;
chessArr1[2][3] = 2;
chessArr1[4][5] = 2;
// 输出原始的二维数组
System.out.println("原始的二维数组~~");
for (int[] row : chessArr1) {
for (int data : row) {
System.out.printf("%d\t", data);
}
System.out.println();
}
// 将二维数组 转 稀疏数组的思
// 1. 先遍历二维数组 得到非0数据的个数
int sum = 0;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if (chessArr1[i][j] != 0) {
sum++;
}
}
}
// 2. 创建对应的稀疏数组
int sparseArr[][] = new int[sum + 1][3];
// 给稀疏数组赋值
sparseArr[0][0] = 11;
sparseArr[0][1] = 11;
sparseArr[0][2] = sum;
// 遍历二维数组,将非0的值存放到 sparseArr中
int count = 0; //count 用于记录是第几个非0数据
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if (chessArr1[i][j] != 0) {
count++;
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = chessArr1[i][j];
}
}
}
// 输出稀疏数组的形式
System.out.println();
System.out.println("得到稀疏数组为~~~~");
for (int i = 0; i < sparseArr.length; i++) {
System.out.printf("%d\t%d\t%d\t\n", sparseArr[i][0], sparseArr[i][1], sparseArr[i][2]);
}
System.out.println();
//将稀疏数组 --》 恢复成 原始的二维数组
/*
* 1. 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组,比如上面的 chessArr2 = int [11][11]
2. 在读取稀疏数组后几行的数据,并赋给 原始的二维数组 即可.
*/
//1. 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];
//2. 在读取稀疏数组后几行的数据(从第二行开始),并赋给 原始的二维数组 即可
for(int i = 1; i < sparseArr.length; i++) {
chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
// 输出恢复后的二维数组
System.out.println();
System.out.println("恢复后的二维数组");
for (int[] row : chessArr2) {
for (int data : row) {
System.out.printf("%d\t", data);
}
System.out.println();
}
}
}
2.队列
介绍
队列是一个有序列表,可以用数组或是链表来实现
遵循先进先出
因为队列的输出和输入分别是从前后端来处理,因此需要两个变量front及rear分别及记录队列前后端的下标,front会随着数据输出而改变,而rear则是随着数据输入而改变
数组模拟实现队列
class Arrayqueue {
private int maxSize;
private int front;
private int rear;
private int[] arr;
//构造器
public Arrayqueue(int maxSize) {
this.maxSize = maxSize;
arr = new int[maxSize];
front = -1;//指向队列头部的前一个位置,不包含具体的数据
rear = -1;//指向队列尾部的具体的数据
}
//判断队列是否满
public boolean isFull() {
return rear == maxSize - 1;//因为数组是从0开始的
}
//判断队列是否为空
public boolean isEmpty() {
return rear == front;
}
//添加数据
public void addQueue(int n) {
if (isFull()) {
return;
}
rear++;
arr[rear] = n;
}
//出队列
public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("队列空");
}
front++;
return arr[front];
}
//显示队列的所有数据
public void showQueue(){
//遍历
if(isEmpty()){
System.out.println("队列为空");
}
for(int i = 0; i < arr.length; i++){
System.out.println(String.format("arr[%d] = %d\n",i,arr[i]));
}
}
//显示队列的头部数据,不是取出数据
public int headQueue(){
if(isEmpty()){
throw new RuntimeException("没有数据");
}
return arr[front+1];
}
}
环形队列
思路分析
1.front的含义调整为指向队列的第一个元素,初始值为0;
2.reard的含义调整为指向对列最后一个元素的后一个位置,因为要预留一个空间做约定,rear的初始值为0
3.当队列满时,条件为(rear + 1)%maxsize = front吗,即最后一个空间被front占据
4.当队列为空时,rear = front
5.队列中有效的数据个数:(rear + maxsize - front) % maxsize,模实际上只对rear有效果
class Arrayqueue {
private int maxSize;
private int front;
private int rear;
private int[] arr;
//构造器
public Arrayqueue(int maxSize) {
this.maxSize = maxSize;
arr = new int[maxSize];
front = -1;//指向队列头部的前一个位置,不包含具体的数据
rear = -1;//指向队列尾部的具体的数据
}
//判断队列是否满
public boolean isFull() {
return (rear + 1 ) % maxSize== front;
}
//判断队列是否为空
public boolean isEmpty() {
return rear == front;
}
//添加数据
public void addQueue(int n) {
if (isFull()) {
return;
}
arr[rear] = n;
rear = (rear + 1) % maxSize;
}
//出队列
public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("队列空");
}
int value = arr[front];
front = (front + 1) % front;
return arr[front];
}
//显示队列的所有数据
public void showQueue(){
//遍历
if(isEmpty()){
System.out.println("队列为空");
}
//思路:从front开始遍历,遍历多少个元素
//
for(int i = 0; i < front + size(); i++){
System.out.println(String.format("arr[%d] = %d\n",i % maxSize,arr[i % maxSize]));
}
}
//求出有效数据个数
public int size() {
return (rear + maxSize - front) % maxSize;
}
//显示队列的头部数据,不是取出数据
public int headQueue(){
if(isEmpty()){
throw new RuntimeException("没有数据");
}
return arr[front];
}
}
单向链表
特点
1.链表是以节点的方式来储存的
2.每个节点包含data域,next域(指向下一个节点)
3.链表的顺序是由每个next来决定的
4.链表分带头结点的链表和没有头结点的链表
应用
第一种方式:直接添加链表尾部
public class singlelinkedlisy {
public static void main(String[] args) {
//创建节点
HeroNode heroNode1 = new HeroNode(1, "lhb", "saui");
HeroNode heroNode2 = new HeroNode(2, "lh", "sau");
HeroNode heroNode3 = new HeroNode(3, "l", "sa");
//创建链表
SinglelinkedList singlelinkedList = new SinglelinkedList();
//加入
singlelinkedList.cadd(heroNode1);
singlelinkedList.cadd(heroNode3);
singlelinkedList.cadd(heroNode2);
singlelinkedList.List();
}
}
//定义SinglelinkedList
class SinglelinkedList {
//初始化头节点,头结点不要动,不存放具体的数据
private HeroNode head = new HeroNode(0, "", "");
//添加节点
//不考虑添加顺序
public void add(HeroNode node) {
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = node;
}
//显示链表
public void List() {
//判断是否为空
if (head.next == null) {
return;
}
HeroNode temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
class HeroNode {
public int no;
public String nams;
public String nickname;
public HeroNode next;
public HeroNode(int no, String nams, String nickname) {
this.no = no;
this.nams = nams;
this.nickname = nickname;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", nams='" + nams + '\'' +
", nickname='" + nickname +
'}';
}
}
双向链表
双向链表的遍历,添加,修改,删除
遍历
思路
方式和单向列表一样,只是可以从后往前
代码
public void list() {
// 判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
// 因为头节点,不能动,因此我们需要一个辅助变量来遍历
HeroNode2 temp = head.next;
while (true) {
// 判断是否到链表最后
if (temp == null) {
break;
}
// 输出节点的信息
System.out.println(temp);
// 将temp后移, 一定小心
temp = temp.next;
}
}
修改
思路
方式和单向列表一样
代码
public void update(HeroNode2 newHeroNode) {
// 判断是否空
if (head.next == null) {
System.out.println("链表为空~");
return;
}
// 找到需要修改的节点, 根据no编号
// 定义一个辅助变量
HeroNode2 temp = head.next;
boolean flag = false; // 表示是否找到该节点
while (true) {
if (temp == null) {
break; // 已经遍历完链表
}
if (temp.no == newHeroNode.no) {
// 找到
flag = true;
break;
}
temp = temp.next;
}
// 根据flag 判断是否找到要修改的节点
if (flag) {
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else { // 没有找到
System.out.printf("没有找到 编号 %d 的节点,不能修改\n", newHeroNode.no);
}
}
删除(比较一下单向和双向)
直接找到要删除的节点temp
temp.next.pre = temp.pre;
temp.pre.next = temp.next;
代码
public void del(int no) {
// 判断当前链表是否为空
if (head.next == null) {// 空链表
System.out.println("链表为空,无法删除");
return;
}
HeroNode2 temp = head.next; // 辅助变量(指针)
boolean flag = false; // 标志是否找到待删除节点的
while (true) {
if (temp == null) { // 已经到链表的最后
break;
}
if (temp.no == no) {
// 找到的待删除节点的前一个节点temp
flag = true;
break;
}
temp = temp.next; // temp后移,遍历
}
// 判断flag
if (flag) { // 找到
// 可以删除
// temp.next = temp.next.next;[单向链表]
temp.pre.next = temp.next;
// 这里我们的代码有问题?
// 如果是最后一个节点,就不需要执行下面这句话,否则出现空指针
if (temp.next != null) {
temp.next.pre = temp.pre;
}
} else {
System.out.printf("要删除的 %d 节点不存在\n", no);
}
}