数据结构和算法学习笔记一_数组_队列_链表
一、稀疏数组(压缩存储数组)
1.1、介绍
当一个数组种大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
1.2、稀疏数组的处理办法是:
- 记录数组一共有几行几列,有多少个不同的值
- 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
1.3、举例:五子棋程序,存盘退出和恢复上盘
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ec9X7eui-1605108410981)(D:\TyporaPic\image-.png)]](https://i-blog.csdnimg.cn/blog_migrate/6eeab4401a9b44382c58fbbc7199fe8a.png#pic_center)
因为该二维数组的很多值是默认值0,因此记录了很多没有意义的数据->转换成稀疏数组

二维数组转成稀疏数组的思路:
- 遍历原始的二位数组,得到有效数据的个数sum
- 根据sum就可以创建稀疏数组sparseArr int[sum+1][3]
- 将二维数组的有效数据存入到稀疏数组
稀疏数组转成二维数组的思路:
- 读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
- 读取稀疏数组除第一行的数组并赋值给原始的二维数组
1.4、代码实现
package com.lxf.sparsearray;
public class SparseArray {
public static void main(String[] args) {
//创建一个原始的二维数组11*11,假设有两个棋子:黑子在第二行第三列,蓝子在第三行第四列。
//0:表示没有棋子,1表示黑子 ,2表篮子
int charArr1[][] = new int[11][11];
charArr1[1][2]=1;
charArr1[2][3]=2;
charArr1[1][3]=1;
charArr1[1][4]=2;
charArr1[3][2]=1;
charArr1[2][2]=2;
//row column 长度
int rowL=charArr1.length;
int columnL=charArr1[0].length;
//输出原始的二维数组
System.out.println("原始的二维数组:");
for (int[] row : charArr1) {
for (int data : row) {
System.out.printf("%d\t",data);
}
System.out.println();
}
//原始二维数组转成稀疏数组
//1.
// 得到非0数据的个数
//记录数组中非零数的个数
int sum=0;
for (int[] row : charArr1) {
for (int data : row) {
if(data!=0){
sum++;
}
}
}
//2.创建对应的稀疏数组
int[][] sparseArr = new int[sum + 1][3];
//3.将二维数组的数赋值给稀疏数组
sparseArr[0][0]=rowL;
sparseArr[0][1]=columnL;
sparseArr[0][2]=2;
//记录稀疏数组行
int count=0;
for (int i = 0; i < rowL; i++) {
for (int j = 0; j < columnL; j++) {
if(charArr1[i][j]!=0){
count++;
sparseArr[count][0]=i;
sparseArr[count][1]=j;
sparseArr[count][2]=charArr1[i][j];
}
}
}
//4.打印稀疏数组
System.out.println("得到的稀疏数组为:");
for (int[] row : sparseArr) {
System.out.printf("%d\t%d\t%d\t",row[0],row[1],row[2]);
System.out.println();
}
//5.将稀疏数组恢复成二维数组
int[][] charArr2=new int[sum+1][3];
for (int i = 1; i < sparseArr.length; i++) {
charArr2[sparseArr[i][0]][sparseArr[i][1]]=sparseArr[i][2];
}
//输出恢复后的二维数组
System.out.println("恢复后的二维数组:");
for (int[] row : charArr2) {
for (int data : row) {
System.out.printf("%d\t",data);
}
System.out.println();
}
}
}
结果:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tiimeHw8-1605108411030)(D:\TyporaPic\image-.png)]](https://i-blog.csdnimg.cn/blog_migrate/c554fe454b88a67720b5759f2b9927fc.png#pic_center)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7LEFGRlX-1605108411047)(D:\TyporaPic\image-.png)]](https://i-blog.csdnimg.cn/blog_migrate/7d2bacd8075888fbb0c95f22eb7150b6.png#pic_center)
稀疏数组存入文件,从文件取出操作代码:
package com.lxf.sparsearray;
import java.io.*;
import java.util.Arrays;
public class SparseArray {
public static void main(String[] args) throws IOException {
//创建一个原始的二维数组11*11,假设有两个棋子:黑子在第二行第三列,蓝子在第三行第四列。
//0:表示没有棋子,1表示黑子 ,2表篮子
int charArr1[][] = new int[11][11];
charArr1[1][2]=1;
charArr1[2][3]=2;
charArr1[1][3]=1;
charArr1[1][4]=2;
charArr1[3][2]=1;
charArr1[2][2]=2;
//row column 长度
int rowL=charArr1.length;
int columnL=charArr1[0].length;
//原始二维数组转成稀疏数组
//1.
// 得到非0数据的个数
//记录数组中非零数的个数
int sum=0;
for (int[] row : charArr1) {
for (int data : row) {
if(data!=0){
sum++;
}
}
}
//2.创建对应的稀疏数组
int[][] sparseArr = new int[sum + 1][3];
//3.将二维数组的数赋值给稀疏数组
sparseArr[0][0]=rowL;
sparseArr[0][1]=columnL;
sparseArr[0][2]=2;
//记录稀疏数组行
int count=0;
for (int i = 0; i < rowL; i++) {
for (int j = 0; j < columnL; j++) {
if(charArr1[i][j]!=0){
count++;
sparseArr[count][0]=i;
sparseArr[count][1]=j;
sparseArr[count][2]=charArr1[i][j];
}
}
}
//将稀疏数组存储到文件中
File file=new File("C:\\Users\\Administrator\\Desktop\\test.txt");
if(file!=null&&!file.exists()){
file.createNewFile();
}
//输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
//先输出第一行
//写出到文件中
bos.write((rowL+","+columnL+","+2+"\r\n").getBytes());
bos.flush();
for (int i = 1; i < sparseArr.length; i++) {
//将每一行拼接成字符串
String str=sparseArr[i][0]+","+sparseArr[i][1]+","+sparseArr[i][2]+"\r\n";
//写出到文件中
bos.write(str.getBytes());
bos.flush();
}
//输入流,读取文件
BufferedReader br = new BufferedReader(new FileReader(file));
int[][] charArr2=new int[sum+1][3];
String s=null;
int count2=0;
while((s=br.readLine())!=null){
int[] split = Arrays.stream(s.split(",")).mapToInt(Integer::valueOf).toArray();
charArr2[count2][0]=split[0];
charArr2[count2][1]=split[1];
charArr2[count2][2]=split[2];
count2++;
}
for (int i = 0; i < charArr2.length; i++) {
System.out.printf("%d\t%d\t%d",charArr2[i][0],charArr2[i][1],charArr2[i][2]);
System.out.println();
}
}
}
二、队列
2.1、数组模拟队列

MaxSize:队列的最大容量,rear:尾端下标,front:前端下标
- 当我们将数据存入队列时称为"addQueue",addQueue的处理需要有两个步骤:思路分析
- 将尾指针往后移:rear+1,当front==rear【空】
- 若尾指针rear小于队列的最大下标maxSize-1,则将数据存入rear所指的数据元素中,否则无法存入数据。rear==maxSize-1【队列满】
代码实现:
package com.lxf.queue;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
*
* @author lxf
*/
public class ArrayQueueDemo {
public static void main(String[] args) throws IOException {
//测试
ArrayQueue arrayQueue = new ArrayQueue(3);
//接收输入的命令
char key = ' ';
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
boolean flag = true;
while (flag) {
System.out.println("输入e(esc)表示退出程序");
System.out.println("输入a(addQueue)表示往队列中加入一个数据");
System.out.println("输入r(removeQueue)表示从队列中取出一个数据");
System.out.println("输入s(showQueue)表示展示队列的所有数据");
System.out.println("输入h(headQueue)表示展示队列的头部第一个数据");
System.out.println("输入t(tailQueue)表示展示队列的尾部最后一个数据");
//获取输入的命令
key = br.readLine().charAt(0);
switch (key){
case 'e':
br.close();
flag=false;
break;
case 'a':
System.out.println("请输入您要添加的数据:");
int n = Integer.valueOf(br.readLine());
arrayQueue.addQueue(n);
break;
case 'r':
try {
int r = arrayQueue.removeQueue();
System.out.printf("取出的数据为:%d",r);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 's':
arrayQueue.showQueue();
break;
case 'h':
int h = 0;
try {
h = arrayQueue.headQueue();
System.out.printf("队列头部第一个数据为:%d",h);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 't':
int t = 0;
try {
t = arrayQueue.tailQueue();
System.out.printf("队列头部第一个数据为:%d",t);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
System.out.println("命令错误,请参考提示重新输入!");
break;
}
}
System.out.println("成功退出程序!");
}
}
/**
* 使用数组模拟队列-编写一个ArrayQueue类
*/
class ArrayQueue{
/**
* 表示数组的最大容量
*/
private int maxSize;
/**
* 队列头
*/
private int front;
/**
* 队列尾
*/
private int rear;
/**
* 该数组用于存放数据,模拟队列
*/
private int[] arr;
/**
* 创建队列的构造器
* @param maxSize 数组大小
*/
public ArrayQueue(int maxSize) {
this.maxSize = maxSize;
arr=new int[maxSize];
//指向队列头部,分析出front是指向队列头的前一个位置
front=-1;
//指向队列尾,指向队列尾的数据(即就是队列的最后一个数据)
rear=-1;
}
/**
* 判断队列是否满
* @return
*/
public boolean isFull(){
return rear==maxSize-1;
}
/**
* 判断队列是否为空
* @return
*/
public boolean isEmpty(){
return rear==front;
}
/**
* 添加数据到队列
* @param n 添加的数据
*/
public void addQueue(int n){
if(isFull()){
System.out.println("队列已满,无法加入数据!");
return;
}
//rear后移添加数据
arr[++rear]=n;
System.out.println("添加数据成功!");
}
/**
* 从队列中取出数据
* @return 取出的数据
*/
public int removeQueue(){
//判断队列是否为空
if(isEmpty()){
//抛出异常
throw new RuntimeException("队列空,不能取数据");
}
return arr[++front];
}
/**
* 显示队列的所有数据
*/
public void showQueue(){
if(isEmpty()){
System.out.println("队列为空,没有数据!");
return;
}
for (int i = front+1;; i < arr.length; i++) {
System.out.printf("arr[%d]=%d\n",i,arr[i]);
}
}
/**
* 显示头部第一个数
* @return
*/
public int headQueue(){
//判断
if(isEmpty()){
throw new RuntimeException("队列为空,没有数据!");
}
return arr[front+1];
}
/**
* 显示尾部最后一个数
* @return
*/
public int tailQueue(){
//判断
if(isEmpty()){
throw new RuntimeException("队列为空,没有数据!");
}
return arr[rear];
}
}
2.2、数组模拟环形队列
1.1出现问题出现并优化:
- 目前数组使用一次就不能再用,没有达到复用效果
- 将这个数组使用算法,改进成一个环形的队列 取模:%
思路如下:
- front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素front的初始值=0
- rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间做为约定,rear的初始值=0
- 当队列满时,条件时(rear+1)%maxSize=front【满】
- 当队列为空的条件,rear==front【空】
- 当我们这样分析,队列中有效的数据的个数(rear+maxSize-front)%maxSize
代码如下:
package com.lxf.queue;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
*
* @author lxf
*/
public class CircleArrayQueueDemo {
public static void main(String[] args) throws IOException {
//测试
System.out.println("测试数组模拟环形队列的案例:");
//说明设置4:其队列的有效数为3
CircleArray circleArray = new CircleArray(4);
//接收输入的命令
char key = ' ';
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
boolean flag = true;
while (flag) {
System.out.println("输入e(esc)表示退出程序");
System.out.println("输入a(addQueue)表示往队列中加入一个数据");
System.out.println("输入r(removeQueue)表示从队列中取出一个数据");
System.out.println("输入s(showQueue)表示展示队列的所有数据");
System.out.println("输入h(headQueue)表示展示队列的头部第一个数据");
System.out.println("输入t(tailQueue)表示展示队列的尾部最后一个数据");
//获取输入的命令
key = br.readLine().charAt(0);
switch (key){
case 'e':
br.close();
flag=false;
break;
case 'a':
System.out.println("请输入您要添加的数据:");
int n = Integer.valueOf(br.readLine());
circleArray.addQueue(n);
break;
case 'r':
try {
int r = circleArray.removeQueue();
System.out.printf("取出的数据为:%d",r);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 's':
circleArray.showQueue();
break;
case 'h':
int h = 0;
try {
h = circleArray.headQueue();
System.out.printf("队列头部第一个数据为:%d",h);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 't':
int t = 0;
try {
t = circleArray.tailQueue();
System.out.printf("队列头部第一个数据为:%d",t);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
default:
System.out.println("命令错误,请参考提示重新输入!");
break;
}
}
System.out.println("成功退出程序!");
}
}
class CircleArray{
/**
* 表示数组的最大容量
*/
private int maxSize;
/**
* front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]
* front的初始值=0
*/
private int front;
/**
* rear变量的含义做一个调整:rear指向队列的最后一个位置,因为希望空出一个空间做为约定
* rear的初始值=0
*/
private int rear;
/**
* 该数据用于存放数据,模拟队列
*/
private int[] arr;
/**
* 创建队列的构造器
* @param maxSize 数组大小
*/
public CircleArray(int maxSize) {
this.maxSize = maxSize;
arr=new int[maxSize];
}
/**
* 判断队列是否满
* @return
*/
public boolean isFull(){
return (rear+1)%maxSize==front;
}
/**
* 判断队列是否为空
* @return
*/
public boolean isEmpty(){
return rear==front;
}
/**
* 添加数据到队列
* @param n 添加的数据
*/
public void addQueue(int n){
if(isFull()){
System.out.println("队列已满,无法加入数据!");
return;
}
//rear添加数据再后移
arr[rear]=n;
rear=(rear+1)%maxSize;
System.out.println("添加数据成功!");
}
/**
* 从队列中取出数据
* @return 取出的数据
*/
public int removeQueue(){
//判断队列是否为空
if(isEmpty()){
//抛出异常
throw new RuntimeException("队列空,不能取数据");
}
//1.先把front对应的值保留到一个临时变量
//2.将front后移
//3.将临时保存的变量返回
int value=arr[front];
front=(front+1)%maxSize;
return value;
}
/**
* 显示队列的所有数据
*/
public void showQueue(){
if(isEmpty()){
System.out.println("队列为空,没有数据!");
return;
}
//从front开始遍历,遍历多少个元素
for (int i = front; i < front+size(); i++) {
System.out.printf("arr[%d]=%d\n",i%maxSize,arr[i%maxSize]);
}
}
/**
* 求出当前队列有效数据的个数
* @return
*/
private int size() {
return (rear+maxSize-front)%maxSize;
}
/**
* 显示头部第一个数
* @return
*/
public int headQueue(){
//判断
if(isEmpty()){
throw new RuntimeException("队列为空,没有数据!");
}
return arr[front];
}
/**
* 显示尾部最后一个数
* @return
*/
public int tailQueue(){
//判断
if(isEmpty()){
throw new RuntimeException("队列为空,没有数据!");
}
return arr[rear];
}
}
三、链表
链表是有序的列表,它在内存中是存储如下的:


3.1、单链表的应用
使用带head头的单向链表实现-水浒英雄排行榜管理完成对英雄人物的增删改查操作
- 第一种方法在添加英雄时,直接添加到链表的尾部

- 第二种方法在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示),思路分析图:

- 修改节点功能
修改节点的信息,根据英雄的no编号来找到对应节点后修改英雄信息
- 删除节点
- 我们先找到需要删除的这个节点的前一个节点temp
- temp.next=temp.next.next
- 被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收
代码如下:
package com.lxf.linklist;
/**
*单链表
* @author Administrator
*/
public class SingleLinkedListDemo {
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(4, "林冲", "豹子头");
//创建一个链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.addByOrder(hero2);
singleLinkedList.addByOrder(hero3);
singleLinkedList.addByOrder(hero1);
singleLinkedList.addByOrder(hero4);
//显示数据
singleLinkedList.list();
//修改成功一次
//singleLinkedList.update(new HeroNode(1, "宋江~", "及时雨~"));
//显示数据
//singleLinkedList.list();
//修改失败一次
//singleLinkedList.update(new HeroNode(11, "宋江~", "及时雨~"));
singleLinkedList.deleteNode(hero2);
singleLinkedList.deleteNode(hero1);
singleLinkedList.deleteNode(hero3);
singleLinkedList.deleteNode(hero4);
singleLinkedList.deleteNode(hero3);
}
}
/**
* 定义一个SingleLinkedList 管理我们的英雄
*/
class SingleLinkedList{
/**
* 先初始化一个头节点,头节点不要动,不存放具体的数据
*/
private HeroNode head=new HeroNode(0, "","");
/**
* 第一种方式添加节点到单向链表
* 思路(当不考虑编号顺序时):
* 1.找到当前链表的最后节点
* 2.将最后这个节点的next指向新的节点
* @param heroNode
*/
public void add(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助节点temp遍历
HeroNode temp=head;
while (true){
//找到链表的最后
if(temp.next==null){
break;
}
//如果没有找到最后,就将temp后移
temp=temp.next;
}
//当退出while循环时,temp就指向了链表的最后
//将这个节点加在最后即可
temp.next=heroNode;
}
/**
* 第二种方式在添加英雄时,根据排名插入到指定位置
* 思路(虑编号顺序时):
* 1.找到当前链表的最后节点
* 2.将最后这个节点的next指向新的节点
* @param heroNode
*/
public void addByOrder(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助节点temp遍历
HeroNode temp=head;
boolean flag=false;
while (true){
if(temp.next==null){
break;
}
//位置找到,就在temp的后面插入
if(temp.next.no>heroNode.no){
break;
}else if(temp.next.no==heroNode.no){
//说明编号存在
flag=true;
break;
}
//后移,遍历当前链表
temp=temp.next;
}
//判断flag的值
if(flag){
System.out.printf("准备插入的英雄的编号 %d 已经存在了,不能加入了\n",heroNode.no);
}else{
//插入到链表中,temp的后面
heroNode.next=temp.next;
temp.next=heroNode;
}
//当退出while循环时,temp就指向了链表的最后
temp.next=heroNode;
}
/**
* 显示链表
*/
public void list(){
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
//因为头节点不能动,因此我们需要一个辅助变量来遍历
HeroNode temp=head.next;
while (true){
//判断是否到链表的最后
if (temp == null) {
break;
}
//输出节点的信息
System.out.println(temp);
//将temp后移
temp=temp.next;
}
}
/**
* 修改节点的信息,根据no编号来修改,即no编号不能改
* 说明:
* 1.根据newHeroNode的no来修改即可
* @param newHeroNode
*/
public void update(HeroNode newHeroNode){
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空!");
return;
}
//找到需要修改的节点,根据no编号
//定义一个辅助变量
HeroNode temp=head.next;
while (temp!=null){
if(temp.no==newHeroNode.no){
//找到该节点,修改
temp.name=newHeroNode.name;
temp.nickName=newHeroNode.nickName;
System.out.println("修改节点成功!");
return;
}
temp=temp.next;
}
System.out.printf("没有找到编号为%d的英雄!",newHeroNode.no);
}
/**
* 根据英雄节点的no删除一个节点
* @param heroNode
*/
public void deleteNode(HeroNode heroNode){
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空!");
return;
}
HeroNode temp=head;
while (temp!=null){
if (temp.next.no==heroNode.no) {
//找到要删除的节点,直接将该节点从引用链上剔除,它最终会GC回收
temp.next=temp.next.next;
System.out.println("删除{"+heroNode+"}节点成功");
return;
}
temp=temp.next;
}
System.out.println("未找到{"+heroNode+"}节点");
}
}
/**
* 定义HeroNode,每个HeroNode对象就是一个节点
*/
class HeroNode{
/**
* 英雄编号
*/
public int no;
/**
* 英雄名字
*/
public String name;
/**
* 英雄昵称
*/
public String nickName;
/**
* 指向下一个节点
*/
public HeroNode next;
/**
* 构造器
* @param no 英雄编号
* @param name 英雄名字
* @param nickName 英雄昵称
*/
public HeroNode(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
3.2、单链表面试题(新浪、百度、腾讯)
单链表的常见面试题有如下:
- 求单链表中有效节点的个数
/**
* 方法:获取到单节点的节点的个数(如果是带头节点的链表,需不统计头节点)
* @param head 链表额头节点
* @return 返回的就是有效节点的个数
*/
public static int getLength(HeroNode head){
if(head.next==null){
//如果头节点的指向为null,说明该链表是空链表
return 0;
}
int length=0;
//定义一个辅助的变量
HeroNode cur=head.next;
while (cur!=null){
length++;
cur=cur.next;
}
return length;
}
-
查找单链表中的倒数第k个结点【新浪面试题】
- 思路:
- 1.编写一个方法,接收head节点,同时接收一个index
- 2.index表示是倒数第index个节点
- 3.先把链表从头到尾遍历的,得到链表的总的长度getLength
- 4.得到size后,我们从链表的第一个开始遍历(size-index)个,就可以得到
- 5.如果找到了,则返回该节点,否则返回null
/**查找单链表中的倒数第index个节点 * @param head 头节点 * @param index 倒数第几个的下标 * @return */ public static HeroNode findLastIndexNode(HeroNode head,int index){ //判断链表是否为空 if(head.next==null){ return null; } //循环整个链表的长度 int length = getLength(head); if(index>length||index<0){ //倒数第index比length还大,说明没有这个节点 return null; } //要获取的节点是第length-index个 int i = length - index+1; //记录当前的下标 int count=1; //因为头节点是固定的,所以取中间值来操作 HeroNode temp=head.next; while (count<i){ temp=temp.next; count++; } return temp; } -
单链表的反转【腾讯面试题】
/**
* 思路:顺序遍历链表,到达一个节点先取出它的下一个节点,然后再把它指向反向的那个节点
* 反转链表
* @param head 头节点
*/
public static void reverseLinkedList(HeroNode head){
//判断是否需要反向链表
if(head.next==null||head.next.next==null){
return;
}
//存储当前节点值
HeroNode cur=head.next;
//存储下一节点值
HeroNode nexNode=null;
//韩老师讲的太复杂了,这个就是为了存储上一个节点值的对象
//有了三个节点值:当前、下一节点、上一节点,就可以实现反转了
HeroNode reverseHead=new HeroNode(0, "", "");
while (cur!=null){
//获取下一节点值
nexNode=cur.next;
//将当前节点的引用指向上一节点,reverseHead.next刚开始为null
cur.next=reverseHead.next;
//将当前节点赋值给reverseHead对象,"存储上一节点值"
reverseHead.next=cur;
//当前节点等于下一节点,此时三个节点又构造好了
cur=nexNode;
}
//最后就是原链表头节点重新指向头节点就可以了
head.next=reverseHead.next;
}
-
从尾到头打印单链表【百度,要求方式1:反序遍历。方式2:Stack栈】
- 解决方法一:单链表的反转方法反转后打印,缺点:破坏链表原有结构(参考上题)
- 解决方法二:将链表按顺序入栈再出栈
/** * 反转打印单向链表 * @param head 头节点 */ public static void reversePrintLinkedList(HeroNode head){ //如果链表为空或者链表长度为1,无需操作直接返回 if(head.next==null||head.next.next==null){ return; } //新建一个栈存储链表 Stack<HeroNode> heroNodeStacks=new Stack<>(); HeroNode temp=head.next; while(temp!=null){ //顺序入栈 heroNodeStacks.push(temp); temp=temp.next; } while (!heroNodeStacks.empty()){ //出栈打印 System.out.println(heroNodeStacks.pop()); } } -
合并两个有序的单链表,合并之后的链表依然有序【课后练习】
/**
* 合并两个有序列表,合并后也有序
* @param head1
* @param head2
*/
public void mergeLinkedList(HeroNode head1,HeroNode head2) {
//定义两个中间节点
HeroNode temp1=head1.next;
HeroNode temp2=head2.next;
//记录temp1上一个节点
HeroNode preNode1=head1;
//记录temp2的下一节点
HeroNode nextNode2=null;
//判断两个链表是否为空
if(temp1==null||temp2==null) {
return;
}
//循环链表2的每一个数据
while(temp2!=null) {
//循环比较链表1的数据,找到对应的位置加入
while(temp1!=null) {
//一个一个比较,找到比找到它大的数,这个数的前一个位置就是要插入的位置
if(temp2.no<temp1.no) {
//链表2指向下一个数据
nextNode2=temp2.next;
//将链表2的数据加入链表1中
preNode1.next=temp2;
temp2.next=temp1;
//赋值链表1的开始位置,进入下一个循环
temp1=head1.next;
preNode1=head1;
break;
}
if(temp1.next==null) {
//如果这个数比链表的所有数据都大则加入链表1的最后位置
nextNode2=temp2.next;
//将链表2的数据加入链表1中
temp1.next=temp2;
temp2.next=null;
//赋值链表1的开始位置,进入下一个循环
temp1=head1.next;
preNode1=head1;
break;
}
//指向链表的下一个位置,直到找到对应位置
preNode1=temp1;
temp1=temp1.next;
}
temp2=nextNode2;
}
}
3.3、双向链表的增删改查

-
遍历和单链表一样,只是可以向前,也可以向后查找
-
添加(默认加到双向链表的最后)
- 先找到双向链表的最后这个节点
- temp.next=newHeroNode
- newHeroNode.pre=temp;
-
修改思路和原理同单向链表一样。
-
删除
- 因为是双向链表,因此,我们可以实现自我删除某个节点
- 直接找到要删除的这个节点,比如temp
- temp.pre.next=temp.next
- temp.next.pre=temp.pre
代码如下:
package com.lxf.linklist; /** * * @author lxf */ public class DoubleLinkedListDemo { public static void main(String[] args) { //测试 DoubleLinkedList doubleLinkedList=new DoubleLinkedList(); HeroNodeD hero1 = new HeroNodeD(1, "宋江", "及时雨"); HeroNodeD hero2 = new HeroNodeD(2, "卢俊义", "玉麒麟"); HeroNodeD hero3 = new HeroNodeD(3, "吴用", "智多星"); HeroNodeD hero4 = new HeroNodeD(4, "林冲", "豹子头"); doubleLinkedList.addNode(hero1); doubleLinkedList.addNode(hero2); doubleLinkedList.addNode(hero3); doubleLinkedList.addNode(hero4); doubleLinkedList.list(); System.out.println(); doubleLinkedList.deleteNode(hero3); doubleLinkedList.list(); System.out.println(); doubleLinkedList.update(new HeroNodeD(2, "有用", "有才")); doubleLinkedList.list(); System.out.println(); } } class DoubleLinkedList{ /** * 初始化头节点 */ private HeroNodeD headD=new HeroNodeD(0, "",""); public HeroNodeD getHeadD() { return headD; } /** * 显示链表 */ public void list(){ //判断链表是否为空 if (headD.next == null) { System.out.println("链表为空"); return; } //因为头节点不能动,因此我们需要一个辅助变量来遍历 HeroNodeD temp=headD.next; while (true){ //判断是否到链表的最后 if (temp == null) { break; } //输出节点的信息 System.out.println(temp); //将temp后移 temp=temp.next; } } /** * */ public void addNode(HeroNodeD newHeroNode){ //因为head节点不能动,因此我们需要一个辅助节点temp遍历 HeroNodeD temp=headD; while (true){ //找到链表的最后 if(temp.next==null){ break; } //如果没有找到最后,就将temp后移 temp=temp.next; } //当退出while循环时,temp就指向了链表的最后 //将这个节点加在最后即可 //并将新加的节点指向先前的尾节点 temp.next=newHeroNode; newHeroNode.pre=temp; } /** * 修改节点的信息,根据no编号来修改,即no编号不能改 * 说明: * 1.根据newHeroNode的no来修改即可 * @param newHeroNodeD */ public void update(HeroNodeD newHeroNodeD){ //判断链表是否为空 if (headD.next == null) { System.out.println("链表为空!"); return; } //找到需要修改的节点,根据no编号 //定义一个辅助变量 HeroNodeD temp=headD.next; while (temp!=null){ if(temp.no==newHeroNodeD.no){ //找到该节点,修改 temp.name=newHeroNodeD.name; temp.nickName=newHeroNodeD.nickName; System.out.println("修改节点成功!"); return; } temp=temp.next; } System.out.printf("没有找到编号为%d的英雄!",newHeroNodeD.no); System.out.println(); } /** * 根据英雄节点的no删除一个节点 * 1.对于双向链表,我们可以直接找到这个要被删除的节点,找到后,自我删除即可 * @param heroNodeD */ public void deleteNode(HeroNodeD heroNodeD){ //判断链表是否为空 if (headD.next == null) { System.out.println("链表为空!"); return; } HeroNodeD temp=headD.next; //标志是否找到待删除的节点 boolean flag=false; while (true){ if(temp==null){ break; } //找到该节点 if(temp.no==heroNodeD.no){ flag=true; break; } temp=temp.next; } //未找到,打印信息并返回 if(!flag){ System.out.println("未找到{"+heroNodeD+"}节点"); return; } //找到直接将该节点删除(如果删除的节点是最后一个节点特殊处理) temp.pre.next=temp.next==null?null:temp.next; temp.next.pre=temp.pre; } } /** * 定义HeroNode,每个HeroNode对象就是一个节点 */ class HeroNodeD{ /** * 英雄编号 */ public int no; /** * 英雄名字 */ public String name; /** * 英雄昵称 */ public String nickName; /** * 指向上一个节点 */ public HeroNodeD pre; /** * 指向下一个节点 */ public HeroNodeD next; /** * 构造器 * @param no 英雄编号 * @param name 英雄名字 * @param nickName 英雄昵称 */ public HeroNodeD(int no, String name, String nickName) { this.no = no; this.name = name; this.nickName = nickName; } @Override public String toString() { return "HeroNode{" + "no=" + no + ", name='" + name + '\'' + ", nickName='" + nickName + '\'' + '}'; } }家庭作业:双向链表的第二种添加方式,按照编号顺添加
/**
* 根据编号顺序添加节点
* @param heroNewNodeD
*/
public void addNodeByOrder(HeroNodeD heroNewNodeD){
//判断双向链表是否为空
if(headD.next==null){
return;
}
HeroNodeD temp=headD.next;
while(temp!=null){
//找到比新加节点大的节点然后插入这个节点之前
if(temp.no>heroNewNodeD.no){
//temp的前一个节点next指向新节点
temp.pre.next=heroNewNodeD;
//新节点pre指向前一个节点
heroNewNodeD.pre=temp.pre;
//新节点next指向temp
heroNewNodeD.next=temp;
//temp的前一节点指向新节点
temp.pre=heroNewNodeD;
break;
}
//直到查找到链表尾部仍未找到
if(temp.next==null){
//temp的next指向新节点
temp.next=heroNewNodeD;
//新节点的pre指向temp
heroNewNodeD.pre=temp;
//新节点next指向null(可以不写,默认为null)
heroNewNodeD.next=null;
break;
}
temp=temp.next;
}
}
3.4、环形列表
3.4.1、单向环形链表

构建一单向的环形链表思路:
- 先创建一个节点,让first指向该节点,并形成环形
- 后面当我们每创建一个新的节点,就把该节点加入到已有的环形链表中即可
遍历环形链表
- 先让一个辅助指针(变量),指向firs节点
- 然后通过一个while循环遍历该环形链表即可,当curBoy.next==first结束
代码演示:
package com.lxf.linklist;
public class Josephu {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList=new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.list();
}
}
/**
* 创建一个环形的单向链表
*/
class CircleSingleLinkedList{
//创建一个first节点,当前没有编号
private Boy first=new Boy(-1);
/**
* 添加小孩节点,构建成一个环形的链表
*/
public void addBoy(int nums){
//nums做一个简单的数据校验
if(nums<1){
System.out.println("nums的值不正确!");
return;
}
//辅助变量,帮助构建环形链表
Boy curBoy=null;
//for循环创建环形链表
for (int i = 1; i <= nums; i++) {
Boy boy=new Boy(i);
if(i==1){
//第一步比较特殊
//首先头节点要等于这个boy,first的下一个节点等于自己,形成环
//再记录现在的节点为first
first=boy;
first.setNext(first);
curBoy=first;
continue;
}
//第二步及之后的只需要切入就行
boy.setNext(curBoy.getNext());
curBoy.setNext(boy);
curBoy=boy;
}
}
/**
* 遍历当前的环形链表
*/
public void list(){
//判断链表是否为空
if (first == null) {
System.out.println("没有任何小孩");
return;
}
//因为first不能动,因此我们仍然使用一个指针完成遍历
Boy curBoy=first;
while(true){
System.out.printf("小孩的编号%d \n",curBoy.getNo());
if(curBoy.getNext()==first){
break;
}
curBoy=curBoy.getNext();
}
}
}
/**
* 创建一个Boy类,表示一个节点
* @author lxf
*/
class Boy{
private int no;//编号
private Boy next;//指向下一个节点,默认Null
public Boy(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}

Josephu问题代码解决:
/**
* 根据用户输入,计算出小孩出圈的顺序
* @param startNo 表示从第几个小孩开始数数 k
* @param countNum 表示数几下 m
* @param nums 表示最初有多少小孩在圈中 n
*/
public void countBoy(int startNo,int countNum,int nums){
//对数据进行校验
if(first.getNo()<0||startNo<1||startNo>nums||countNum<1){
System.out.println("参数输入有误,请重新输入");
return;
}
//1.从第几个数字开始数,first就移动到哪个位置
for (int i = 1; i < startNo; i++) {
first=first.getNext();
}
//创建要给辅助指针,帮助完成小孩出道
Boy helper=first;
//2.需求创建一个辅助指针(变量)helper,事先应该指向环形链表的最后这个节点
while(true){
if (helper.getNext()==first) {
//说明helper指向最后小孩节点
break;
}
helper=helper.getNext();
}
//当小孩报数时,让first和helper指针同时的移动m-1次,然后出圈
//这里是一个循环操作,直到圈中只有一个节点
while(true){
if(helper==first){//说明圈中只有一个节点
break;
}
//让first和helper指针同时的移动m-1次
for (int i = 0; i < countNum - 1; i++) {
first=first.getNext();
helper=helper.getNext();
}
//这时first指向的节点,就是要出圈的小孩节点
System.out.printf("小孩%d出圈\n",first.getNo());
//这时将first指向的小孩节点出圈
helper.setNext(first.getNext());
first=helper.getNext();
}
System.out.printf("小孩%d出圈\n",first.getNo());
}
本文介绍了稀疏数组、队列及链表等基本数据结构的概念、应用场景及实现方式,包括数组模拟队列、环形队列、单链表、双向链表、环形链表等,提供了丰富的代码实例。
237

被折叠的 条评论
为什么被折叠?



