一.稀疏数组
1.1 稀疏数组sparsearray
1.1.1一个实际需求
编写的五子棋程序中,有存盘退出和续上盘的功能。
➢分析问题:因为该二维数组的很多值是默认值0,因此记录了很多没有意义的数据->所以可以采用稀疏数组来记录。
1.1.2基本介绍
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:记录数组一共有几行几列,有多少个不同的值把具有不同值的元素的行列及值记录在-一个小规模的数组中,从而缩小程序的规模
1.1.2思路分析
二维数组转稀疏数组的思路
1.遍历原始的二_维数组,得到有效数据的个数sum .
2.根据sum就可以创建稀疏数组sparseArr int[sum+1] [3]
3.将二维数组的有效数据数据存入到稀疏数组
package sparsearray;
import java.io.*;
public class Sparsearray {
//二维数组转稀疏数组
//1代表黑子 2代表蓝子 0代表没有棋子
public static void main(String[] args) throws IOException {
int[][] ints = new int[11][11];
ints[1][2] =1;
ints[2][3] =2;
System.out.println("**********原数组***********");
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
System.out.print(ints[i][j]+"\t");
}
System.out.println();
}
System.out.println("************稀疏数组*************");
//1.先遍历二维数组 得到非零数据的个数
int sum = 0;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if(ints[i][j] != 0){
sum++;
}
}
}
//2.创建对应的稀疏数组
int[][] sparsearray = new int[sum + 1][3];
sparsearray[0][0] = 11;
sparsearray[0][1] = 11;
sparsearray[0][2] = sum;
int count = 1;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if(ints[i][j] != 0){
sparsearray[count][0] = i;
sparsearray[count][1] = j;
sparsearray[count][2] = ints[i][j];
count++;
}
}
}
for (int i = 0; i < sum+1; i++) {
for (int j = 0; j < 3; j++) {
System.out.print(sparsearray[i][j]+"\t");
}
System.out.println();
}
//将稀疏数组中的数据写到文件夹中(其实可以在上面的循环中就可以实现数据存储的,单独拿出来是为了理解!!!)
FileWriter fw = new FileWriter("data.txt");//字符流
for (int[] ints2 : sparsearray) {
for (int i = 0; i < 3; i++) {
String srr =String.valueOf(ints2[i]);
//笔记,将"123134"字符串变成整型 int num = Integer.valueOf("123135");
fw.write(srr+"\t");
}
fw.write("\n");
}
fw.close();
}
}
稀疏数组转原始的二维数组的思路
1.先读取稀疏数组的第-行,根据第- -行的数据,创建原始的二维数组,比如上面的chessArr2= int[11][11]
2.在读取稀疏数组后几行的数据,并赋给原始的二维数组即可
public class IOSparsearray {
public static void main(String[] args) throws IOException {
ArrayList<Integer> sparsearray = new ArrayList<>();
FileReader fr = new FileReader("data.txt");
BufferedReader bfr = new BufferedReader(fr);
String line = bfr.readLine();
while(line != null){
String[] strings = line.split("\t");
for (int j = 0; j < strings.length; j++) {
sparsearray.add(Integer.valueOf(strings[j]));//比较垃圾qvq,按理来说我应该让他变成二维数组,但我搞了很久
} //以后会了之后会回来修改(如果大佬你写出来了几个call我)
line = bfr.readLine();
}
System.out.println(sparsearray);
//还原数组
System.out.println("*********原数组********");
int[][] ints = new int[sparsearray.get(0)][sparsearray.get(1)];
for (int i = 1; i <= sparsearray.get(2) ; i++) {
ints[sparsearray.get(i*3)][sparsearray.get(i*3+1)] = sparsearray.get(3*i+2);
}
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
System.out.print(ints[i][j]+"\t");
}
System.out.println();
}
bfr.close();
}
}
.
二.队列
2.2.1队列的介绍
➢队列是一个有序列表,可以用数组(顺序存储)或是链表(链式存储)来实现。
➢遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
➢示意图: (使用数组模拟队列示意图)
2.2.2数组模拟队列思路
➢队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图,其中maxSize 是该队列的最大容量。
➢因为队列的输出、输入是分别从前后端来处理,因此需要两个变量front及rear分别记录队列前后端的下标,front会随着数据输出而改变,而rear则是随着数据输入而改变,如上图所示.
➢当我们将数据 存入队列时称为”addQueue",addQueue 的处理需要有两个步骤:思路分析
1)将尾指针往后移: rear+1, 当front== rear [空]
2)若尾指针rear 小于队列的最大下标maxSize-1, 则将数据存入rear 所指的数组元素中,否则无法存入数据。rear == maxSize - 1[队列满]
2.2.3代码实现
package Demo02queue;
import java.util.Scanner;
public class ArrayQueueDemo {
public static void main(String[] args) {
ArrayQueue queue = new ArrayQueue(3);
char key =' ';//接收用户输入
Scanner scanner = new Scanner(System.in);
boolean loop = true;
while(loop){
System.out.println("s(show): 显示队列");
System.out.println("e(exit): 退出程序" );
System.out.println("a(add): 添加数据队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列的数据");
key =scanner.next().charAt(0);//接收一个字符
switch(key){
case 'a':
System.out.println("输入一个数");
int value = scanner.nextInt();
queue.addQueue(value);
break;
case 's':
queue.showQueue();
break;
case 'g'://取出数据
try {
int res = queue.getQueue();
System.out.println("取出的数据是"+res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
int res = queue.headQueue();
System.out.println("队列的头序列是"+res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'e':
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出!");
}
}
//使用数组模拟队列-编写一个ArrayQueue类
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;//指向队列头部,分析出front是指向队列头的前一个位置。
rear = -1;//指向队列尾,指向队列尾的数据(即就是队列的最后一个数据)
}
//判断队列是否已满
public boolean isFull(){
return rear == maxSize - 1;
}
//判断队列是否为空
public boolean isEmpty(){
return rear ==front;
}
//添加数据到队列
public void addQueue(int n){
//判断队列是否已满
if(isFull()){
System.out.println("队列已满,不能加入数据");
return;//直接结束方法
}
rear++;//让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("arr["+i+"]="+arr[i]);
}
}
//显示队列的头数据(不是取出数据)
public int headQueue(){
if(isEmpty()){
throw new RuntimeException("队列为空,没有数据");
}
return arr[front+1];
}
}
问题分析并优化
1)目前的数组使用一次就不能用了,没有实现服用效果.
2)将这个数组使用算法,改进成一个环形队列,用取模实现
2.3环形队列
2.3.1思路
1)front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素。(front的初始值为0)
2)rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置,因为希望空出一个空间作为约定。(由于出现了预留空间,那么队列的长度最长也是规定的数组的长度减一)(rear的初始值为0)
3)当队列满时,条件是(rear + 1) % maxSize= front [满]
4)对队列为空的条件,rear == front空
5)当我们这样分析,队列中有效的数据的个数(rear + maxSize- front) % maxSize (rear= 1 front=0)
import java.util.Scanner;
public class CricleArrayQueue {
public static void main(String[] args) {
CircleArray queue = new CircleArray(3);
char key =' ';//接收用户输入
Scanner scanner = new Scanner(System.in);
boolean loop = true;
while(loop){
System.out.println("s(show): 显示队列");
System.out.println("e(exit): 退出程序" );
System.out.println("a(add): 添加数据队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列的数据");
key =scanner.next().charAt(0);//接收一个字符
switch(key){
case 'a':
System.out.println("输入一个数");
int value = scanner.nextInt();
queue.addCircleQueue(value);
break;
case 's':
queue.showCircleQueue();
break;
case 'g'://取出数据
try {
int res = queue.getCircleQueue();
System.out.println("取出的数据是"+res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
int res = queue.headCircleQueue();
System.out.println("队列的头序列是"+res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'e':
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出!");
}
}
class CircleArray{
private int maxSize;//表示数组的长度
//front指向队列的第一个元素
//front的初始值为front=0
private int front;
private int rear;
//rear变量的含义是:指向队列的最后一个元素的后一个位置
//!!!是指向队列的最后一个元素,rear<front的情况是成立的!!
//
private int[] arr;//用于存放数据,模拟队列
public CircleArray(int arrMaxSize) {
front = 0;
rear = 0;
maxSize = arrMaxSize;
arr = new int[arrMaxSize];
}
//判断环形队列是否已满
public boolean isFull(){
return (rear+1) % maxSize == front;
}
//判断环形队列是否为空
public boolean isEmpty(){
return rear == front;
}
//添加数据到环形队列
public void addCircleQueue(int n){
if(isFull()){
System.out.println("环形队列已满,不能加入数据");
return;
}
arr[rear] = n;
rear = (rear+1)%maxSize;
}
//取出队列中的元素
public int getCircleQueue(){
if(isEmpty()){
//通过抛出异常
throw new RuntimeException("队列为空无法取出!");
}
//front是指向队列的第一个元素
int temp = front;
front = (front+ 1)%maxSize;
return arr[temp];
}
//显示队列中的所有数据
public void showCircleQueue(){
if(isEmpty()){
System.out.println("队列为空,无法遍历");
}
for (int i = front; i < front +size() ; i++) {
System.out.println("arr["+i+"]="+arr[i]);
}
}
//返回队列的长度
public int size(){
return (rear - front + maxSize)%maxSize;
//队列的长度实际上就是rear-front的绝对值,这样做是为了防止出现负数
}
//队列的头数据(不是取出数据)
public int headCircleQueue(){
if(isEmpty()){
throw new RuntimeException("队列为空,没有数据");
}
return arr[front];
}
}
三.链表(Linked List)介绍
3.1单链表
3.1.1单链表介绍
链表是有序列表,但是它在内存中是储存如下
(有的链表带头节点,有的不带)
(内存中的实际结构)
1)链表是以节点的方式来存储
2)每个节点包含data域,next域:指向下一个节点
3)如图:发现链表的各个节点不一定是连续存储
4)链表分带头节点的链表和没有头节点的链表,根据实际需求来确定
3.1.2单链表的实例应用
使用带head头的单向链表实现-水浒英雄排行榜管理
1)完成对英雄 人物的增删改查操作
2)第一种方法在添加英雄时,直接添加到链表的尾部
3)第二种方式在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
第一种方式
package Demo03LinkedList;
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 sigleLinkedListDemo = new SingleLinkedList();
//加入
sigleLinkedListDemo.add(hero1);
sigleLinkedListDemo.add(hero2);
sigleLinkedListDemo.add(hero3);
sigleLinkedListDemo.add(hero4);
//显示
sigleLinkedListDemo.list();
}
}
class SingleLinkedList{
//初始化一个头节点,头节点不懂,不存放具体数据
private HeroNode head = new HeroNode(0,"","");
//添加节点到单项列表,当不考虑编号顺序时
//1.找到当前链表的最后节点
//2.将最后这个节点的next指向 新的节点
public void add(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助遍历temp
HeroNode temp = head;
//遍历链表到最后
while(true){
//找到链表的最后一个(next域为null)的
if(temp.next==null){
break;
}
//将temp后移
temp = temp.next;
}
//当退出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;
}
}
}
//定义SigleLinkedListDemo来管理hero
//定义HeroNode,每个HeroNode 对象就是一个节点
class HeroNode{
public int no;//指的是编号
public String name;//名字
public String nickName;//昵称
public HeroNode next;//next域,指向下一个节点
//构造器
public HeroNode(int hNo,String hName,String hNickName){
this.no = hNo;
this.name = hName;
this.nickName = hNickName;
}
//为了显示方便,重写toString
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName+
'}';
}
}
第二种方式
(以下方法都是都是添加到SingleLinkedList类中的)
//第二种方法在添加英雄时,根据排名将英雄插入指定位置
//如果有这个排名,添加失败,并给出提示
public void addByOrder(HeroNode heroNode){
//因为头节点不能动,因此仍热得通过一个辅助指针(变量)来帮助找到添加位置
//因为是单链表,我们 找的temp是添加位置的前一个节点
HeroNode temp = head;
while(true){
if(temp.next==null){
temp.next =heroNode;
break;
}
if(temp.no== heroNode.no){
System.out.println(heroNode.no+"已存在无法添加");
break;
}
if((temp.next.no>heroNode.no)&&(temp.no<heroNode.no)){
//实际上直接if(tem.next.no>heroNode.no)即可,我这么写好理解写==
heroNode.next = temp.next;
temp.next = heroNode;
break;
}
temp = temp.next;//遍历
}
}
根据编号修改节点的信息
//根据编号no来修改(no是不能改的)
public void update(HeroNode heroNode){
HeroNode temp = head;
while(true){
if(temp.next ==null){
System.out.println("该英雄节点不在链表中,无法修改");
break;
}
if(temp.no==heroNode.no){
temp.nickName =heroNode.nickName;
temp.name = heroNode.name;
break;
}
temp = temp.next;
}
}
//根据编号删除节点
public void del(int hNo){
HeroNode temp = head;
while(true){
if (temp.next == null){
System.out.println("无该编号的的HeroNode");
break;
}
if(temp.next.no == hNo){
temp.next = temp.next.next;
if(temp.next==null){
break;
}
}
temp =temp.next;
}
3.1.3单链表的面试题
##其实把3.1.2看懂了下面的都很简单的
1)新浪面试题
求单链表中有效节点的个数
(如果是带头结点的链表,头节点不需要计入)
(我依靠上面的代码简写了)就是一个方法 在SingleLinkedList这个类中添加方法
public int getLength(){
Node temp = head.next; //Node 链表中的节点 就是上面的HeroNode
int a=o;
while(temp != null){
a++;
temp = temp.next;
}
return a;
}
查找单链表中倒数第K个节点
思路
1.编写一个方法,接收一个index
2.index 表示的是倒数的index的节点
3.先把链表从头到尾遍历,的到链表的总长度(即上面的方法getLength)
4.得到size后,从链表的第一个开始遍历(size-index)个,就可以得到
5.如果找到了则返回该节点,否则返回null
public int index(int index){
Node temp = head;//这也是上面的,这里我没有定义,定义在SingleLinkedList中
if(temp.next == null){
return null;//链表为空返回null
}
in size = getLength(head);//这是什么不用我说说了吧
//遍历 size = getLength(g)
if(index<=0||index>=size){
return null;
}
for(int i=0;i<(size-index);i++){
temp =temp.next;
}
return temp;
}
2)腾讯面试题
单链表的反转(这个难)
思路:
1.先定义一个节点reverseHead =new HeroNode();
2.从头到尾遍历原来的链表,每遍历-一个节点,就将其取出,并放在新的链表reverseHead的最前端.
3.原来的链表的head.next =reverseHead.next
public class ReverseSingleLinkedListDemo {
public static void main(String[] args) {
Node node1 =new Node(1,"胡成小弟");
Node node2 =new Node(2,"腾达小弟");
Node node3 =new Node(3,"志文小弟");
Node node4 =new Node(4,"浩爸爸");
ReverseSingleLinkedList reverseSingleLinkedList = new ReverseSingleLinkedList();
reverseSingleLinkedList.add(node1);
reverseSingleLinkedList.add(node4);
reverseSingleLinkedList.add(node2);
reverseSingleLinkedList.add(node3);
System.out.println("*******遍历加入的元素");
reverseSingleLinkedList.list();
System.out.println("*******遍历逆转后的元素");
reverseSingleLinkedList.reverse();
}
}
class ReverseSingleLinkedList{
private Node head = new Node(0,"");//头节点,不存放数据
//向链表中有序添加元素(按照输入的编号添加)
public void add(Node node){
//定义一个中间变量
Node temp = head;
while(true){
if(temp.next==null){
temp.next = node;
break;
}
if(temp.next.no>node.no){
node.next = temp.next;
temp.next = node;
break;
}else if(temp.next.no==node.no){
System.out.println(node.no+"已存在");
break;
}
temp = temp.next;
}
}
//逆转这个单链表(不包括头节点)
public void reverse(){
if(head.next == null|| head.next.next ==null){
System.out.println("链表为长度达不到逆转要求");
}
Node reverseHead = new Node(0,"");
Node temp=head.next;
Node temp1;
while(temp != null){
temp1 = temp.next;
temp.next =reverseHead.next;//指向新链表的最前端
reverseHead.next = temp;
temp = temp1;
}
head.next = reverseHead.next;
list();
}
//遍历链表
public void list(){
Node temp =head.next;
if(temp == null){
System.out.println("链表为空,无法遍历");
return;
}
while(temp != null){
System.out.println(temp);
temp = temp.next;
}
}
}
//链表中的节点类
class Node{
public int no;
public String name;//data域,节点中的数据,自己根据情况自己定义
public Node next;//next域,用于连接下一个节点
public Node(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
3)百度面试题
从尾到头打印单链表(要求方式:反向遍历,Stack栈)
思路:
1.要求的是逆序打印单链表
2.方式一:先将单链表进行反转操作,再打印出来即可(就是上面腾讯的),但这样操作实际上会破坏原来的单链表的结构(他要求的只是是逆向打印,不是将链表的结构改变在打印==),so不建议
3.方式二:可以利用栈这个数据结构,将各个节点压入栈中,然后利用栈先进后出的特点,就实现了逆序打印的效果
因此我选择用方法二,但是现阶段是没有学栈的
//这是一些栈的基本方法,后面会有栈的详细讲解
import java.util.Stack;
//stack的基本演示
public class StackTest {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
//向栈中压入元素(入栈)
stack.add("hc");
stack.add("ttd");
stack.add("zzw");
//将栈中的元素弹出(出栈)
while(stack.size()>0){
System.out.println(stack.pop());
}
}
}
具体实现不改变原有结构实现逆序输出(方法就写这了啊,这我不用多解释了吧)
//逆序遍历单链表
public void reverseListStack(){
Stack<Node> nodes = new Stack<>();
Node temp = head.next;
if(temp==null){
System.out.println("没有节点你叫我怎么遍历啊!!");
}
while(temp!=null){
nodes.add(temp);//push和add一样的作用
temp = temp.next;
}
while(nodes.size()>0){
System.out.println(nodes.pop());
}
}
4)练习
看完前面这么多,链表应该能理解的差不多了吧。这题自己去写写 ==
合并两个有序的单链表, 合并之后的链表依然有序
(如果链表理解的差不多了,是可以直接看SingleLInked类中的merge方法
package Demo03LinkedList;
public class LianXi {
public static void main(String[] args) {
NodeBiu one = new NodeBiu(1, "浩爸爸");
NodeBiu two = new NodeBiu(2, "胡成");
NodeBiu three = new NodeBiu(3, "谭腾达");
NodeBiu four = new NodeBiu(4, "詹志文");
NodeBiu five = new NodeBiu(5, "陈泽宇");
NodeBiu six = new NodeBiu(6, "卢诗航");
SingleLinked one1 = new SingleLinked();
SingleLinked two2 = new SingleLinked();
one1.add(two);
one1.add(five);
one1.add(four);
System.out.println("one1链表~~~");
one1.list();
two2.add(six);
two2.add(three);
two2.add(one);
System.out.println("two2链表~~");
two2.list();
System.out.println("合并");
SingleLinked three3 = one1.merge(two2);
three3.list();
}
}
class SingleLinked{
public void list(){
NodeBiu temp = head.next;
if(temp == null){
System.out.println("链表为空,无法遍历");
return;
}
while(temp != null){
System.out.println(temp);
temp = temp.next;
}
}
private NodeBiu head =new NodeBiu(0, "");//头节点,不存放数据
public NodeBiu getHead(){
return head;
}
public void add(NodeBiu nodeBiu){
NodeBiu temp = head;
while(true){
if(temp.next==null){
temp.next = nodeBiu;
break;
}
if(temp.next.no>nodeBiu.no){
nodeBiu.next = temp.next;
temp.next = nodeBiu;
break;
}
if(temp.next.no == nodeBiu.no){
break;
}
temp = temp.next;
}
}
//与另一个单链表合并,返回一个新的单链表,(默认的合并的节点中no没有相等的,如果有no相等的节点,那别的data数据也必须想的,不然这就不叫合并了)
public SingleLinked merge(SingleLinked one) {
SingleLinked singleLinked = new SingleLinked();
NodeBiu temp1 = head.next;
NodeBiu temp2 = one.getHead().next;
NodeBiu temp3 = singleLinked.getHead();
while (true) {
if (temp1 == null && temp2 == null) {
return singleLinked;
} else if (temp1 == null && temp2 != null) {
temp3.next = temp2;
temp3 = temp3.next;
temp2 = temp2.next;
} else if (temp1 != null && temp2 == null) {
temp3.next = temp1;
temp3 = temp3.next;
temp1 = temp1.next;
} else if(temp1 != null && temp2 != null){
if (temp1.no > temp2.no) {
temp3.next = temp2;
temp3 = temp3.next;
temp2 = temp2.next;
} else if (temp1.no < temp2.no) {
temp3.next = temp1;
temp3 = temp3.next;
temp1 = temp1.next;
// }else{
// temp3.next = temp1.next;
// temp3 = temp3.next;
// temp1 = temp1.next;
// temp2 = temp2.next;
// }
}
}
}
}
}
//链表中的节点类
class NodeBiu{
public int no;
public String name;//data域,节点中的数据,自己根据情况自己定义
public NodeBiu next;//next域,用于连接下一个节点
public NodeBiu(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
3.2双向链表
3.2.1双向链表的应用
使用带head头的双向链表实现- -水浒英雄排行榜管理单向链表的缺点分析:
1)单向链表, 查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
2)单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp的下一个节点来副除的(认真体会).
3)示意图帮助理解删除
分析 双向链表的遍历,添加,修改,删除的操作思路
1)遍历方和单链表一样,只是可以向前,也可以向后查找
2)添加(默认添加到双向链表的最后)
(1)先找到双向链表的最后这个节点
(2) temp.next = newHeroNode
(3) newHeroNodepre = temp;
3)修改思路和原理的单向链表一样.
4)删除
(1)因为是双向链表,因此,我们可以实现自我删除某个节点
(2)直接找到要删除的这个节点,比如temp
(3) temp.pre.next =temp.next
(4) temp.next.pre=temp.pre;
package Demo03LinkedList;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
System.out.println("双向链表的测试");
HeroNode2 node1 =new HeroNode2(1,"胡成小弟");
HeroNode2 node2 =new HeroNode2(2,"腾达小弟");
HeroNode2 node3 =new HeroNode2(3,"志文小弟");
HeroNode2 node4 =new HeroNode2(4,"浩爸爸");
//创建一个双向链表对象
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.add(node1);
doubleLinkedList.add(node2);
doubleLinkedList.add(node3);
doubleLinkedList.add(node4);
doubleLinkedList.list();
//修改
HeroNode2 node5 = new HeroNode2(3, "狗蛋");
System.out.println("修改后~~~");
doubleLinkedList.update(node5);
doubleLinkedList.list();
//删除
System.out.println("删除后~~");
doubleLinkedList.del(3);
doubleLinkedList.list();
}
}
class DoubleLinkedList{
private HeroNode2 head = new HeroNode2(0,"");
//只能在链表的末尾添加
public void add(HeroNode2 heroNode){
HeroNode2 temp = head;
while(temp.next != null){
temp = temp.next;
}
temp.next = heroNode;
heroNode.pre = temp;
}
//遍历双向链表
public void list(){
HeroNode2 temp = head.next;
if(temp == null){
System.out.println("链表为空无法遍历");
}
while(temp != null){
System.out.println(temp);
temp = temp.next;
}
}
//从双向链表中删除一个节点
public void del(int no){
//判断当前链表是否为空
if(head.next ==null){
System.out.println("当前链表为空链表");
return;
}
HeroNode2 temp = head.next;
while(true){
if(temp == null){
System.out.println("链表中的节点无该编号");
break;
}
if(temp.no == no){
temp.pre.next = temp.next;//
if(temp.next != null) {
//如果要删除的节点是最后一个,这句话则不要,不然会出现空指针异常
temp.next.pre = temp.pre;
}
break;
}
temp = temp.next;
}
}
public void update(HeroNode2 heroNode){
HeroNode2 temp = head;
while(true){
if(temp ==null){
System.out.println("该英雄节点不在链表中,无法修改");
break;
}
if(temp.no==heroNode.no){
temp.name = heroNode.name;
break;
}
temp = temp.next;
}
}
}
//定义HeroNode,每个HeroNode 对象就是一个节点
class HeroNode2{
public int no;//指的是编号
public String name;//名字
public HeroNode2 next;
public HeroNode2 pre;
//构造器
public HeroNode2(int hNo,String hName){
this.no = hNo;
this.name = hName;
}
//为了显示方便,重写toString
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
3.3双向环形链表
3.3.1应用
Josephu问题为:设编号为1, 2, …n的n个人 围坐一圈,约定编号为k (1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
提示:用一个不带头结点的循环链表来处理Josephu问题:
先构成一一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束
n=5,即有5个人
k=1,从第一个人开始报数
m=2,数2下
3.3.2环形链表的构建思路
public class yosephu {
public static void main(String[] args) {
CircleSingleLinked circleSingleLinked = new CircleSingleLinked();
circleSingleLinked.addBoy(5);
circleSingleLinked.list();
}
}
class CircleSingleLinked{
//先创建一个first节点,当前没有编号
private Boy first;
public void addBoy(int nums){
//先做一个数据校验
if(nums<1){
System.out.println("nums的值不正确");
return;
}
Boy temp =null;
//用for循环构建环形链表
for (int i = 1; i < nums+1; i++) {
//根据编号创建小孩节点
Boy boy = new Boy(i);
if(i==1){
first =boy;//此处的first有点绕,其实你就可以把他也当作和temp一样的中间变量,它的值就是第一个节点
//本CircleLinkedlist的类是直接产生一个环形链表(通过addBoy来确认环形链表的数量)
boy.next = boy;
temp = boy;
}else{
temp.next = boy;
boy.next =first;
temp = boy;
}
}
}
//遍历当前的环形链表
public void list(){
//判断链表是否为空
if(first == null){
System.out.println("链表为空,无法遍历");
return;
}
Boy temp = first;
while(true){
System.out.println("编号为"+temp.no);
if(temp.next == first){
break;
}
temp = temp.next;//temp后移
}
}
}
//先创建一个boy类,表示节点
class Boy{
public int no ;//编号
public Boy next;
public Boy(int no) {
this.no = no;
}
}
3.3.3josephu环的出圈思路
根据用户的输入,生成一个小孩出圈的顺序
n=5,即有5个人
k=1,从第一个人开始报数
m=2,数2下
1.需求创建-一个辅助指针(变量) helper,事先应该指向环形链表的最后这个节点
(补充:报数之前先让)
2.当小孩报数时,让first和helper指针同时的移动m -1次.
3.这时就可以将first指向的小孩节点出圈
first = first .next
helper.next = first
原来first指向的节点就没有任何引用,就会被回收
3.3.4代码实现
public class yosephu {
public static void main(String[] args) {
CircleSingleLinked circleSingleLinked = new CircleSingleLinked();
circleSingleLinked.addBoy(5);
circleSingleLinked.list();
circleSingleLinked.countBoy(1,2,5);
}
}
class CircleSingleLinked{
//先创建一个first节点,当前没有编号
private Boy first;
public void addBoy(int nums){
//先做一个数据校验
if(nums<1){
System.out.println("nums的值不正确");
return;
}
Boy temp =null;
//用for循环构建环形链表
for (int i = 1; i < nums+1; i++) {
//根据编号创建小孩节点
Boy boy = new Boy(i);
if(i==1){
first =boy;//此处的first有点绕,其实你就可以把他也当作和temp一样的中间变量,它的值就是第一个节点
//本CircleLinkedlist的类是直接产生一个环形链表(通过addBoy来确认环形链表的数量)
boy.next = boy;
temp = boy;
}else{
temp.next = boy;
boy.next =first;
temp = boy;
}
}
}
//遍历当前的环形链表
public void list(){
//判断链表是否为空
if(first == null){
System.out.println("链表为空,无法遍历");
return;
}
Boy temp = first;
while(true){
System.out.println("编号为"+temp.no);
if(temp.next == first){
break;
}
temp = temp.next;//temp后移
}
}
//根据用户的输入,计算出小孩的出圈顺序
/**
*
* @param startNo 表示从第几个小孩开始数数
* @param countNum 表示数几下
* @param nums 表示最初有多少个小孩在圈中
*/
public void countBoy(int startNo,int countNum,int nums){
if(first == null ||startNo<1 ||startNo>nums){
System.out.println("参数输入错误,请重新输入");
return;
}
//创建辅助指针,帮助小孩出圈
Boy helper = first;
//需要创建一个辅助指针(变量)helper,事先应该指向环形链表的最后一个节点,(就是指向first的后面)
while(true){
if(helper.next== first){
break;
}
helper = helper.next;
}
//小孩报数前,先让first和helper移动k-1次
for (int i = 1; i < startNo; i++) {
first = first.next;
helper = helper.next;
}
while(helper != first){//说明圈中只有一个节点
for (int i = 1; i <countNum ; i++) {
first = first.next;
helper = helper.next;
}
System.out.println("第"+first.no+"小孩出圈");
//这时first是在出圈小孩的节点处
first =first.next;
helper.next =first;
}
System.out.println("最后在圈内的小孩是"+first.no);
}
}
//先创建一个boy类,表示节点
class Boy{
public int no ;//编号
public Boy next;
public Boy(int no) {
this.no = no;
}
}