java数据结构和算法
第一章 概述
- 数据结构是一门研究组织数据方式的学课,学好数据结构可以编写出更加漂亮和有效率的代码
- 学会多考虑如何将生活中遇到的问题,用程序去实现解决
- 程序=数据结构+算法
- 数据额结构是算法的基础
第二章 线性和非线性结构
线性结构
- 线性结构是最常用的数据结构,其特点是数据元素之间存在一对一的线性结构
- 线性结构有两种不同的存储结构,顺序存储结构(数组)和链式存储结构(链表)。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的。
- 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息。
- 线性结构常见的有:数组,队列,链表和栈
非线性结构
一个节点元素可能有多个直接前驱和多个直接后继
常见的有:二维数组,多维数组,广义表,树(二叉树等)
第三章 稀疏数组和队列
问题:该二维数组的很多值是默认值0,因此记录了很多没有意义的数据 ->稀疏数组
稀疏数组
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存数组
处理方式:
- 记录数组一共有几行几列,有多少个不同的值
- 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
第一行记录数组中有几行几列,多少个值。放在索引为0的位置
后面记录数组中元素的具体位置和值
package Sparse;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
import com.fasterxml.jackson.databind.util.JSONPObject;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class SparseArray {
public static void main(String[] args) throws IOException {
//创建11*11的二维数组
// 0表示没有棋子 1表示黑子 2表示蓝子
int chessArr1[][]= new int[11][11];
String s = chessArr1.toString();
chessArr1[1][2]=1;
chessArr1[2][3]=2;
System.out.println("原始的二维数组~~~");
for (int[] row : chessArr1) {
for (int data : row) {
System.out.print(data+" ");
}
System.out.println();
}
//将原始的二维数组转稀疏数组
int sum=0;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
if (chessArr1[i][j]!=0){
sum++;
}
}
}
// row col val
// 0 11 11 2 几行几列几个有效值
// 1 1 2 1 后面开始存具体数据,第几行第几列值多少
//初始化
int sparseArr[][]=new int[sum+1][3];
sparseArr[0][0]=11;
sparseArr[0][1]=11;
sparseArr[0][2]=sum;
//赋值
int 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]=2;
sparseArr[count][2]=chessArr1[i][j];
}
}
}
System.out.println("稀疏数组");
for (int i = 0; i < sparseArr.length; i++) {
System.out.println(sparseArr[i][0]+" "+sparseArr[i][1]+" "+sparseArr[i][2]);
}
//返回去
int chessArr2[][]=new int[sparseArr[0][0]][sparseArr[0][1]];
for (int i = 1; i < sparseArr.length; i++) {
chessArr2[sparseArr[i][0]][sparseArr[i][1]]=sparseArr[i][2];
}
}
}
将稀疏数组保存到本地磁盘
一:转成json格式,写入
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(chessArr1);
FileOutputStream fileOutputStream=new FileOutputStream(new File("d:/aa.txt"));
fileOutputStream.write(json.getBytes());
fileOutputStream.close();
FileInputStream fileInputStream=new FileInputStream(new File("d:/aa.txt"));
byte[]bytes=new byte[1024];
int len;
while ((len=fileInputStream.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
二:用序列化保存
写个类,实现序列化接口
import java.io.Serializable;
public class Line implements Serializable {
private int row;
private int col;
private int val;
public Line() {
}
public Line(int row, int col, int val) {
this.row = row;
this.col = col;
this.val = val;
}
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public int getCol() {
return col;
}
public void setCol(int col) {
this.col = col;
}
public int getVal() {
return val;
}
public void setVal(int val) {
this.val = val;
}
}
String fileName = "D:\aa.txt";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));
ArrayList<Line> list = new ArrayList<>();
for (int i = 0; i < sparseAarray.length; i++) {
list.add(new Line(sparseAarray[i][0],sparseAarray[i][1],sparseAarray[i][2]));
}
oos.writeObject(list);
oos.close();
//读取稀疏数组
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
Object o = ois.readObject();
ois.close();
//从读取的文件中恢复数据
System.out.println("读取出的数据:");
ArrayList<Line> arr = (ArrayList<Line>)o;
for (Line line : arr) {
System.out.printf("%d\t%d\t%d\t\n",line.getRow(),line.getCol(),line.getVal());
}
队列
过安检,先进先出
- 队列是一个有序列表,可以用数组或者链表来实现
- 先入先出,先存入队列的数据,要先取出。后入存入的要后取出
- 队列本身是有序列表,若使用数组的结构来存储队列的数据,则MaxSize是该队列的对大容量
- 因为队列的输出和输出是分别从前后端来处理,需要两个变量front和rear分别记录前后端的下表,front会随着数据输出而改变,而rear会随数据输入而改变
当数据存入队列时称为“addQueue”
1.将尾指针后移 rear+1,当front=rear【空】
2.若尾指针rear小于队列的最大下标maxSzie-1,则将数据存入rear所指的数组元素中,否则无法存入。rear=maxSize-1【队列满】
数组模拟环形队列,把数组看成一个环形(取模的方式来实现)
尾索引的下一个为头索引时表示队列满了即将队列容量空出一个座位约定,在这个做判断队列满时需要注意
(rear+1)%maxSize==front【满】
rear=front【空】
代码实现环形队列
package queue;
public class ArrayQueue {
//环形队列,可复用了
private int maxSize;//最大容量
private int front;//队列头
private int rear;//队列尾
private int[]arr;//该数据用于存放数据,模拟队列
public ArrayQueue(int arrMaxSize){
maxSize=arrMaxSize;
arr=new int[arrMaxSize];
front=0; //指向队列头部
rear=0;//指向队列尾部,
}
public boolean isFull(){//判断队列是否满
return (rear+1)%maxSize==front;
}
public boolean isEmpty(){//是否为空
return rear==front;
}
public void addQueue(int n){
if (isFull()){
System.out.println("队列满了");
return;
}
arr[rear]=n;
//rear后移,考虑取模,实现复用
rear=(rear+1)%maxSize;
}
public int getQueue(){
if (isEmpty()){
throw new RuntimeException("队列空,不能取数据");
};
int value=arr[front];
front=(front+1)%maxSize;//考虑越界
return value;
}
public void showQueue(){
if (isEmpty()){
System.out.println("队列空的,没有数据");
return;
}
for (int i=front;i<front+size();i++){
System.out.println(i%maxSize+" "+arr[i%maxSize]);
}
}
//定义个方法求出当前队列有效数据的个数
public int size(){
return (rear+maxSize-front)%maxSize;
}
public int headQueue(){
if (isEmpty()){
throw new RuntimeException("队列空的,没有数据");
}
return arr[front];
}
}
链表(Linked List)
链表是有序的列表,但实际在内存中存储如下
- 链表是以节点的方式来存储的,链式存储
- 每个节点包含data域,next域:指向下一个节点
- 链表的各个节点不一定是连续存储
- 链表分带头节点的链表和没有头节点的链表
1.添加英雄时,直接添加到链表的尾部
2.根据排名插入到指定位置
3.修改节点
先找到该节点,通过遍历
temp.name=newHeroNode.name;
temp.nickname=newHeroNode.nickname
4.删除节点
public class HeroNode {
public int no;
public String name;
public String nickname;
public HeroNode next;
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 + '\'' +
'}';
}
}
package List;
import domain.HeroNode;
import java.util.Stack;
public class SingleLinkedList {
//头结点初始化
private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return head;
}
//添加结点到单向链表
public void add(HeroNode heroNode) {
//找到当前链表的最后结点,讲最后的和这个结点的next指向新的结点
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
//没有找到后移
temp = temp.next;
}
//当退出while时,temp指向链表的最后
temp.next = heroNode;
}
//第二种方法添加英雄,根据排名插入
public void addByOrder(HeroNode heroNode) {
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;//最后
}
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {
//说明要添加的编号已经存在
flag = true;
break;
}
temp=temp.next;//后移
}
if (flag){
System.out.println("准备插入的英雄编号已经存在了,不能加入"+heroNode.no);
}else {
heroNode.next=temp.next;
temp.next=heroNode;
}
}
//删除节点
public void delete(int no){
HeroNode temp=head;
boolean flag=false;
while (true){
if (temp.next==null){
break;//到最后了,退出
}
if (temp.next.no==no){
//找到待删除的结点的前一个
flag=true;
break;
}
temp=temp.next;
}
if (flag){
temp.next=temp.next.next;
}else {
System.out.println("没有找到要删除的结点,无法删除");
}
}
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.next;
}
}
//获取单链表中有效节点的个数
public static int getLength(HeroNode head){
//获取到单链表的结点个数
if (head.next==null){
return 0;
}
int length=0;
HeroNode cur=head.next;
while (cur!=null){
length++;
cur=cur.next;
}
return length;
}
//查找单链表中倒数第K个结点
public static HeroNode findLastIndexNode(HeroNode head,int index){
if (head.next==null){
return null;
}
//得到链表的长度
int size = getLength(head);
if (index<=0||index>size){
return null;
}
//定义一个辅助变量
HeroNode cur=head.next;
for (int i = 0; i < size - index; i++) {
cur=cur.next;
}
return cur;
}
public static void reversetList(HeroNode head){
//如果当前链表为空,或者只有一个节点,无需反转直接返回
if (head.next==null||head.next.next==null){
return;
}
//定义一个辅助的指针,帮助我们遍历原来的链表
HeroNode cur=head.next;
HeroNode next=null;//指向当前节点【cur】的下一个节点
HeroNode reverseHead=new HeroNode(0,"","");
//遍历原来的链表,每遍历一个节点就将其取出,并放在新的链表最前端
while (cur!=null){
next=cur.next;//保存当前节点的下一个节点
cur.next=reverseHead.next;//指针反转
reverseHead.next=cur;//将curr链接到新链表上
cur=next;//后移
}
head.next=reverseHead.next;
}
public static void reversePrint(HeroNode head){
if (head.next==null){
return;
}
Stack<HeroNode>stack=new Stack<HeroNode>();
HeroNode cur=head.next;
while (cur!=null){
stack.push(cur);
cur=cur.next;
}
while (stack.size()>0){
System.out.println(stack.pop());
}
}
}
这个反转链表也可以存入到stack栈中,因为栈先进后出,可以实现逆序打印的效果
双向链表
多一个Head头节点
可以向前或者向后查询,可以实现自我删除
遍历:可以向前或者向后
添加:默认添加到最后
先找到双向链表的最后一个节点
temp.next=newHeroNode
newHeroNode.pre=temp
修改:一样
删除:可以自我删除,先找到要删除的节点
temp.pre.next=temp.next
temp.next.pre=temp.pre
package domain;
public class HeroNode2 {
public int no;
public String name;
public String nickname;
public HeroNode2 next;
public HeroNode2 pre;
public HeroNode2(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+"]";
}
}
package List;
import domain.HeroNode;
import domain.HeroNode2;
public class DoubleLinkedList {
private HeroNode2 head = new HeroNode2(0, "", "");
public HeroNode2 getHead() {
return head;
}
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.next;
}
}
public void add(HeroNode2 heroNode2) {
HeroNode2 temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = heroNode2;//后指前
heroNode2.pre = temp;//前指后
}
public void update(HeroNode2 heroNode2) {
if (heroNode2.next == null) {
System.out.println("链表为空");
return;
}
HeroNode2 temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.no == heroNode2.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = heroNode2.name;
temp.nickname = heroNode2.nickname;
} else {
System.out.println("没有找到指定编号的节点,无法修改");
}
}
public void delete(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) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.pre.next = temp.next;//当前元素的后指针赋给前元素的后指针
//要判断是不是最后一个节点
if (temp.next != null) {
temp.next.pre = temp.pre;
}
} else {
System.out.println("要删除的节点不存在");
}
}
}
双向链表按编号添加
//第二种方法添加英雄,根据排名插入
public void addByOrder(HeroNode2 heroNode) {
HeroNode2 temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;//最后
}
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {
//说明要添加的编号已经存在
flag = true;
break;
}
temp=temp.next;//后移
}
if (flag){
System.out.println("准备插入的英雄编号已经存在了,不能加入"+heroNode.no);
}else {
if (temp.next!=null){
heroNode.next=temp.next;
temp.next.pre=heroNode;
temp.next=heroNode;
heroNode.pre=temp;
}else {
temp.next = heroNode;//后指前
heroNode.pre = temp;//前指后
}
}
}
约瑟夫josephu问题
josephu:设编号1,2,。。。n的n个人围坐一圈,约定编号k(1<=k<=n)的人从1开始报数,数到m的那个人出列,下一位又从1开始数,数到m的那个人出列,以此类推,直到所有人出列为止,产生一个出队编号的序列。
提示:用一个不带头结点的循环链表来处理josephu问题,先构建一个有n个结点的单循环链表,然后由k结点起,从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除节点的下一个节点又从1开始计数,知道最后一个节点从链表中删除 结束
package List;
import com.sun.org.apache.xpath.internal.operations.Bool;
import domain.Boy;
public class josepfu {
//约瑟夫环形链表
private Boy first = null;
public void addBoy(int nums) {
if (nums < 1) {
System.out.println("nums的值不正确");
return;
}
Boy curBoy = null;//辅助指针,帮助构建
for (int i = 1; i <= nums; i++) {
//根据编号,创建节点
Boy boy = new Boy(i);
if (i == 1) {
//第一个
first = boy;
first.setNext(first);//自成环
curBoy = first;
} else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
public void showBoy() {
if (first == null) {
System.out.println("链表为空");
return;
}
Boy curBoy = first;
while (true) {
System.out.println(curBoy.getNo());
if (curBoy.getNext() == first) {
break;
}
curBoy = curBoy.getNext();
}
}
public void countBoy(int startNo, int countNum, int nums) {
if (first == null || startNo < 1 || startNo > nums) {
System.out.println("参数输入有误,请重新输入");
return;
}
Boy helper = first;
//拿到最后一个节点
while (true) {
if (helper.getNext() == first) {
break;
}
helper = helper.getNext();
}
while (true){
if (helper==first){
//说明只要一个节点
break;
}
//让first和helper指针同时移动countNum-1 往后走
for (int i = 0; i < countNum - 1; i++) {
first=first.getNext();
helper=helper.getNext();
}
System.out.println("小孩出圈"+first.getNo());
first=first.getNext();
helper.setNext(first);
}
System.out.println("最后在圈中的小孩"+helper.getNo());
}
}