数据结构和算法

1. 数据结构

1.1 稀疏数组

  • 这个简单
  • 稀疏数组即二维数组中有大量为0或同一个无效值的时候,将其压缩为只有有效数据的稀疏数组,需要使用时将其读写出来转为二维数组。
public class dom1 {public static void main(String[] args) {//创建一个原始二维数组  11*11int ints[][] = new int[11][11];ints[1][2] = 1 ;ints[2][3] = 2 ;System.out.println("原始的二位数组");for (int[] anInt : ints) {for (int i : anInt) {System.out.printf("%d\t",i);}System.out.println("");}//将二维数组转稀疏数组//遍历二位数组,得到有效数据的个数int sum = 0;for (int[] anInt : ints) {for (int i : anInt) {if(i!=0){sum++;}}}System.out.println("有效数据个数"+sum);System.out.println("遍历完毕");//创建稀疏数组int arr[][] = new int[sum+1][3];//给稀疏数组赋值arr[0][2] = sum;int count = 1;   //记录第几个非零数int col = 0;for (int[] anInt : ints) {for (int i : anInt) {if(i!=0){arr[count][0]   = count;   // 第几行arr[count][1]   = col;     // 第几列arr[count][2]   = i;count++;continue;}col++;}col = 0;}//懒得加参数算行列个数了,就按照标准稀疏数组读取arr[0][0] = 11;arr[0][1] = 11;//输出稀疏数组/**for (int[] ints1 : arr) {for (int i : ints1) {System.out.printf("%d\t",i);}System.out.println("");}*///稀疏数组只有三列,对以上打印方法优化for(int i = 0;i < arr.length;i++){System.out.printf("%d%d%d\n",arr[i][0],arr[i][1],arr[i][2]);}//将稀疏数组恢复成原始二位数组//读取第一行,创建新二维数组int[][] brr = new int[arr[0][0]][arr[0][1]];//数据转移int num2 = 0;for (int i = 1;i<arr.length;i++){brr[arr[i][0]][arr[i][1]] = arr[i][2];}//打印新二维数组for (int[] ints1 : brr) {for (int i : ints1) {System.out.printf("%d\t",i);}System.out.println("");}}
}

1.2 队列

1.2.1 数组模拟单向队列

  • 先来先出原则
  • 代码实现:
import java.util.Scanner;public class dom2 {//队列本身是有序列表public static void main(String[] args) {ArrayQueue arrayQueue = new ArrayQueue(3);//不添加先测展示数据
//        arrayQueue.showQueue();
//        System.out.println(arrayQueue.headQueue());//搞一个菜单玩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 's':arrayQueue.showQueue();break;case 'e'://断开scanner.close();loop = false;break;case 'a':System.out.println("客官想添点啥呢");int value = scanner.nextInt();arrayQueue.addQueue(value);break;case 'g'://方法有异常处理,用try/catch写法try {System.out.println("提取出来的是"+arrayQueue.getQueue());}catch (Exception e){//固打印提升信息就行,别断进程System.out.println(e.getMessage());}break;case 'h':try {System.out.println("提取出来的队列头数据是"+arrayQueue.headQueue());}catch (Exception e){System.out.println(e.getMessage());}default://用户不按照菜单提示输入时...System.out.println("给朕好好输!");break;}}System.out.println("欢迎下次光临~~~");}
}//数组模拟单向队列类
class ArrayQueue{//最大容量private int maxSize;//队列头private int front;//队列尾private int rear;//模拟队列的数组private int arr [];//创建队列构造器public ArrayQueue(int MaxSize){maxSize = MaxSize;arr = new int[maxSize];//初始化指向队列头尾部前一个位置front = -1;rear = -1;}//判断队列满不满public boolean isFull(){return rear==maxSize-1?true:false;}//判断队列是不是空public boolean isEmpty(){return rear == front?true:false;}//添加数据到队列public void addQueue(int n){//判断是否满if (isFull()){System.out.println("满了满了满了满了!!!!!");return;}rear++;//添加是往尾部加arr[rear] = n;//简写:arr[++rear] = n;}//获取队列数据---出去public int getQueue(){//判断是否空if (isEmpty()){//通过异常处理throw new RuntimeException("没了没了没了没了!!!!!");//return;   这个不用写!!!}//出队列front++;return arr[front];//return arr[++front];}//显示队列所有数据public void showQueue(){if (isEmpty()){System.out.println("这啥也没有,拜拜了您呢");return;}for (int i : arr) {System.out.println(i);}}//显示头数据public int headQueue(){if (isEmpty()){throw new RuntimeException("想不到吧,队列是空的~~~~~~");}return arr[front+1];//这不能写arr[0],即要返回的是当前还在排队的第一个}
}
  • 问题提出:数组只能单次使用,不能复用。

1.2.2 数组模拟环形队列

分析

  • 解决单向队列不能复用的问题
  • 满了的情况尾跑到头:rear代表指向元素最后一个元素,不是数组空间的固定某个位置
  • 满了的条件:(rear+1)%maxSize = front; ---- 这个算法牛逼!
  • 空的条件不变:rear = front
  • 初始值:rear,front都是0;原先是站在队列外等待进去,现在是站在环形跑道内。
  • 有效数据个数:(rear+maxSize-front)%maxSize

代码实现

  • 在单项队列上进行改进
import java.util.Scanner;/*** 里面需要好好思考的算法:* 判满:(rear+1)%maxSize==front?true:false;* * 有效值个数:(rear + maxSize -front)%maxSize;* * 巧用临时变量保存数据: int value = arr[front];*                    front = (front+1)%maxSize;*                    return value;*                    *打印全部有效数据:     for (int i = front;i < front+size();i++){*                        System.out.println(arr[i%maxSize]);*                   }*///环形队列
public class dom3 {public static void main(String[] args) {//测试环形队列CircleQueue circleQueue = new CircleQueue(4);//队列有效数据是三个//搞一个菜单玩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 's':circleQueue.showQueue();break;case 'e'://断开scanner.close();loop = false;break;case 'a':System.out.println("客官想添点啥呢");int value = scanner.nextInt();circleQueue.addQueue(value);break;case 'g'://方法有异常处理,用try/catch写法try {System.out.println("提取出来的是"+circleQueue.getQueue());}catch (Exception e){//固打印提升信息就行,别断进程System.out.println(e.getMessage());}break;case 'h':try {System.out.println("提取出来的队列头数据是"+circleQueue.headQueue());}catch (Exception e){System.out.println(e.getMessage());}break;default://用户不按照菜单提示输入时...System.out.println("给朕好好输!");break;}}System.out.println("欢迎下次光临~~~");}
}//数组模拟环形队列类
class CircleQueue{//最大容量private int maxSize;//队列头private int front;//队列尾private int rear;//模拟队列的数组private int arr [];//创建队列构造器public CircleQueue(int MaxSize){maxSize = MaxSize;arr = new int[maxSize];//初始化指向队列头尾部前一个位置//rear,front默认为0,这里可以不用赋值}//判断队列满不满public boolean isFull(){return (rear+1)%maxSize==front?true:false;}//判断队列是不是空public boolean isEmpty(){return rear == front?true:false;}//添加数据到队列public void addQueue(int n){//判断是否满if (isFull()){System.out.println("满了满了满了满了!!!!!");return;}arr[rear] = n;//尾后移,防止越界rear = (rear+1)%maxSize;}//获取队列数据---出去public int getQueue(){//判断是否空if (isEmpty()){//通过异常处理throw new RuntimeException("没了没了没了没了!!!!!");//return;   这个不用写!!!}//出队列//return arr[front];//不能直接返回,return后front就没有后移的机会了//用临时变量保存起来,先把指针后移,再提取//防止越界一定要取模int value = arr[front];front = (front+1)%maxSize;return value;}//获取队列有效值个数public int size(){return (rear + maxSize -front)%maxSize;}//显示队列所有数据public void showQueue(){if (isEmpty()){System.out.println("这啥也没有,拜拜了您呢");return;}//从front遍历,遍历多少个元素。for (int i = front;i < front+size();i++){System.out.println(arr[i%maxSize]);}}//显示头数据public int headQueue(){if (isEmpty()){throw new RuntimeException("想不到吧,队列是空的~~~~~~");}return arr[front];//这不能写arr[0],即要返回的是当前还在排队的第一个}
}

1.3 链表

1.3.1 单链表的创建和遍历

基本介绍

  • 链表是有序列表

  • 链表以节点的方式存储

  • 每个节点包含data域存储数据,next域指向下一个节点

  • 链表的各个节点不一定是连续存储

  • 链表分为带头结点的跟不带头节点的,头节点根据需求确定有无

  • 头节点不存放具体数据,只存放指向下一个节点地址

  • 最后一个节点next域为null

  • 创建:

    • 创建一个head头节点
    • 每添加一个节点,直接加入到最后
  • 遍历:

    • 通过一个辅助变量,帮助遍历整个链表

代码实现----添加水浒人物

public class dom4 {public static void main(String[] args) {//先创建节点HeroNode heroNode1 = new HeroNode(1, "宋江", "马后炮");HeroNode heroNode2 = new HeroNode(2, "卢俊义", "黑麒麟");HeroNode heroNode3 = new HeroNode(3, "吴用", "无用");HeroNode heroNode4 = new HeroNode(4, "林冲", "猫猫头");HeroNode heroNode5 = new HeroNode(5, "李逵", "李白的黑妹");HeroNode heroNode6 = new HeroNode(6, "张顺", "浪里白条鸡");HeroNode heroNode7 = new HeroNode(7, "孙二娘", "母老虎");//new 一个管理器SingleLinkedList singleLinkedList = new SingleLinkedList();//加入singleLinkedList.addSingleLinkedList(heroNode1);singleLinkedList.addSingleLinkedList(heroNode2);singleLinkedList.addSingleLinkedList(heroNode3);singleLinkedList.addSingleLinkedList(heroNode4);singleLinkedList.addSingleLinkedList(heroNode5);singleLinkedList.addSingleLinkedList(heroNode6);singleLinkedList.addSingleLinkedList(heroNode7);//显示singleLinkedList.list();}
}//定义SingleLinkedList管理hero
class SingleLinkedList{//初始化头节点,头节点不动,不放具体数据private HeroNode head = new HeroNode(0,"","");//添加节点向单链表//不考虑编号顺序时,找到当前链表最后节点,后节点next指向新节点public void addSingleLinkedList(HeroNode heroNode){//head节点不能动,需要辅助变量,注意辅助变量temp是HeroNode类HeroNode temp = head;//遍历链表找到最后while (true){if(temp.next == null){break;}//没有找到null,temp后移temp = temp.next;}//退出while循环时,temp指向了链表最后//将最后节点指向新节点temp.next = heroNode;   // 所有数据都在next里面!!!!!!!!!!!!!!!!!!!将next想成层层嵌套的宝塔肉}//显示链表,通过辅助变量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);//一定要将next后移temp = temp.next;}}
}//定义一个HeroNode,每个对象是一个节点
class HeroNode{private int no;private String name;private String nikeNode;//数据都放在next里面public HeroNode next;//这样构造是为了插入数据public HeroNode(int hno,String hName,String hNikeName){this.no = hno;this.name = hName;this.nikeNode = hNikeName;}//显示方便,重写toString//注意不要打印next,每一层next都包含着本层后面所有数据@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nikeNode='" + nikeNode +'}';}
}
  • 当前实现的问题是将插入的数据连接起来,跟插入的前后有关,与插入的对象自身属性无关

  • 与数组形式的列表不同,链表可以没有上限,真实物理内存动态变化。

  • 这里较为难理解的next,注意!!!

  • 只能实现尾插

1.3.2 单链表按顺序插入节点

  • 修改addSingleLinkedList方法变成按顺序插入
public class dom4 {public static void main(String[] args) {//先创建节点HeroNode heroNode1 = new HeroNode(1, "宋江", "马后炮");HeroNode heroNode2 = new HeroNode(2, "卢俊义", "黑麒麟");HeroNode heroNode5 = new HeroNode(5, "李逵", "李白的黑妹");HeroNode heroNode6 = new HeroNode(6, "张顺", "浪里白条鸡");HeroNode heroNode3 = new HeroNode(3, "吴用", "无用");HeroNode heroNode4 = new HeroNode(4, "林冲", "猫猫头");HeroNode heroNode7 = new HeroNode(7, "孙二娘", "母老虎");//new 一个管理器SingleLinkedList singleLinkedList = new SingleLinkedList();//加入singleLinkedList.addSingleLinkedList(heroNode1);singleLinkedList.addSingleLinkedList(heroNode2);singleLinkedList.addSingleLinkedList(heroNode3);singleLinkedList.addSingleLinkedList(heroNode4);singleLinkedList.addSingleLinkedList(heroNode5);singleLinkedList.addSingleLinkedList(heroNode6);singleLinkedList.addSingleLinkedList(heroNode7);//显示singleLinkedList.list();}
}//定义SingleLinkedList管理hero
class SingleLinkedList{//初始化头节点,头节点不动,不放具体数据private HeroNode head = new HeroNode(0,"","");//添加节点向单链表//不考虑编号顺序时,找到当前链表最后节点,后节点next指向新节点public void addSingleLinkedList(HeroNode heroNode){//head节点不能动,需要辅助变量HeroNode temp = head;//判断是不是已经有这个编号boolean flag = false;//遍历链表找到要插入的位置while (true){//temp在链表最后面if(temp.next == null){break;}//直接从头节点开始判断下一个与当前heroNode.no,这个真没想到//排除法原则if(temp.next.no > heroNode.no){break;}else if(temp.next.no == heroNode.no){flag=true;break;}temp = temp.next;}//判断flagif(flag){System.out.println(heroNode.no+"  这个编号被占了");}else {heroNode.next = temp.next;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);//一定要将next后移temp = temp.next;}}
}//定义一个HeroNode,每个对象是一个节点
class HeroNode{public int no;//这俩参数暂时用不到private String name;private String nikeNode;//数据都放在next里面public HeroNode next;//这样构造是为了插入数据public HeroNode(int hno,String hName,String hNikeName){this.no = hno;this.name = hName;this.nikeNode = hNikeName;}//显示方便,重写toString//注意不要打印next,每一层next都包含着本层后面所有数据@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nikeNode='" + nikeNode +'}';}
}
  • 算法亮点:用链表的机制做插入判断,遍历过程中直接判断下一个。

1.3.3 单链表节点的修改

public class dom4 {public static void main(String[] args) {//先创建节点HeroNode heroNode1 = new HeroNode(1, "宋江", "马后炮");HeroNode heroNode2 = new HeroNode(2, "卢俊义", "黑麒麟");HeroNode heroNode5 = new HeroNode(5, "李逵", "李白的黑妹");HeroNode heroNode6 = new HeroNode(6, "张顺", "浪里白条鸡");HeroNode heroNode3 = new HeroNode(3, "吴用", "无用");HeroNode heroNode4 = new HeroNode(4, "林冲", "猫猫头");HeroNode heroNode7 = new HeroNode(7, "孙二娘", "母老虎");//测试修改HeroNode heroNode8 = new HeroNode(1, "鲁智深", "阿弥陀佛么么哒");HeroNode heroNode9 = new HeroNode(3, "武松", "动物保护协会会长");HeroNode heroNode10 = new HeroNode(7, "史进", "纹身的不良少年");HeroNode heroNode11 = new HeroNode(8, "关胜", "关二爷的小迷弟");//new 一个管理器SingleLinkedList singleLinkedList = new SingleLinkedList();//加入singleLinkedList.addSingleLinkedList(heroNode1);singleLinkedList.addSingleLinkedList(heroNode2);singleLinkedList.addSingleLinkedList(heroNode3);singleLinkedList.addSingleLinkedList(heroNode4);singleLinkedList.addSingleLinkedList(heroNode5);singleLinkedList.addSingleLinkedList(heroNode6);singleLinkedList.addSingleLinkedList(heroNode7);//修改singleLinkedList.updateSingleLinkedList(heroNode8);singleLinkedList.updateSingleLinkedList(heroNode9);singleLinkedList.updateSingleLinkedList(heroNode10);//测试不存在的对象修改singleLinkedList.updateSingleLinkedList(heroNode11);//显示singleLinkedList.list();}
}//定义SingleLinkedList管理hero
class SingleLinkedList{//初始化头节点,头节点不动,不放具体数据private HeroNode head = new HeroNode(0,"","");//添加节点向单链表//不考虑编号顺序时,找到当前链表最后节点,后节点next指向新节点public void addSingleLinkedList(HeroNode heroNode){//head节点不能动,需要辅助变量HeroNode temp = head;//判断是不是已经有这个编号boolean flag = false;//遍历链表找到要插入的位置while (true){//temp在链表最后面if(temp.next == null){break;}//直接从头节点开始判断下一个与当前heroNode.no,这个真没想到//排除法原则if(temp.next.no > heroNode.no){break;}else if(temp.next.no == heroNode.no){flag=true;break;}temp = temp.next;}//判断flagif(flag){System.out.println(heroNode.no+"  这个编号被占了");}else {heroNode.next = temp.next;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);//一定要将next后移temp = temp.next;}}//根据newHeroNode的 no 修改就行public void updateSingleLinkedList(HeroNode newHeroNode){if (head.next == null){System.out.println("链表是空哒小老弟");return;}HeroNode temp = head;while (true){if(temp == null){System.out.println("没有 "+newHeroNode.no+" 号这个小朋友");break;}//找到了if(temp.no == newHeroNode.no){temp.name = newHeroNode.name;temp.nikeNode = newHeroNode.nikeNode;break;}temp = temp.next;}}
}//定义一个HeroNode,每个对象是一个节点
class HeroNode{public int no;//能修改了那就改成publicpublic String name;public String nikeNode;//数据都放在next里面public HeroNode next;//这样构造是为了插入数据public HeroNode(int hno,String hName,String hNikeName){this.no = hno;this.name = hName;this.nikeNode = hNikeName;}//显示方便,重写toString//注意不要打印next,每一层next都包含着本层后面所有数据@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nikeNode='" + nikeNode +'}';}
}
  • 加一个update类方法就行,这个简单

1.3.4 单链表节点的删除

public class dom4 {public static void main(String[] args) {//先创建节点HeroNode heroNode1 = new HeroNode(1, "宋江", "马后炮");HeroNode heroNode2 = new HeroNode(2, "卢俊义", "黑麒麟");HeroNode heroNode5 = new HeroNode(5, "李逵", "李白的黑妹");HeroNode heroNode6 = new HeroNode(6, "张顺", "浪里白条鸡");HeroNode heroNode3 = new HeroNode(3, "吴用", "无用");HeroNode heroNode4 = new HeroNode(4, "林冲", "猫猫头");HeroNode heroNode7 = new HeroNode(7, "孙二娘", "母老虎");//测试修改HeroNode heroNode8 = new HeroNode(1, "鲁智深", "阿弥陀佛么么哒");HeroNode heroNode9 = new HeroNode(3, "武松", "动物保护协会会长");HeroNode heroNode10 = new HeroNode(7, "史进", "纹身的不良少年");HeroNode heroNode11 = new HeroNode(8, "关胜", "关二爷的小迷弟");//new 一个管理器SingleLinkedList singleLinkedList = new SingleLinkedList();//增加singleLinkedList.addSingleLinkedList(heroNode1);singleLinkedList.addSingleLinkedList(heroNode2);singleLinkedList.addSingleLinkedList(heroNode3);singleLinkedList.addSingleLinkedList(heroNode4);singleLinkedList.addSingleLinkedList(heroNode5);singleLinkedList.addSingleLinkedList(heroNode6);singleLinkedList.addSingleLinkedList(heroNode7);//修改singleLinkedList.updateSingleLinkedList(heroNode8);singleLinkedList.updateSingleLinkedList(heroNode9);singleLinkedList.updateSingleLinkedList(heroNode10);//测试不存在的对象修改singleLinkedList.updateSingleLinkedList(heroNode11);//删除singleLinkedList.deleteSingleLinkedList(3);singleLinkedList.deleteSingleLinkedList(7);singleLinkedList.deleteSingleLinkedList(9);//显示singleLinkedList.list();}
}//定义SingleLinkedList管理hero
class SingleLinkedList{//初始化头节点,头节点不动,不放具体数据private HeroNode head = new HeroNode(0,"","");//添加节点向单链表//不考虑编号顺序时,找到当前链表最后节点,后节点next指向新节点public void addSingleLinkedList(HeroNode heroNode){//head节点不能动,需要辅助变量HeroNode temp = head;//判断是不是已经有这个编号boolean flag = false;//遍历链表找到要插入的位置while (true){//temp在链表最后面if(temp.next == null){break;}//直接从头节点开始判断下一个与当前heroNode.no,这个真没想到//排除法原则if(temp.next.no > heroNode.no){break;}else if(temp.next.no == heroNode.no){flag=true;break;}temp = temp.next;}//判断flagif(flag){System.out.println(heroNode.no+"  这个编号被占了");}else {heroNode.next = temp.next;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);//一定要将next后移temp = temp.next;}}//根据newHeroNode的 no 修改就行public void updateSingleLinkedList(HeroNode newHeroNode){if (head.next == null){System.out.println("链表是空哒小老弟");return;}HeroNode temp = head;while (true){if(temp == null){System.out.println("没有 "+newHeroNode.no+" 号这个小朋友");break;}//找到了if(temp.no == newHeroNode.no){temp.name = newHeroNode.name;temp.nikeNode = newHeroNode.nikeNode;break;}temp = temp.next;}}//删除,想删几号嘉宾?public void deleteSingleLinkedList(int no){if (head.next == null){System.out.println("链表是空哒小老弟");return;}HeroNode temp = head;//自己写的,绕了点
//        while (true){//            //尾删,不管咋删注意遍历的temp指向的是欲删节点的前一个节点
//            if(temp.next.no == no && temp.next.next==null){//                temp.next = null;
//                System.out.println("删除了"+ no +"号嘉宾");
//                break;
//            }
//            //尾之前的删
//            if(temp.next.no == no){//                temp.next = temp.next.next;
//                System.out.println("删除了"+ no +"号嘉宾");
//                break;
//            }
//            temp = temp.next;
//            //注意判断遍历完的位置
//            if(temp.next == null){//                System.out.println("没有 "+ no +" 号这个小朋友");
//                break;
//            }
//        }//大佬的思维//先判断能不能删!默认不能boolean flag = false;while (true){if(temp.next == null){System.out.println("没有 "+no+" 号这个小朋友");break;}if(temp.next.no == no){flag = true;break;}temp = temp.next;}if (flag){temp.next = temp.next.next;}}
}//定义一个HeroNode,每个对象是一个节点
class HeroNode{public int no;//能修改了那就改成publicpublic String name;public String nikeNode;//数据都放在next里面public HeroNode next;//这样构造是为了插入数据public HeroNode(int hno,String hName,String hNikeName){this.no = hno;this.name = hName;this.nikeNode = hNikeName;}//显示方便,重写toString//注意不要打印next,每一层next都包含着本层后面所有数据@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nikeNode='" + nikeNode +'}';}
}
  • 被删除的节点不会有任何引用指向它,会被jvm垃圾回收机制清理。

  • 回顾单链表:

    • 链表存储的形式
    • 逻辑示意图-像羊肉串
    • 链表的增删改查
    • 大佬的思维:修改和删除–标志位法,先找到该节点(删除-节点前一项)

1.3.5 单链表经典面试题

  1. 求单链表中节点个数
  1. 查找单链表倒数第k个节点 – 新浪
  1. 单链表反转 – 腾讯
  1. 从尾到头打印单链表 --要求:方法1.反向遍历 方法2.Stack栈 – 百度
  1. 合并两个有序链表,合并之后的链表依然有序
  • 尝试实现-

  • 方便显示五道题互不干涉,实现的方法写在一起

    • 实现成功:
      • 计算节点个数: T(n) = n
      • 查找单链表倒数第k个节点: T(n) = n
      • 从尾到头打印单链表: T(n) = n2
    • 未实现成功
      • 反转 — 不了解栈的具体操作实现,暴力遍历…
    • 欠缺思路
      • 合并

public class dom5 {public static void main(String[] args) {//创建对象User u1 = new User(1, "赵信");User u2 = new User(2, "盖伦");User u3 = new User(3, "嘉文");User u4 = new User(4, "铁苍蝇");User u5 = new User(5, "狗头");//创建链表管理器SingleLinkedList2 singleLinkedList2 = new SingleLinkedList2();//测试添加singleLinkedList2.addUser(u1);singleLinkedList2.addUser(u4);singleLinkedList2.addUser(u2);singleLinkedList2.addUser(u3);//测试显示singleLinkedList2.showUser();//测试计数器System.out.println(singleLinkedList2.UserNum());//测试倒数第 k 个数据打印singleLinkedList2.reciprocalByK(1);singleLinkedList2.reciprocalByK(2);singleLinkedList2.reciprocalByK(3);singleLinkedList2.reciprocalByK(4);singleLinkedList2.reciprocalByK(5);singleLinkedList2.reciprocalByK(-1);//先测尾插---添加时候只能添加新的节点对象,不能重复添加,重复的链表对象只要不是尾插next里面不是null,容易造成死循环singleLinkedList2.add(u5);//测试反转---测试失败!!!!!!!!!!!!!!!!!!!!!!!!!!!!//singleLinkedList2.reversal();//System.out.println("----------------");//singleLinkedList2.showUser();//测试反向打印singleLinkedList2.reciprocalPrintln();}
}class SingleLinkedList2{//头节点User head = new User(0,"");//给尾插用(受反转影响)User str = head;//有序添加,自动排序public void addUser(User user){User temp = head;//能不能插入,默认不能boolean flag = false;while (true) {if(temp.next == null){flag = true;break;}if(temp.next.id>user.id){flag = true;break;}else if(temp.next.id==user.id){break;}temp = temp.next;}if(flag){user.next = temp.next;temp.next = user;}else {System.out.println(user.id+"数据重复,插入失败");}}//尾插--反转用得到public void add(User user){while (true){if(str.next == null){break;}str = str.next;}str.next = user;}//显示所有数据public void showUser(){if(head.next==null){System.out.println("链表为空");return;}User temp = head.next;while (true) {if(temp == null){break;}System.out.println(temp);temp = temp.next;}}//计算节点数public int UserNum(){if(head.next == null){return 0;}User temp = head.next;int con = 0;while (true){if(temp == null){return con;}con++;temp = temp.next;}}//打印倒数第k个对象public void reciprocalByK(int k){if (head.next == null){System.out.println("链表为空");return;}if(k < 1){System.out.println("无效参数");return;}int num = UserNum();if(k>num){System.out.println("链表当前仅有 " + num +"条数据");return;}User temp = head.next;while (num-k > 0){num--;temp = temp.next;}System.out.println("倒数第 "+ k + " 条数据为" + temp);}//链表反转--先写的反向打印--反向都是一个思路  失败!!!!!!!!!!!!!public void reversal(){if (head.next == null){System.out.println("链表为空");return;}int num = UserNum();//这里得用一个新临时头节点代表之前的链表头,真正的head得变了User newHead = head.next;User temp = head.next;while (num>0){int i = num;while (true){if(i == 1){//调用尾插-- 置空,只插一条数据User user = temp;user.next = null;add(user);break;}i -- ;temp = temp.next;}num --;//头节点后移head = head.next;temp = newHead.next;}}//链表反向打印---有UserNum干啥都方便public void reciprocalPrintln(){if (head.next == null){System.out.println("链表为空");return;}int num = UserNum();User temp = head.next;while (num>0){int i = num;while (true){if(i == 1){System.out.println(temp);break;}i -- ;temp = temp.next;}num --;temp = head.next;}System.out.println("反向打印完毕");}
}//链表对象
class User{public int id;public String name;public User next;public User(int Uid , String Uname){this.id = Uid;this.name = Uname;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name +'}';}
}
  • 来看大佬的思维
//单链表面试题
//        > 1. 求单链表中节点个数
//        > 2. 查找单链表倒数第k个节点  -- 新浪
//        > 3. 单链表反转  -- 腾讯
//        > 4. 从尾到头打印单链表 --要求:方法1.反向遍历  方法2.Stack栈   -- 百度
//        > 5. 合并两个有序链表,合并之后的链表依然有序import java.util.Stack;public class dom5 {public static void main(String[] args) {//创建对象User u1 = new User(1, "赵信");User u2 = new User(2, "盖伦");User u3 = new User(3, "嘉文");User u4 = new User(4, "铁苍蝇");User u5 = new User(5, "狗头");//头节点User head = new User(0,"");//创建链表管理器SingleLinkedList2 singleLinkedList2 = new SingleLinkedList2();//测试添加singleLinkedList2.addUser(head,u1);singleLinkedList2.addUser(head,u4);singleLinkedList2.addUser(head,u2);singleLinkedList2.addUser(head,u3);//测试显示
//        singleLinkedList2.showUser(head);//测试计数器
//        System.out.println(singleLinkedList2.UserNum(head));//测试倒数第 k 个数据打印---我的写法
//        singleLinkedList2.reciprocalByK(head,1);
//        singleLinkedList2.reciprocalByK(head,2);
//        singleLinkedList2.reciprocalByK(head,3);
//        singleLinkedList2.reciprocalByK(head,4);
//        singleLinkedList2.reciprocalByK(head,5);
//        singleLinkedList2.reciprocalByK(head,-1);//        System.out.println("倒数第k个节点,老师写法");
//        User test1 = singleLinkedList2.find(head,1);
//        User test2 = singleLinkedList2.find(head,2);
//        User test3 = singleLinkedList2.find(head,3);
//        User test4 = singleLinkedList2.find(head,4);
//        User test5 = singleLinkedList2.find(head,5);
//        System.out.println(test1);
//        System.out.println(test2);
//        System.out.println(test3);
//        System.out.println(test4);
//        System.out.println(test5);//测试反转---测试失败!!!!!!!!!!!!!!!!!!!!!!!!!!!!//singleLinkedList2.reversal();//System.out.println("----------------");//singleLinkedList2.showUser();//测试反转,新思路:从倒数第二个开始依次向前遍历添加到最后面//测试反转---测试失败!!!!!!!!!!!!!!!!!!!!!!!!!!!!//System.out.println("------------------------");//singleLinkedList2.reversal2(head);//singleLinkedList2.showUser(head);//失败总结:::永远不要在同一条链中有复制粘贴类的想法!!!!,它会出现同手同脚,删除时两个位置全部删,赋值死循环的问题。//单链表反转---测试老师的代码
//        System.out.println("原版:");
//        singleLinkedList2.showUser(head);
//        System.out.println("反转后");
//        SingleLinkedList2.reversalList(head);
//        singleLinkedList2.showUser(head);//测试反向打印//singleLinkedList2.reciprocalPrintln();//测试stack栈的逆向打印方式,不会改变链表结构System.out.println("原版:");singleLinkedList2.showUser(head);System.out.println("逆向打印");SingleLinkedList2.reversePrint(head);}
}class SingleLinkedList2{//有序添加,自动排序public void addUser(User head,User user){User temp = head;//能不能插入,默认不能boolean flag = false;while (true) {if(temp.next == null){flag = true;break;}if(temp.next.id>user.id){flag = true;break;}else if(temp.next.id==user.id){break;}temp = temp.next;}if(flag){user.next = temp.next;temp.next = user;}else {System.out.println(user.id+"数据重复,插入失败");}}//尾插--反转用得到--实际上尾插可以,用尾插的反转没成功public static void add(User head,User user){User str = head;while (true){if(str.next == null){break;}str = str.next;}str.next = user;}//显示所有数据public void showUser(User head){if(head.next==null){System.out.println("链表为空");return;}User temp = head.next;while (true) {if(temp == null){break;}System.out.println(temp);temp = temp.next;}}//计算节点数public static int UserNum(User head){if(head.next == null){return 0;}User temp = head.next;int length = 0;while (temp != null){length ++;temp = temp.next;}return length;}//打印倒数第k个对象public static void reciprocalByK(User head,int k){if (head.next == null){System.out.println("链表为空");return;}if(k < 1){System.out.println("无效参数");return;}int num = UserNum(head);if(k>num){System.out.println("链表当前仅有 " + num +"条数据");return;}User temp = head.next;while (num-k > 0){num--;temp = temp.next;}System.out.println("倒数第 "+ k + " 条数据为" + temp);}//打印第k个节点,老师写法public static User find(User head,int index){if(head.next == null){return null;}//第一次遍历得到链表长度int num = UserNum(head);User temp = head.next;if(index < 1 || index - num > 0){System.out.println("无效参数");return null;}while (num-index > 0){num--;temp = temp.next;}return temp;}//链表反转--先写的反向打印--反向都是一个思路public static void reversal(User head) {if (head.next == null){System.out.println("链表为空");return;}int num = UserNum(head);//这里得用一个新临时头节点代表之前的链表头,真正的head得变了User newHead = head.next;User temp = head.next;while (num>0){int i = num;while (true){if(i == 1){//调用尾插-- 置空,只插一条数据User user = temp;user.next = null;add(head,user);break;}i -- ;temp = temp.next;}num --;//头节点后移head = head.next;temp = newHead.next;}}//链表反转--新思路--从倒数第二个开始依次向前遍历添加到最后面--遍历length-1次public  void reversal2(User head){if(head.next == null){System.out.println("链表为空");return;}int length = UserNum(head)-1;User temp = head;while(length>0){//临时存储数据User sum;//找到原链表倒数第二个数据while (true){if(temp.next.next.next == null){sum = temp;while (true){if(temp.next == null){temp.next = sum.next;temp.next.next = null;break;}temp = temp.next;}break;}temp = temp.next;}//删除原位置节点sum.next = sum.next.next;temp = head.next;length--;}}//关于反转看看老师的思路public static void reversalList(User head){//只有一个就没必要了if(head.next == null || head.next.next == null){return;}//定义辅助指针,帮助遍历原来链表User cur = head.next;//指向当前节点的下一个节点User next = null;//新的表头User reverseHead = new User(0,"");//遍历原来链表,每遍历一个节点将其取出,放在node前面while (cur != null){//暂时保存下一个节点next = cur.next;//将cur下一个节点指向新链表最前端--不要原链表后面的了!!cur.next = reverseHead.next;//将原链表连接到新链表reverseHead.next = cur;cur = next;}//连接head.next = reverseHead.next;}//链表反向打印---有UserNum干啥都方便public void reciprocalPrintln(User head){if (head.next == null){System.out.println("链表为空");return;}int num = UserNum(head);User temp = head.next;while (num>0){int i = num;while (true){if(i == 1){System.out.println(temp);break;}i -- ;temp = temp.next;}num --;temp = head.next;}System.out.println("反向打印完毕");}//反向打印:老师思维路线---1.先反转,再打印(时间复杂度为n,无需嵌套,完美方法),但是会破坏原链表结构,这个可以实现就不用了。//2.利用栈数据结构,将各个节点压入到栈中,利用栈的先进后出特点。public static void reversePrint(User head){if(head.next == null || head.next.next == null){return;}Stack<User> stack = new Stack<>();User temp = head.next;while (temp != null){//入栈,先进stack.push(temp);temp = temp.next;}//出栈,后出while (stack.size()>0){System.out.println(stack.pop());}}}//链表对象
class User{public int id;public String name;public User next;public User(int Uid , String Uname){this.id = Uid;this.name = Uname;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name +'}';}
}
  • 算法亮点及收获:
    • 静态方法
    • 代码更加简洁
    • 反转:对同一链表不能做copy类行为,用一条新链表做临时变量,原表顺序遍历再到新表从前插队,时间复杂度 T(n) = n; 高效简单易读的算法!
    • 反向打印:之前的算法反向遍历类似暴力排序,T(n) = n2; 不推荐使用,stack栈的操作真的是方便快捷。

1.3.6 双向链表

  • 单链表体现出来的不足:

    • 每次只能从头并且只有一个方向遍历,双向链表有两个方向
    • 单链表删除只能依赖前一个节点,不能自我删除,双向链表可以实现自我删除
  • 双向链表创建与基本增删改查功能实现

public class dom6 {public static void main(String[] args) {Student st1 = new Student(1, "张三");Student st2 = new Student(2, "李四");Student st3 = new Student(3, "王二");Student st4 = new Student(4, "马六");Student st5 = new Student(4, "老八");Student st6 = new Student(1, "老八");Student st7 = new Student(0, "老八");Student st8 = new Student(5, "老八");//插入SingleLinkedList3 singleLinkedList3 = new SingleLinkedList3();singleLinkedList3.addById(singleLinkedList3.getHead(), st1);singleLinkedList3.addById(singleLinkedList3.getHead(), st3);singleLinkedList3.addById(singleLinkedList3.getHead(), st4);singleLinkedList3.addById(singleLinkedList3.getHead(), st2);singleLinkedList3.list(singleLinkedList3.getHead());//修改
//        singleLinkedList3.update(singleLinkedList3.getHead(), st5);
//        singleLinkedList3.update(singleLinkedList3.getHead(), st6);
//        singleLinkedList3.update(singleLinkedList3.getHead(), st7);
//        singleLinkedList3.update(singleLinkedList3.getHead(), st8);
//        singleLinkedList3.list(singleLinkedList3.getHead());//删除
//        singleLinkedList3.delete(singleLinkedList3.getHead(),1);
//        singleLinkedList3.delete(singleLinkedList3.getHead(),2);
//        singleLinkedList3.delete(singleLinkedList3.getHead(),4);
//        singleLinkedList3.delete(singleLinkedList3.getHead(),5);
//        singleLinkedList3.list(singleLinkedList3.getHead());}
}//双向链表管理器类
class SingleLinkedList3{Student head = new Student(0,"");//获取头节点public Student getHead(){return head;}//遍历双向链表public void list(Student head){if(head.next == null){System.out.println("链表为空");}Student temp = head.next;while (temp != null){System.out.println(temp);temp = temp.next;}}//尾插public void add(Student head,Student student){Student temp = head;while (true){if(temp.next == null){temp.next = student;student.pre = temp;break;}temp = temp.next;}}//添加--按顺序public void addById(Student head , Student student){Student temp = head;//判断能不能插入,默认为trueboolean flag = true;//找插入位置while (true){if(temp.next == null){break;}if(temp.next.id > student.id){break;}else if(temp.next.id == student.id){flag = false;}temp = temp.next;}if(flag){student.next = temp.next;temp.next = student;student.pre = temp;if(student.next != null){student.next.pre = student;}}else {System.out.println(student.id + ":该编号已存在");}}//修改public void update(Student head , Student student){if(head.next == null){System.out.println("链表为空");return;}boolean flag = false;Student temp = head.next;while (true){if(temp.id == student.id){flag = true;break;}if(temp.next == null){break;}temp = temp.next;}if(flag){temp.name = student.name;}else {System.out.println("查无此人");}}//删除public void delete(Student head , int index){if(head.next == null){System.out.println("链表为空");return;}Student temp = head.next;boolean flag = false;while (true){if(temp == null){break;}if(temp.id == index){if(temp.next == null){temp.pre.next = null;return;}flag = true;break;}temp = temp.next;}if(flag){temp.pre.next = temp.next;temp.next.pre = temp.pre;}else {System.out.println("无效参数");}}
}//双向链表类
class Student{int id;String name;Student next;Student pre;public Student(int id,String name){this.id = id;this.name = name;}@Overridepublic String toString() {return "Student{" +"id=" + id +", name='" + name + '\'' +'}';}
}

1.3.7 单向环形链表与约瑟夫问题

  • Josephu 约瑟夫环

约瑟夫问题详情

  • 编号为1,2,3…的n个人围坐在一圈,约定编号为k的人从1报数,数到m的人出列,以此类推,直到所有人出列,求产生的出队编号。

  • 分析:单项环形链表!

  • 先自己写一个,不难

public class dom7 {public static void main(String[] args) {SingleLinkedList4 singleLinkedList4 = new SingleLinkedList4();//边界测试,边界值为  n>0,m>0singleLinkedList4.GetJosepho(0,1);singleLinkedList4.GetJosepho(1,0);singleLinkedList4.GetJosepho(0,0);singleLinkedList4.GetJosepho(1,1);//测试约瑟夫问题singleLinkedList4.GetJosepho(5,9);singleLinkedList4.GetJosepho(10,9);singleLinkedList4.GetJosepho(10,1);}
}class SingleLinkedList4{//创建环形链表,要几个节点?返回的节点为环尾,即josephu.next = 环首public Josephu getJosephu(int n){//初始化头Josephu josephu = new Josephu(0);//记录环首Josephu Next = null;int length = n;int i = 1;while (length>0){josephu.next = new Josephu(i);if(i == 1){Next = josephu.next;}i++;length --;josephu = josephu.next;}josephu.next = Next;return josephu;}//打印约瑟夫环算法  n: 多少人    m: 第几个出列public void GetJosepho(int n , int m){//完成合理性校验,即参数正确,环不为空if(n<1 || m<1){System.out.println("无效参数");return;}//得到约瑟夫环及所有数据Josephu josephu = getJosephu(n);int num = 1;while (n - num > -1){int sum = m;while (true){if(sum==1){System.out.println("第" + num +"个出列的为:"+josephu.next);josephu.next = josephu.next.next;break;}sum--;josephu = josephu.next;}num++;}}
}//节点
class Josephu{public int id;public Josephu next;public Josephu(int id){this.id = id;}@Overridepublic String toString() {return "Josephu{" +"id=" + id +'}';}
}
  • 老师的方式:创建思路一致,出圈方式都是数到一个m删一个

  • 原链表管理器上加一个遍历链表的方法

    //遍历环public void showJosepho(int n){if(n<1){System.out.println("无效参数");return;}//得到约瑟夫环及所有数据Josephu josephu = getJosephu(n);//尽量不要改变公用参数   nint i = n;while (i-->0){System.out.println(josephu.next);josephu = josephu.next;}}

1.4 栈

1.4.1 数组模拟栈

  • stack – 先入后出的有序列表
  • 限制只能在线性表的同一端进行的特殊线性表,任何操作只能在栈顶操作,栈底固定
  • 子程序调用:跳往子程序时将主程序的下一步的地址存到栈中,子程序执行完毕,从栈中取出地址,继续主程序
  • push:入 pop:出

数组模拟栈

import java.util.Scanner;public class domTow01 {//测试数组模拟的栈public static void main(String[] args) {Stack stack = new Stack(2);//用循环菜单String key = "";boolean loop = true;//控制退出菜单Scanner scanner = new Scanner(System.in);while (loop){System.out.println("show:显示栈");System.out.println("push:添加数据");System.out.println("pop:取出数据");System.out.println("exit:退出程序");System.out.println("num:栈内数据个数");//接收数据key = scanner.next();switch (key){case "show":stack.list();break;case "push":System.out.println("请输入:");int value = scanner.nextInt();try {stack.push(value);} catch (Exception e) {//提示错误信息就行,不关闭进程System.out.println(e.getMessage());}break;case "pop":stack.pop();break;case "exit"://退出一定要释放资源!scanner.close();loop = false;break;case "num":System.out.println(stack.num());break;default:System.out.println("what?????????");break;}}}
}//定义栈结构类
class Stack{private int maxSize;private int arr[];//栈顶private int top = -1;//构造器public Stack(int maxSize){this.maxSize = maxSize;arr = new int[maxSize];}//判断栈满public boolean isFull(){return top != maxSize - 1 ?false:true;}//判断栈空public boolean isNoone(){return top == -1?true:false;}//入栈public void push(int value){if(isFull()){System.out.println("栈满");}top ++;arr[top] = value;}//出栈public int pop(){if(isNoone()){//这里必须有返回值,不能return 0,用异常处理的方式throw new RuntimeException("栈空");}top -- ;return arr[top+1];}//遍历栈--从栈顶显示数据public void list(){if (isNoone()){System.out.println("栈空");}//遍历不能改变原始结构-即top位置for (int i = top;i>-1;i--){System.out.println(arr[i]);}}//栈内数据个数public int num(){return top+1;}
}
  • 基础巩固:一定要注意数组对象的创建,异常处理的使用方式,中断进程or不中断进程打印异常信息。

1.4.2 链表模拟栈

  • 这个比数组好写,只用链表最基础简单的结构就能实现
public class domTow02 {public static void main(String[] args) {Stack2 stack1 = new Stack2(1);Stack2 stack2 = new Stack2(2);Stack2 stack3 = new Stack2(3);Stack2 stack4 = new Stack2(4);//测试,出入遍历随便写
//        SingleLinkedList5 singleLinkedList5 = new SingleLinkedList5(2);
//        singleLinkedList5.push(singleLinkedList5.getHead(), stack1);
//        singleLinkedList5.list(singleLinkedList5.getHead());
//        System.out.println("成功取出:"+singleLinkedList5.pop(singleLinkedList5.getHead()));
//        singleLinkedList5.push(singleLinkedList5.getHead(), stack2);
//        singleLinkedList5.push(singleLinkedList5.getHead(), stack3);
//        singleLinkedList5.push(singleLinkedList5.getHead(), stack4);
//        singleLinkedList5.list(singleLinkedList5.getHead());
//        System.out.println("成功取出:"+singleLinkedList5.pop(singleLinkedList5.getHead()));
//        System.out.println("成功取出:"+singleLinkedList5.pop(singleLinkedList5.getHead()));
//        System.out.println("成功取出:"+singleLinkedList5.pop(singleLinkedList5.getHead()));}
}//链表对象类
class Stack2{int id;Stack2 next;public Stack2(int id){this.id = id;}@Overridepublic String toString() {return "Stack2{" +"id=" + id +'}';}
}//链表管理器类
class SingleLinkedList5{Stack2 head = new Stack2(0);int maxSize;int nowSize=0;//栈最大容量public SingleLinkedList5(int maxSize){this.maxSize = maxSize;}//获取头节点public Stack2 getHead(){return head;}//添加---模拟栈只能是尾插public void push(Stack2 head,Stack2 stack){if(nowSize == maxSize){System.out.println("栈满");return;}Stack2 temp = head;while (true){if (temp.next == null){temp.next = stack;break;}temp = temp.next;}nowSize ++;}//取出---同样只能尾插public Stack2 pop(Stack2 head){if(head.next == null && nowSize == 0){System.out.println("栈空");return null;}Stack2 temp = head;while (true){if(temp.next.next==null){Stack2 cur = temp.next;temp.next = null;nowSize --;return cur;}temp = temp.next;}}//遍历public void list(Stack2 head){if(head.next == null && nowSize == 0){System.out.println("栈空");return;}Stack2 temp = head.next;while (true){if (temp == null){break;}System.out.println(temp);temp = temp.next;}}
}

1.4.3 栈实现综合计算器

  • 树栈:存放表达式

  • 符号栈:存放运算符

  • 整数型跟char型可以混用!!!符号底层也是一个数字来表示

算法思路

  • 通过index(索引),遍历表达式
  • 数字—直接入树栈
  • 符号—入符号栈
    • 当前符号栈为空,直接入栈
    • 符号栈有操作符,进行比较
      • 当前操作符优先级小于等于栈中操作符,需要从树栈中pop出两个数,从符号栈中pop一个符号,进行运算,计算结果入树栈,当前符号入符号栈
      • 当前操作符的优先级大于栈中的操作符,直接入符号栈
  • 表达式扫描完毕,顺序从树栈和符号栈中pop出相应的数字和符号,运算
  • 最后树栈中只有一个数字,符号栈为空,就是表达式的结果
  • 代码实现

//栈模拟简单计算器代码实现
public class domTow04 {public static void main(String[] args) {//创建树栈和符号栈Stack3 numStack = new Stack3(10);Stack3 operStack = new Stack3(10);//测试的表达式String str = "1+2*3-1";//需要的变量int num1 = 0;int num2 = 0;int oper = 0; //符号  : char型和整型可以混用int ver = 0; //计算结果int index = 0; //扫描的索引char ch = ' ';//当前扫描到的数据//扫描遍历while (true) {//扫描依次得到表达式每一个字符ch = str.substring(index , index+1).charAt(0);//记住字符串的substring方法//如果是符号if (numStack.isOper(ch)) {//判断符号栈是否为空if (operStack.isNoone()) {operStack.push(ch);} else {//判断符号优先级//当前优先级小于等于栈中符号的优先级if (operStack.priority(ch) <= operStack.priority(operStack.watch())) {num1 = numStack.pop();num2 = numStack.pop();oper = operStack.pop();ver = numStack.cal(num1, num2, oper);numStack.push(ver);//算完一定要将当前的运算符放到符号栈operStack.push(ch);} else {//当前优先级大于栈中符号的优先级operStack.push(ch);}}} else {//不是符号则直接入数栈//第一次入栈时一定要注意! 遍历出来的是字符,表达的数字与真实有差异,想要字符转数字,ch - 48 或 ch - '0'numStack.push(ch-'0');}index++;//遍历结束if (index == str.length()) {break;}}//对扫描后的数栈跟符号栈顺序遍历计算while (true) {//若符号栈为空,则不需要计算if (operStack.isNoone()) {ver = numStack.pop();break;}//顺序计算num1 = numStack.pop();num2 = numStack.pop();oper = operStack.pop();ver = numStack.cal(num1, num2, oper);numStack.push(ver);}//输出计算结果System.out.println("计算:" + str + " = " + ver);}
}//先创建一个栈
class Stack3{private int maxSize;private int arr[];//栈顶private int top = -1;//构造器public Stack3(int maxSize){this.maxSize = maxSize;arr = new int[maxSize];}//判断栈满public boolean isFull(){return top != maxSize - 1 ?false:true;}//判断栈空public boolean isNoone(){return top == -1?true:false;}//入栈public void push(int value){if(isFull()){System.out.println("栈满");}top ++;arr[top] = value;}//出栈public int pop(){if(isNoone()){//这里必须有返回值,不能return 0,用异常处理的方式throw new RuntimeException("栈空");}top -- ;return arr[top+1];}//遍历栈--从栈顶显示数据public void list(){if (isNoone()){System.out.println("栈空");}//遍历不能改变原始结构-即top位置for (int i = top;i>-1;i--){System.out.println(arr[i]);}}//栈内数据个数public int num(){return top+1;}//在原来的数组栈中扩展功能//返回运算符优先级---这是程序员定的//运算符优先级由数字表示public int priority(int oper){if(oper == '+' || oper == '-'){return 0;}else if(oper == '*' || oper == '/'){return 1;}else {//假定只有加减乘除功能return -1;}}//判断是不是运算符public boolean isOper(char val){return val == '+' || val == '-' || val == '*' || val == '/' ;}//计算方法---注意计算顺序public int cal(int num1 , int num2 , int oper){int ver = 0;switch (oper){case '+':ver = num1 + num2;break;case '-':ver = num2 - num1;break;case '*':ver = num1 * num2;break;case '/':ver = num2 / num1;break;default:break;}return ver;}//查看栈顶数据,不是poppublic int watch(){return arr[top];}
}
  • 以上代码只能实现一位数的运算,不能实现多位数运算
  • 改进后的代码如下—只改扫描遍历那块即可
        String keepNum = "";  //定义字符串变量,拼接字符串//扫描遍历while (true) {//扫描依次得到表达式每一个字符ch = str.substring(index , index+1).charAt(0);//记住字符串的substring方法//如果是符号if (numStack.isOper(ch)) {//判断符号栈是否为空if (operStack.isNoone()) {operStack.push(ch);} else {//判断符号优先级//当前优先级小于等于栈中符号的优先级if (operStack.priority(ch) <= operStack.priority(operStack.watch())) {num1 = numStack.pop();num2 = numStack.pop();oper = operStack.pop();ver = numStack.cal(num1, num2, oper);numStack.push(ver);//算完一定要将当前的运算符放到符号栈operStack.push(ch);} else {//当前优先级大于栈中符号的优先级operStack.push(ch);}}} else {//不是符号则直接入数栈//第一次入栈时一定要注意! 遍历出来的是字符,表达的数字与真实有差异,想要字符转数字,ch - 48 或 ch - '0'//numStack.push(ch-'0');//不能发现是一个数字直接入数栈,可能是多位数//定义字符串变量keepNum,拼接字符串//遍历当前数字的后一位,是数字则继续遍历,符号入栈keepNum += ch;if (index == str.length() - 1) {//如果遍历到最后一位,直接入栈,避免index+2的指针溢出问题numStack.push(Integer.parseInt(keepNum));} else {if (numStack.isOper(str.substring(index + 1, index + 2).charAt(0))) {//下一位是字符,将字符串直接入栈---强转numStack.push(Integer.parseInt(keepNum));//入栈后keepNum置空keepNum = "";} else {}}}index++;//遍历结束if (index == str.length()) {break;}}

1.5 前缀,中缀,后缀表达式

1.5.1 简介

  • 前缀表达式(波兰表达式): 运算符位于数字之前
    • 从右向左扫描表达式 (3+4) * 5 -6 ---- - * + 3 4 5 6
  • 中缀表达式: 常见的运算表达式: (3+4) * 5 -6
    • 对于人简洁明了:对于计算机较为困难
  • 后缀表达式(逆波兰表达式): 运算符位于数字之后
    • 对计算机来说最方便的表达式
    • 从左到右依次扫描,遇到符号就计算,不用考虑优先级
    • (3+4) * 5 -6 ---- 3 4 + 5 * 6 -
    • a + b ---- a b +
    • a + (b - c) — a b c - +
    • a + (b - c) * d — a b c - d * +
    • a + d * (b - c) ---- a d b c - * +
    • a = b + c — a b c + =

1.5.2 逆波兰计算器

  • 输入后缀表达式,使用栈实现
  • 支持小括号及多位整数
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;public class domTow05 {public static void main(String[] args) {//定义逆波兰表达式//为方便数字和符号中使用空格隔开String expression = "3 4 + 5 * 6 -";List<String> list = getListString(expression);System.out.println("list = " + list);System.out.println(cal(list));}//将表达式依次放入ArrayListpublic static List<String> getListString(String expression){//分割表达式,返回一个String数组String[] split = expression.split(" ");List<String> list = new ArrayList<String>();for (String s : split) {//将split数组里面的数据依次放入listlist.add(s);}return list;}//运算式public static int cal(List<String> list){//只需要一个栈就行Stack<String> stack = new Stack<String>();//遍历listfor(String item : list){//使用正则表达式取出数if(item.matches("\\d+")){//匹配多位数stack.push(item);}else {//遇到符号就计算int num2 = Integer.parseInt(stack.pop());int num1 = Integer.parseInt(stack.pop());int res = 0;if(item.equals("+")){res = num1 + num2;}else if(item.equals("-")){res = num1 - num2;}else if(item.equals("*")){res = num1 * num2;}else if(item.equals("/")){res = num1 / num2;}else {throw new RuntimeException("错误符号");}//数字转字符串放到栈中stack.push(res + "");}}//时刻要注意数据形式转换return Integer.parseInt(stack.pop());}
}
  • 涉及到的基础回顾加强:
    • ArrayList
    • split 分割字符串: 将字符串变为字符串数组
    • 正则表达式
    • 格式转换: 数字转字符串 num + “” , 字符串转数字 Integer.parseInt()

1.5.3 中缀转后缀

  • 后缀表达式计算机理解方便,人不容易写出来,需要在开发中将中缀表达式自动转换为后缀表达式

步骤分析:

  1. 初始化两个栈,中间栈(中间结果),符号栈

  2. 顺序扫描表达式

  3. 遇到操作数,入中间栈

  4. 遇到运算符,比较优先级

    4.1 符号栈为空或左括号,直接入符号栈

    4.2 当前的优先级比栈顶高也入符号栈

    4.3 否则将符号栈栈顶运算符弹出并压入中间栈,当前运算符与符号栈栈顶运算符继续比较

  5. 遇到括号时:

    5.1 左括号直接入符号栈

    5.2 右括号:依次弹出栈顶运算符,压入中间栈,直到遇到右括号为止,将右括号丢弃

  6. 遍历到表达式完

  7. 将符号栈运算符全部依次放入中间栈

  8. 依次弹出中间栈元素,转为字符串并反转

//将中缀表达式转为对应的listpublic static List<String> toInfix(String s){//定义list,存放中缀表达式List<String> ls =  new ArrayList<String>();int i = 0; // 指针:遍历字符串sString str ; //拼接多位数char c ; //每遍历一个字符,放入listdo {//遍历到的非数字,直接加入到lsif((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57){ls.add("" + c);i ++ ;}else {//考虑多位数的问题str = "";  //先置空!while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57){str += c;  // 拼接字符串i ++ ;}ls.add("" + str);}}while (i < s.length());return ls;}//中缀转后缀表达式public static String parseSuffix(List<String> ls){//初始化两个栈Stack<String> milStack = new Stack<String>();Stack<String> operStack = new Stack<String>();//由于中间栈全程只是添加,用list更方便List<String> mil = new ArrayList<String>();//利用for取出list里面的数据for (String item : ls) {if(item.matches("\\d+")){//正则表达式匹配多位数//是数字直接入栈milStack.push(item);}else if(item.equals("(")){operStack.push(item);}else if(item.equals(")")){// 右括号:依次弹出栈顶运算符,压入中间栈,直到遇到右括号为止,将右括号丢弃String ch;while (!(ch = operStack.pop()).equals("(")){milStack.push(ch);}}else {//最后就是运算符了,这里需要比较优先级//如果为空,直接入栈if(operStack.size() == 0){operStack.push(item);}else if(priority(item) > priority(watch(operStack))){//当前的优先级比栈顶高也入符号栈operStack.push(item);}else {//否则将符号栈栈顶运算符弹出并压入中间栈,当前运算符与符号栈栈顶运算符继续比较while (true){milStack.push(operStack.pop());if(operStack.size() == 0){operStack.push(item);break;}if(priority(item) > priority(watch(operStack))){operStack.push(item);break;}}}}}//将符号栈运算符全部依次放入中间栈while (operStack.size() != 0){milStack.push(operStack.pop());}//要返回的字符串String str = "";//将中间栈的数据以字符串形式返回while (milStack.size() != 0){//拼接时候注意一下,省的再反转str = milStack.pop() + " " + str;}//删除最后一个空格str = str.substring(0 , str.length() - 1);return str;}//判断优先级public static int priority(String oper){if(oper.equals("+")|| oper.equals("-")){return 0;}else if(oper.equals("*") || oper.equals("/")){return 1;}else {//假定只有加减乘除功能return -1;}}//查看栈顶数据public static String watch(Stack<String> st){String str = st.pop();st.push(str);return str;}
  • main方法测试
//测试将中缀表达式转为listString str = "1+((2+3)*4)-5";List<String> infix = toInfix(str);System.out.println(infix);//测试转换后的表达式System.out.println(parseSuffix(infix));
  • 需要注意到的点:
    • 注意两个栈同时使用时进出栈别出错
    • 栈判空用它自己的size()方法
    • 在之后会有反转字符串加空格等操作时,在拼接的时候就可以拼接为想要的

1.5.4 综合逆波兰计算器

  • 将1.5.2 与1.5.3 里面的算法合并,就是完整的逆波兰计算器
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;public class domTow05 {public static void main(String[] args) {//测试将中缀表达式转为listString str = "1+((2+3)*4)-5";List<String> infix = toInfix(str);System.out.println(infix);//测试转换后的表达式System.out.println(parseSuffix(infix));//综合测试System.out.println(cal(getListString(parseSuffix(infix))));}//将表达式依次放入ArrayListpublic static List<String> getListString(String expression){//分割表达式,返回一个String数组String[] split = expression.split(" ");List<String> list = new ArrayList<String>();for (String s : split) {//将split数组里面的数据依次放入listlist.add(s);}return list;}//运算式public static int cal(List<String> list){//只需要一个栈就行Stack<String> stack = new Stack<String>();//遍历listfor(String item : list){//使用正则表达式取出数if(item.matches("\\d+")){//匹配多位数stack.push(item);}else {//遇到符号就计算int num2 = Integer.parseInt(stack.pop());int num1 = Integer.parseInt(stack.pop());int res = 0;if(item.equals("+")){res = num1 + num2;}else if(item.equals("-")){res = num1 - num2;}else if(item.equals("*")){res = num1 * num2;}else if(item.equals("/")){res = num1 / num2;}else {throw new RuntimeException("错误符号");}//数字转字符串放到栈中stack.push(res + "");}}//时刻要注意数据形式转换return Integer.parseInt(stack.pop());}//将中缀表达式转为对应的listpublic static List<String> toInfix(String s){//定义list,存放中缀表达式List<String> ls =  new ArrayList<String>();int i = 0; // 指针:遍历字符串sString str ; //拼接多位数char c ; //每遍历一个字符,放入listdo {//遍历到的非数字,直接加入到lsif((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57){ls.add("" + c);i ++ ;}else {//考虑多位数的问题str = "";  //先置空!while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57){str += c;  // 拼接字符串i ++ ;}ls.add("" + str);}}while (i < s.length());return ls;}//中缀转后缀表达式public static String parseSuffix(List<String> ls){//初始化两个栈Stack<String> milStack = new Stack<String>();Stack<String> operStack = new Stack<String>();//由于中间栈全程只是添加,用list更方便List<String> mil = new ArrayList<String>();//利用for取出list里面的数据for (String item : ls) {if(item.matches("\\d+")){//正则表达式匹配多位数//是数字直接入栈milStack.push(item);}else if(item.equals("(")){operStack.push(item);}else if(item.equals(")")){// 右括号:依次弹出栈顶运算符,压入中间栈,直到遇到右括号为止,将右括号丢弃String ch;while (!(ch = operStack.pop()).equals("(")){milStack.push(ch);}}else {//最后就是运算符了,这里需要比较优先级//如果为空,直接入栈if(operStack.size() == 0){operStack.push(item);}else if(priority(item) > priority(watch(operStack))){//当前的优先级比栈顶高也入符号栈operStack.push(item);}else {//否则将符号栈栈顶运算符弹出并压入中间栈,当前运算符与符号栈栈顶运算符继续比较while (true){milStack.push(operStack.pop());if(operStack.size() == 0){operStack.push(item);break;}if(priority(item) > priority(watch(operStack))){operStack.push(item);break;}}}}}//将符号栈运算符全部依次放入中间栈while (operStack.size() != 0){milStack.push(operStack.pop());}//要返回的字符串String str = "";//将中间栈的数据以字符串形式返回while (milStack.size() != 0){//拼接时候注意一下,省的再反转str = milStack.pop() + " " + str;}//删除最后一个空格str = str.substring(0 , str.length() - 1);return str;}//判断优先级public static int priority(String oper){if(oper.equals("+")|| oper.equals("-")){return 0;}else if(oper.equals("*") || oper.equals("/")){return 1;}else {//假定只有加减乘除功能return -1;}}//查看栈顶数据public static String watch(Stack<String> st){String str = st.pop();st.push(str);return str;}
}

1.6 递归

1.6.1 简介

  • 递归调用机制:自己调用自己

  • 调用一次开一个栈空间,每个空间独立

  • 空间复杂度!!!

  • 递归问题列举

public class domTow06 {public static void main(String[] args) {System.out.println(factorial(4));}//打印问题public static void test(int n){if(n >2){test(n - 1);}System.out.println(n);}//阶乘问题public static int factorial(int n){if(n == 1){return 1;}else {return factorial(n-1) * n;}}
}

1.6.2 递归能解决的问题和规则

  • 谷歌算法大赛 *
  • 算法中:快排,归并等算法问题
  • 迷宫问题,汉诺塔

规则

  • 执行一个方法,创建一个新的受保护的独立空间(栈空间)
  • 方法调用即在栈顶再压入一个栈空间,执行时候遵循栈的原则
  • 方法的局部变量不能互相影响,如果方法中使用的是引用类型变量(数组),则共享该引用类型的数据
  • 递归必须向退出递归的方向逼近,否则会无限循环,到栈满至栈溢出
  • 一个方法执行完毕,遇到return,谁调用返回给谁,同时当方法执行完毕或return时,该方法也执行完毕

1.6.3 迷宫问题

  • 实际可选路程为六行五列的迷宫,中间设置障碍
public class domTow07 {public static void main(String[] args) {//创建二维数组,模拟迷宫int[][] map = new int[8][7];//迷宫周边设置为墙//上下置为一for(int i = 0; i < 7 ; i ++){map[0][i] = 1;map[7][i] = 1;}//左右置为一for(int i = 0; i < 8 ; i ++){map[i][0] = 1;map[i][6] = 1;}//设置迷宫障碍map[3][1] = 1;map[3][2] = 1;//将路堵死测试
//        map[1][3] = 1;
//        map[2][3] = 1;
//        map[3][3] = 1;setWay(map , 1 , 1);showMap(map);}//地图情况public static void showMap(int[][] map){for (int[] ints : map) {for (int anInt : ints) {System.out.print(anInt + " ");}System.out.println();}}/*** map:地图* i:横轴* j:纵轴* boolean:判断找没找到* 出口  map[6][5]* 当地图上map[i][j] = 0:此路没有经过* 当地图上map[i][j] = 1:墙* 当地图上map[i][j] = 2:通路,可以走* 当地图上map[i][j] = 3:该位置已经走过,不通* 确定探路策略:下->右->上->左* 如果走不通:回溯!* *///使用递归找路  放入地图跟起始位置public static boolean setWay(int[][] map , int i , int j){if(map[6][5] == 2){return true;}else {if(map[i][j] == 0){ //如果这个点没有走过//按照策略走,假定可以走通map[i][j] = 2;if(setWay(map , i+1 , j)){   //向下走return true;}else if(setWay(map , i ,j+1)){  //向右走return true;}else if(setWay(map , i-1 , j)){  //向上走return true;}else if(setWay(map , i , j-1)){  //向左走return true;}else {//该点为死路,map[i][j] = 3;return false;}}else { //如果该点不等于0 , map可能为1,2,3//代表起点位置开始就走不通,返回false,该迷宫无解return false;}}}
}

1.6.4 迷宫最短路径问题

  • 小球的路径与程序员设计的路径有关
  • 当前算法调优只能限制找路方法
  • 上下左右的排序依次测试打印数组中2的个数最少的为最短路径
    //修改找路方法 上右下左public static boolean setWay2(int[][] map , int i , int j){if(map[6][5] == 2){return true;}else {if(map[i][j] == 0){ //如果这个点没有走过//按照策略走,假定可以走通map[i][j] = 2;if(setWay(map , i-1 , j)){   //向上走return true;}else if(setWay(map , i ,j+1)){  //向右走return true;}else if(setWay(map , i+1 , j)){  //向下走return true;}else if(setWay(map , i , j-1)){  //向左走return true;}else {//该点为死路,map[i][j] = 3;return false;}}else { //如果该点不等于0 , map可能为1,2,3//代表起点位置开始就走不通,返回false,该迷宫无解return false;}}}

1.6.5 八皇后问题

  • 问题描述:8*8的棋盘,放置八枚皇后棋子,要求每一行,每一列,每一条斜线只能有一枚棋子
  • 用一维数组就能实现----不画棋盘!!!
public class domTow08 {//定义共有多少个皇后int max = 8;//定义数组保存皇后被放置的位置--一维数组就可以int[] array = new int[max];//统计共有多少种方法int num = 0;public static void main(String[] args) {//测试八皇后算法new domTow08().check(0);}//放置第n个皇后private void check(int n){//当需要放置第九个皇后时,意味着八个皇后已经放置妥当if(n == max){num ++;System.out.print("第 " + num +" 种方法:");print();return;}/*** 核心的for循环!!!!!!!!!!!* 每次递归都有一个for循环,一定会执行完* 因为是for循环一定会执行完的原因,完成回溯的算法思想,能将所有可能性执行一遍* *///依次放入皇后,判断冲突for (int i = 0 ; i < max ; i ++){//先把当前皇后放到该行的第1列array[n] = i;//判断第n个皇后放到第i列是否冲突if(judge(n)){ //不冲突//接着放check(n+1);}}}//放置第n个皇后时,检测该皇后是否与之前的冲突,若冲突则为falseprivate boolean judge(int n) {for(int i = 0 ; i < n ; i ++){//是否在同一列,或同一斜线(即二维棋盘上横轴差等于纵轴差)//判断同一行没必要if(array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])){return false;}}return true;}//打印皇后位置--输出最后的结果private void print () {for (int i = 0; i < array.length; i++) {System.out.print(array[i] + " ");}System.out.println();}
}

1.7 哈希表

1.7.1 简介

  • key–value 结构 :根据关键字码值直接进行访问的数据结构
  • 通过关键码值映射到表中一个位置来访问记录,该映射关系函数为散列函数,存放记录的数组称为散列表(哈希表)
  • 缓存层
  • 数组+链表 — 链表数组
  • 数组+二叉树 — 二叉树数组

1.7.2 hash经典笔试题

  • 某公司,有新员工报道时,要求该员工的信息加入(id,姓名,性别,年龄,住址…)输入该员工id时,查到该员工所有信息。

    要求:不用数据库,速度快 => hash(散列)

import java.util.Scanner;public class domThree03 {public static void main(String[] args) {//创建Hash表HashTable hashTable = new HashTable(7);//写个菜单String key = "";Scanner scanner = new Scanner(System.in);while (true){System.out.println("add: 添加");System.out.println("list: 显示");System.out.println("exit: 退出");System.out.println("find: 查找");key = scanner.next();switch (key){case "add":System.out.println("输入id");int id = scanner.nextInt();System.out.println("输入姓名");String name = scanner.next();hashTable.add(new Emp(id,name));break;case "list":hashTable.list();break;case "exit":scanner.close();System.exit(0);case "find":System.out.println("输入id");int id2 = scanner.nextInt();hashTable.listByid(id2);break;default:System.out.println("好好输");break;}}}
}//链表对象类--员工信息类
class Emp{public int id;public String name;public Emp next;public Emp(int id , String name){this.id = id;this.name = name;}@Overridepublic String toString() {return "Emp{" +"id=" + id +", name='" + name  +'}';}
}//创建链表管理器
class EmpLinkedList{//链表的head,直接指向Emppublic Emp head;//添加--默认最后//id自分配,自增public void add(Emp emp){//如果是添加第一个雇员if (head == null){head = emp;return;}Emp cur = head;while (true){if (cur.next == null){break;}cur = cur.next;}cur.next = emp;}//遍历链表public void list(int no){if(head == null){System.out.println("第 "+ no +"条链表为空");return;}Emp cur = head;while (true){System.out.println("第"+ no +"条链表" + cur);if(cur.next == null){break;}cur = cur.next;}}//根据id查找雇员,找到返回Emp,没找到返回nullpublic Emp listById(int id){if(head == null){System.out.println("链表为空");return null;}Emp cur = head;while (true){if (cur.id == id){return cur;}if(cur.next == null){return null;}cur = cur.next;}}
}//hash表
class HashTable{//用管理器类创建叔祖private EmpLinkedList[] empLinkedListsArray;public int size;//构造器public HashTable(int size){this.size = size;//初始化empLinkedListsArray = new EmpLinkedList[size];//不能直接向空数组添加for (int i = 0 ; i < size ; i ++){empLinkedListsArray[i] = new EmpLinkedList();}}//添加雇员public void add(Emp emp){//根据id,得到该员工数据应该添加到那条链表int empLinkedListsNo = hashFun(emp.id);//将emp添加到对应的链表empLinkedListsArray[empLinkedListsNo].add(emp);}//编写散列函数public int hashFun(int id){return id % size;}//遍历哈希表public void list(){for (int i = 0; i < size ; i ++){empLinkedListsArray[i].list(i);}}//根据id查找public void listByid(int id){int empLinkedListsNo = hashFun(id);Emp emp = empLinkedListsArray[empLinkedListsNo].listById(id);if(emp != null){System.out.println(emp);}else {System.out.println("没找着");}}
}
  • 总的来说,就是在原来链表基础上,给链表管理器再套一层hashTable类—链表数组

1.8 树

1.8.1 存储结构

数组存储

  • 根据下表访问,访问速度快

  • 缺点:插入值需要整体移动,效率低

  • 数组扩容时需要创建新的数组,将原来数据拷贝到新数组

  • arrList集合(object类型数组)底层自适应扩容同样也是这个原理,但他是按照比例扩容

链表存储

  • 添加非常方便,避免了数组的扩容问题

  • 缺点:每次遍历都需要从头节点开始

树存储

  • 优化存储,读取的速度,保证插入,修改,删除的速度
  • 如果使用二叉排序树存储数据,对数据的增删改查都将提高

1.8.2 二叉树遍历

前序,中序,后续—父节点输出顺序来区分

  • 前序,根节点—左节点—右节点
  • 中序,左节点—头节点—右节点
  • 后续,左节点—右节点—头节点
//二叉树
public class domThree04 {public static void main(String[] args) {BinaryTree binaryTree = new BinaryTree();//创建节点Node n1 = new Node(1, "老王");Node n2 = new Node(2, "老张");Node n3 = new Node(3, "老刘");Node n4 = new Node(4, "老吴");Node n5 = new Node(5, "老孙");//暂且手动创建  n1为根节点n1.setLeft(n2);n1.setRight(n3);n2.setLeft(n4);n2.setRight(n5);//把根节点给到位binaryTree.setRoot(n1);System.out.println("前序");binaryTree.preOrder();System.out.println("中序");binaryTree.infixOrder();System.out.println("后序");binaryTree.postOrder();}
}//创建树
class BinaryTree {private Node root;public void setRoot(Node root) {this.root = root;}//前序遍历public void preOrder() {if(this.root != null){this.root.preOrder();}else {System.out.println("二叉树为空");}}//中序public void infixOrder() {if (this.root != null) {this.root.infixOrder();} else {System.out.println("二叉树为空");}}//后续public void postOrder(){if (this.root != null) {this.root.postOrder();} else {System.out.println("二叉树为空");}}
}//创建节点
class Node {private int id;private String name;private Node left;private Node right;//构造器public Node(int id , String name){this.id = id;this.name = name;}//前序遍历public void preOrder(){System.out.println(this);//先输出父节点//递归左子树if(this.left != null){this.left.preOrder();}//递归向右if(this.right != null){this.right.preOrder();}}//中序public void infixOrder(){//递归向左if(this.left != null){this.left.infixOrder();}//输出父节点System.out.println(this);if(this.right != null){this.right.infixOrder();}}//后续public void postOrder(){if(this.left != null) {this.left.postOrder();}if(this.right != null){this.right.postOrder();}System.out.println(this);}public int getId() {return id;}public void setId(int id) {this.id = id;}public Node getLeft() {return left;}public void setLeft(Node left) {this.left = left;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Node getRight() {return right;}public void setRight(Node right) {this.right = right;}@Overridepublic String toString() {return "Node{" +"id=" + id +", name='" + name +'}';}
}

1.8.3 二叉树查找

  • 前序中序后序查找

  • main函数

        //测试查找System.out.println("前序");binaryTree.preSeek(3);System.out.println("中序");binaryTree.infixSeek(3);System.out.println("后序");binaryTree.postSeek(3);
  • 节点类
    //查找方法//前序public Node preseek(int id) {System.out.println("进入前序遍历");//统计查询递归查找次数。下同if(this.id == id){return this;}Node falg = null;if(this.left != null){falg = this.left.preseek(id);}if(falg != null){return falg;}if(this.right != null){falg = this.right.preseek(id);}return falg;}//中序public Node infixSeek(int id) {Node falg = null;if(this.left != null){falg = this.left.infixSeek(id);}if(falg != null){return falg;}System.out.println("进入中序遍历");if(this.id == id){return this;}if(this.right != null){falg = this.right.infixSeek(id);}return falg;}//后序public Node postSeek(int id) {Node falg = null;if(this.left != null) {falg = this.left.postSeek(id);}if(falg != null){return falg;}if(this.right != null){falg = this.right.postSeek(id);}if(falg != null){return falg;}System.out.println("进入后续遍历");if (this.id == id){return this;}return falg;}
  • 树类
    //查找方法//前序public void preSeek(int id) {if (this.root != null) {System.out.println(this.root.preseek(id));} else {System.out.println("二叉树为空");}}//中序public void infixSeek(int id) {if (this.root != null) {System.out.println(this.root.infixSeek(id));} else {System.out.println("二叉树为空");}}//后序public void postSeek(int id) {if (this.root != null) {System.out.println(this.root.postSeek(id));} else {System.out.println("二叉树为空");}
  • 一定要搞明白递归思想,否则这里会绕

1.8.4 删除节点

  • 这里规定:

  • 如果是叶子节点,直接删除

  • 如果不是叶子节点,删除子树

  • 二叉树是单向的,参考链表删除节点思想

  • 树类

    //删除public void delet(int id) {if (this.root != null) {if(root.getId() == id){root = null ;System.out.println("删除成功");}else {root.delet(id);}} else {System.out.println("二叉树为空");}}
  • 节点类
    //删除节点(前序)public void delet(int id){if(this.left != null && this.left.id == id){this.left = null;return;}if (this.right != null && this.right.id == id){this.right = null;return;}if(this.left != null){this.left.delet(id);}if(this.right != null){this.right.delet(id);}}

1.8.5 顺序存储二叉树

  • 数组和树能互相转换
  • 必须满足是满二叉树 (完全二叉树) 的情况
    public static void main(String[] args) {int[] arr = {1 , 2 , 3 ,4 , 6 , 7};ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);arrBinaryTree.preOrder();}
}class ArrBinaryTree {private int[] arr;public ArrBinaryTree(int arr[]){this.arr = arr;}//重载遍历方法,使在主函数中更简洁public void preOrder(){preOrder(0);}//实现对数组以前序二叉树方式遍历public void preOrder(int index) {if(this.arr == null && arr.length < index){System.out.println("无法遍历");}//打印当前遍历位置的数据//前序,中序,后序只需要改变这句话与左右递归位置即可System.out.println(arr[index]);//主要核心在于左子节点下标为2n+1,右子节点为2n+2//左递归if(2*index + 1 < arr.length) {preOrder(2*index + 1);}//右递归if(2*index +2 < arr.length) {preOrder(2*index + 2);}}
}

1.8.6 线索化二叉树

  • 充分利用叶子节点的空指针
  • 叶子节点的左右指针分别指向前驱与后继节点
public class domThree08 {public static void main(String[] args) {Hn h1 = new Hn(1, "li");Hn h2 = new Hn(2, "wu");Hn h3 = new Hn(3, "su");Hn h4 = new Hn(4, "lu");Hn h5 = new Hn(5, "gu");BinaryTree2 binaryTree2 = new BinaryTree2();binaryTree2.setRoot(h1);h1.setLeft(h2);h1.setRight(h3);h2.setLeft(h4);h2.setRight(h5);binaryTree2.threadHd();//测试线索化后叶子节点的左右子树是否指向前驱/后继节点System.out.println(h4.getRight());}
}//节点
class Hn {public int id ;public String name;private Hn left;private Hn right;/*** 标记该节点是否为叶子节点,在遍历时需要该参数* leftType:  0 ->左子树   1 -> 前驱节点* rightType:  0 ->右子树   1 -> 后继节点* */private int leftType;private int rightType;public Hn(int id , String name) {this.id = id;this.name = name;}@Overridepublic String toString() {return "Hn{" +"id=" + id +", name='" + name +'}';}public Hn getLeft() {return left;}public void setRight(Hn right) {this.right = right;}public Hn getRight() {return right;}public void setLeft(Hn left) {this.left = left;}public int getLeftType() {return leftType;}public void setLeftType(int leftType) {this.leftType = leftType;}public int getRightType() {return rightType;}public void setRightType(int rightType) {this.rightType = rightType;}
}//树
class BinaryTree2 {private Hn root;private Hn pre = null; // 指向当前节点的前驱节点public void setRoot(Hn root) {this.root = root;}//重载线索化方法public void threadHd(){threadHn(root);}//线索化二叉树(中序)public void threadHn(Hn node) {if(node == null) {return;}//线索化左子树threadHn(node.getLeft());//当前节点//处理当前节点的前驱节点if(node.getLeft() == null){ //只处理叶子节点node.setLeft(pre); //当前左指针指向前驱节点node.setLeftType(1); //修改当前左指针类型}if(pre != null && pre.getRight() == null) {pre.setRight(node); // 后继节点的前驱节点是当前节点pre.setRightType(1);}//!!!!!!!!!每处理完一个节点,当前节点变为前驱节点pre = node;//右子树threadHn(node.getRight());}
}

1.8.7 遍历线索化二叉树

  • 遍历顺序应当和线索化的顺序保持一致
  • 线索化后不需要递归就能实现
    //线索化中序遍历public void preOrder() {Hn node = root;while (node != null) {//先找到头节点while(node.getLeftType() == 0) {node = node.getLeft();}System.out.println(node);while(node.getRightType() == 1) {node = node.getRight();System.out.println(node);}node = node.getRight();}}

1.8.8 赫夫曼树

  • 最优二叉树
  • WPL : 树的带权路径长度
  • 最优二叉树:WPL最小的二叉树
import java.util.ArrayList;
import java.util.Collections;public class domThree09 {public static void main(String[] args) {int arr[] = {1 , 4 , 6 , 2 , 3} ;preOrder(HuffmanTree(arr));}//创建赫夫曼树的方法public static Node3 HuffmanTree(int[] arr) {//为了操作方便,遍历arr数组//将每个元素构成一个Node//将Node全部放入ArrlistArrayList<Node3> node3s = new ArrayList<>();for (int i : arr) {node3s.add(new Node3(i));}while (node3s.size() > 1) {//排序--从小到大Collections.sort(node3s);//取出权值最小的两个二叉树Node3 left = node3s.get(0);Node3 right = node3s.get(1);//构建新二叉树Node3 parent = new Node3(left.val + right.val);parent.left = left;parent.right = right;//挂在树上后删除List里的left与rightnode3s.remove(left);node3s.remove(right);//将新建树根节点放入Listnode3s.add(parent);}//返回赫夫曼树root节点return node3s.get(0);}//遍历方法public static void preOrder(Node3 root) {if(root != null) {root.preOrder();}else {System.out.println("空树");}}
}//创建节点
class Node3 implements Comparable<Node3> {  //让node实现Comparable接口int val;Node3 left;Node3 right;public Node3(int val) {this.val = val;}//前序遍历public void preOrder() {System.out.println(this);if(this.left != null){this.left.preOrder();}if(this.right != null) {this.right.preOrder();}}@Overridepublic String toString() {return "Node3{" +"val=" + val +'}';}@Overridepublic int compareTo(Node3 o) {//表示从小到大排序return this.val - o.val;}
}

1.8.9 数据压缩

1.8.9.1 创建赫夫曼树
1.8.9.2 生成赫夫曼编码表
1.8.9.3 赫夫曼编码字节数组
1.8.9.4 字节转二进制字符串
1.8.9.5 赫夫曼解码
1.8.9.6 赫夫曼压缩文件
1.8.9.7 赫夫曼解压文件

1.8.10 二叉排序树

2.算法

2.1 排序算法

2.1.1 简介

  • 排序算法的介绍
  • 将一组数据,依照指定的顺序排序

分类:

  • 内部排序:所有数据加载到内部存储器中进行排序(重点)
    • 插入排序
      • 直接插入
      • 希尔排序
    • 选择排序
      • 简单选择排序
      • 堆排序
    • 交换排序
      • 冒泡
      • 快排
    • 归并排序
    • 基数排序(桶排序)
  • 外部排序:数据量过大,需要加载外部存储进行排序

2.1.2 时间复杂度

度量程序优越与否

  • 事后统计:运行一下看效果(涉及因素过多,不推荐)
  • 事前估算:时间复杂度评估

时间频度

  • T(n) :算法中语句的执行次数

  • 忽略常数项

  • 忽略低次项

  • 忽略系数

  • T(n)简写为:O(f(n)) —时间复杂度

  • 对数阶:时间复杂度O(log2 n)比线性n还小,非常优秀的算法,仅次于常熟阶O(1)

while (i < n){i = i * 2;}

平均时间复杂度与最坏时间复杂度

  • 平均:所有可能性等概率出现运行时间
  • 最坏:最坏情况运行的时间,算法时间上限!
  • 稳定性:
    • 冒泡,交换,选择,插入:n值小时较好
    • 基数,希尔
    • 快排,归并,堆:n值大时较好

空间复杂度

  • 算法中所耗费的存储空间
  • 更看重时间(用户希望越快越好)—缓存:空间换时间

2.1.3 冒泡排序

  • 理解为从右往左排
public class domThree01 {public static void main(String[] args) {int arr[] = {3 , 4 , 1 , 2 , 6};effervescent(arr);System.out.println(Arrays.toString(arr));}//冒泡排序方法public static void effervescent(int[] arr){int temp;//用来交换for(int i = 0 ; i < arr.length ; i ++){for(int j = 1 ; j < arr.length - i ; j ++){if(arr[j] < arr[j - 1]){temp = arr[j];arr[j] = arr[j - 1];arr[j - 1] = temp;}}}}
}
  • 算法调优:如果某一趟第二次的遍历没有发生交换,说明此时的数组已经有序
    //冒泡排序方法public static void effervescent(int[] arr){int temp;//用来交换for(int i = 0 ; i < arr.length ; i ++){boolean flag = true;for(int j = 1 ; j < arr.length - i ; j ++){if(arr[j] < arr[j - 1]){temp = arr[j];arr[j] = arr[j - 1];arr[j - 1] = temp;flag = false;}}System.out.println("遍历次数:" + (i + 1));if(flag){break;}}}

2.1.4 选择排序

  • 理解为从左往右排
  • 最多只交换 n-1 次,相较于冒泡排序,交换的次数有质的飞跃,在大量随机数的考验下,比冒泡排序快好多倍
    //选择排序public static void choose(int[] arr){for (int i = 0 ; i < arr.length-1 ; i ++){int temp = arr[i];int index = i; //记录下标for (int j = i + 1 ; j < arr.length ; j ++){if(arr[i] > arr[j]){temp = arr[j];index = j;}}if(index != i){ //优化,最小值没有变化就不用交换arr[index] = arr[i];arr[i] = temp;}}}

2.1.5 插入排序

  • 从左往右排

  • 从第二个位置开始循环向前遍历找到插入位置

    //插入排序public static void insert(int[] arr){for(int i = 1 ; i < arr.length ; i ++){//待插入数int val = arr[i];//待插入数前面的下标int index = i - 1;// 待插入数比前一个数大或到了下标为0的位置,退出循环while (index >= 0 && val < arr[index]){arr[index + 1] = arr[index];index --;}//退出while循环即找到插入位置if(i != index + 1){ // 这里优化可有可无,性能不会发生明显变化arr[index + 1] = val;}}}

2.1.6 希尔排序

  • 插入排序缺点:如果最小的几个数都在数组的最后面,位移次数太多,影响效率

  • 改良版插入排序: 分组思想,逐步变为接近有序的数组

  • 缩小增量排序

//希尔排序public static void shier(int[] arr){//定义交换的第三方变量int temp = 0;//分组,定义步长for(int gap = arr.length / 2 ; gap > 0 ; gap /= 2){//按照分组遍历for (int i = gap ; i < arr.length ; i ++ ){//遍历组中所有元素for (int j = i - gap ; j >= 0 ; j -= gap){if(arr[j] > arr[j + gap]){temp = arr[j];arr[j] = arr[j + gap];arr[j + gap] = temp;}}}}}
  • 以上遍历每组所有数据中采用交换法改变数据位置,算法稍复杂,性能跟冒泡差不多(慢!)
  • 以上并没有用到插入排序思想,而真正的希尔排序是在插入排序的思想基础上升级优化的
  • 用移动法优化—性能高于插入排序!!!百倍!!!
    //改良版希尔排序public static void shier2(int[] arr){//确定增量for(int gap = arr.length / 2 ; gap > 0 ; gap /= 2){//根据分组进行插入排序for(int i = gap ; i < arr.length ; i ++){int j = i;int temp = arr[j];while (j - gap >= 0 && temp < arr[j - gap]){arr[j] = arr[j - gap];j -= gap;}//退出while循环,找到插入位置arr[j] = temp;}}}

2.1.7 快速排序

  • 对冒泡排序进行改进

  • 随便找一个值开始就行,这里以中间值为例

  • 比希尔快一点: 空间换时间典型算法

    //快速排序public static void quick(int[] arr , int left , int right){int l = left;int r = right;int pivot = arr[(left + right) / 2];int temp; // 交换的临时变量while (l < r){//左遍历while (arr[l] < pivot){l ++;}//右变量while (arr[r] > pivot){r --;}//左右遍历完毕if(l >= r){break;}//交换temp = arr[l];arr[l] = arr[r];arr[r] = temp;//交换完以后,若当前位置与中间量相等,会一直卡在这里,手动移动,避免死循环if(arr[l] == pivot){r --;}if(arr[r] == pivot){l ++;}}//从这里开始为递归做准备if(l == r){l += 1;r -= 1;}//向左递归if(left < r){ //只剩一个数据时,不会进递归quick(arr , left , r);}//向右递归if(right > r){quick(arr , l , right);}}

2.1.8 归并排序

  • 先分后和(重点在和!)

  • n个数据需要合并n-1次,即调用最复杂的合并方法只需要n-1次

  • 800 0000 条数据仅需要2s , 非常快!!!

  • 该方法需要开一个临时存储空间,即new一个等长数组

public class domThree01 {public static void main(String[] args) {int arr[] = {1 , 2 , 6 , 4 , -1};//临时等长空间int[] temp =  new int[arr.length];//测试合并方法merge(arr , 0 , 2 , 4 , temp);//测试完整归并方法mergeSore(arr , 0 , arr.length - 1 , temp);System.out.println(Arrays.toString(arr));}
    //归并排序//合并方法public static void merge(int arr[] , int left , int mid , int right , int temp[]){int i = left; //左边初始int j = mid + 1; //右边初始int t = 0;  //第三方数组temp初始//左右两边数据按照规则放到temp数组,直到一边全部放完while (i <= mid && j <= right){if(arr[i] > arr[j]){temp[t] = arr[j];t ++;j ++;}else {temp[t] = arr[i];t ++;i ++;}}//剩余部分按照顺序全部放到temp数组if(i == mid + 1){while (j <= right){temp[t] = arr[j];t ++;j ++;}}else {while (i <= mid){temp[t] = arr[i];t ++;i ++;}}//将temp数组拷贝到原始数组int tempLeft = left;for (int n = 0; n < t; n++) {arr[tempLeft] = temp[n];tempLeft ++ ;}}//分+和public static void mergeSore(int[] arr, int left , int right , int[] temp){if(left < right){int mid = (left + right) / 2 ;//向左分解mergeSore(arr , left , mid , temp);//向右分解mergeSore(arr , mid + 1 , right , temp);//合并merge(arr , left , mid , right , temp);}}

2.1.9 基数排序

  • 桶排序的扩展

  • 稳定性排序

  • 按照位数进行分类排序

  • 连递归都用不到!!!

  • 速度比归并还要快得多

  • 缺点就是占用内存:需要额外开辟十倍数据空间

    //基数排序public static void radix(int[] arr){//找到最大数int max = arr[0];for (int i : arr) {if(max < i){max = i;}}//计算数字位数巧妙方法,变成字符串,用它自带的length方法int maxLength = (max + "").length();//定义桶int[][] temp = new int[10][arr.length];//定义数组记录每个桶中数组元素个数int[] sum = new int[10];for (int cal = 0 , n = 1; cal < maxLength ; cal ++ , n *= 10){//便利原数组,按规则放到桶中for (int i : arr) {//取出元素各位int val = i / n % 10 ;//放入桶中temp[val][sum[val]] = i;sum[val]++;}//便利每一个桶,放到原数组int index = 0 ;for (int k = 0 ; k < sum.length ; k ++) {//桶里有数据才放入原数组if (sum[k] != 0){//循环放入for (int j = 0; j < sum[k] ; j ++){arr[index] = temp[k][j];index ++ ;}//放完置空sum[k] = 0;}}}}

2.1.10 堆排序

  • 树结构的应用

  • O(nlogn)

  • 速度与归并相差不多,但不用开空间,整体性能较好

    //堆排序public static void heap(int[] arr) {int temp;for (int i = arr.length / 2 - 1 ; i >= 0 ; i --) {//将待排序序列构造成一个大顶堆,这时整个序列最大值为根节点adjust(arr , i , arr.length);}for (int j = arr.length - 1 ; j > 0 ; j --) {//根节点与末尾元素交换,末尾元素为最大值temp = arr[j];arr[j] = arr[0];arr[0] = temp;//循环遍历其余n-1个元素adjust(arr , 0 , j);}}//将i为非叶子节点的数组调整为大顶堆,---只处理该节点和该节点以下的数据public static void adjust(int arr[] , int i , int length) { //数组  非叶子节点的索引  对多少个元素进行调整int temp = arr[i];for (int k = 2*i + 1 ; k < length ; k = i *2 +1) {if(k + 1 < length && arr[k] < arr[k + 1]) {k ++; //左右节点取大值}if(arr[k] > temp) {arr[i] = arr[k];i = k; //循环遍历子节点}else {break;}} // for循环结束,该非叶子节点为该模块的最大值arr[i] = temp;}

2.2 查找算法

2.2.1 线性查找

  • 遍历数组,这个简单
    //线性查找,顺序比对public static int seq(int[] arr , int n){for (int i = 0 ; i < arr.length ; i ++){if(arr[i] == n){return i;}}throw new RuntimeException("未找到");}

2.1.2 二分查找

  • 也叫折半查找,要求数据是有序的
  • 这个简单
    //二分查找public static int binary(int arr[] , int n , int left , int right){if(left > right){throw new RuntimeException("未找到");}//中间值int mid = (left + right) / 2 ;if(arr[left] == n){return left ;}else if(arr[mid] == n){return mid;}else if(arr[right] == n){return right;}else {if(arr[mid] < n){//右边return binary(arr , n , mid + 1 , right - 1);}else {//左边return binary(arr , n , left + 1 , mid - 1);}}}
  • 主方法调用时注意异常处理
    int arr[] = {1, 3 , 4 , 5 , 6 , 6 , 8};try {System.out.println(binary(arr , 9 , 0 , arr.length - 1));} catch (Exception e) {System.out.println(e.getMessage());}
  • 当需要找到多个数据时
    //二分查找public static ArrayList binary(int arr[] , int n , int left , int right){if(left > right){throw new RuntimeException("未找到");}//中间值int mid = (left + right) / 2 ;if(arr[left] == n ){ArrayList<Integer> list = new ArrayList<Integer>();while (arr[left] == n && left <= right){list.add(left);left ++;}return list ;}else if(arr[mid] == n){ArrayList<Integer> list = new ArrayList<Integer>();int mid2 = mid;while (arr[mid2] == n && mid2 <= right){list.add(mid2);mid2 ++;}while (arr[mid - 1] == n && mid > left){list.add(mid - 1);mid --;}return list ;}else if(arr[right] == n){ArrayList<Integer> list = new ArrayList<Integer>();while (arr[right] == n && left < right){list.add(right);right--;}return list ;}else {if(arr[mid] < n){//右边return binary(arr , n , mid + 1 , right - 1);}else {//左边return binary(arr , n , left + 1 , mid - 1);}}}

2.1.3 插值查找

  • 在二分查找的基础上,将原来的mid中间值修改为按照比例定义中间值位置
  • 将mid取值改为:

​ int mid = left + (right - left) * (n - arr[left]) / (arr[right] - arr[left]);

//二分查找
public static ArrayList binary(int arr[] , int n , int left , int right){if(left > right){throw new RuntimeException("未找到");}//中间值int mid = left + (right - left) * (n - arr[left]) /  (arr[right] - arr[left]);if(arr[left] == n ){ArrayList<Integer> list = new ArrayList<Integer>();while (arr[left] == n && left <= right){list.add(left);left ++;}return list ;}else if(arr[mid] == n){ArrayList<Integer> list = new ArrayList<Integer>();int mid2 = mid;while (arr[mid2] == n && mid2 <= right){list.add(mid2);mid2 ++;}while (arr[mid - 1] == n && mid > left){list.add(mid - 1);mid --;}return list ;}else if(arr[right] == n){ArrayList<Integer> list = new ArrayList<Integer>();while (arr[right] == n && left < right){list.add(right);right--;}return list ;}else {if(arr[mid] < n){//右边return binary(arr , n , mid + 1 , right - 1);}else {//左边return binary(arr , n , left + 1 , mid - 1);}}
}
  • 插值查找,适合关键字分布均匀的数据,否则还是用二分查找稳定一点

数据结构基础和排序算法相关推荐

  1. [ 数据结构 -- 手撕排序算法第三篇 ] 希尔排序

    手撕排序算法系列之:希尔排序. 从本篇文章开始,我会介绍并分析常见的几种排序,大致包括插入排序,冒泡排序,希尔排序,选择排序,堆排序,快速排序,归并排序等. 大家可以点击此链接阅读其他排序算法:排序算 ...

  2. 算法基础:排序算法之冒泡排序

    算法基础:排序算法之冒泡排序 实现:数列有序排序 思想:已知一个数列,令数列中相邻的两个元素一一做比较,按照小大的顺序(或从大到小的顺序),如果前一个数比后一个数大(或后一个数比前一个数大),则互换( ...

  3. 数据结构-常用的排序算法

    总第123篇 好久不见哈,我终于又更新了,惊不惊喜,意不意外,哈哈哈哈.等之后会专门写一篇文章给大家汇报汇报我最近在忙什么呢,今天这篇还是接着之前的数据结构系列继续,主要讲讲数据结构里面常用的几种排序 ...

  4. Java数据结构第一讲-排序算法

    常见数据结构和算法实现(排序/查找/数组/链表/栈/队列/树/递归/海量数据处理/图/位图/Java版数据结构) 数据结构和算法作为程序员的基本功,一定得稳扎稳打的学习,我们常见的框架底层就是各类数据 ...

  5. python基础===八大排序算法的 Python 实现

    本文用Python实现了插入排序.希尔排序.冒泡排序.快速排序.直接选择排序.堆排序.归并排序.基数排序. 1.插入排序 描述 插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一 ...

  6. 值得收藏的时间复杂度速查表:数据结构操作、排序算法、图操作、堆操作

    时间复杂度速查表 这篇文章覆盖了计算机科学里面常见算法的时间和空间的大 OBig-O 复杂度. 在参加面试前,我们经常需要花费很多时间从互联网上查找各种搜索和排序算法的优劣,了节省大家的时间,我收集了 ...

  7. 数据结构的六大排序算法详解

    文章目录 一.简单排序 1.Comparable接口介绍 2.冒泡排序 3.选择排序 4.插入排序 二.高级排序 1.希尔排序 2.归并排序 3.快速排序 4.排序的稳定性 一.简单排序 在我们的程序 ...

  8. 数据结构进阶 八大排序算法详解

    数据结构就是定义出某种结构:像数组结构.链表结构.树形结构等,实现数据结构就是我们主动去管理增删查改的实现函数 排序的概念 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列 ...

  9. 数据结构和常用排序算法复杂度

    1.顺序表 插入操作时间复杂度 最好O(1),最坏O(n),平均O(n) 移动结点的平均次数n/2 删除操作时间复杂度 最好O(1),最坏O(n),平均O(n) 移动结点的平均次数(n-1)/2 按值 ...

最新文章

  1. java字符串10_十个最常见的Java字符串问题
  2. 光纤会在将来完全取代铜缆吗?
  3. Mysql 死锁过程及案例详解之用户自定义锁
  4. axure怎么做5秒倒计时_五个月宝宝早教,5个月婴儿早教怎么做
  5. node缓冲区_Node.js缓冲区介绍
  6. 前端学习(2789):改进导航栏并跳转
  7. 【面向对象】继承与封装
  8. 电梯调度需求调研报告
  9. Java for循环改数据_如何改变arrs数组?当然是需用for循环啦
  10. Android 渗透测试学习手册 第七章 不太知名的 Android 漏洞
  11. 系统架构与软件架构是一层含义吗
  12. c++ socket线程池_Netty(3)——Reactor线程模型
  13. web测试的基本测试点
  14. ArcGis利用栅格处理工具进行影像裁剪
  15. 【云和恩墨大讲堂】 陈顼 - 一次视图合并引起的性能问题
  16. Python验证哥德巴赫猜想,并返回数组
  17. [USACO Hol10] 政党
  18. 打造企业级应用--邮件服务器postfix+dovecot+extmail
  19. win10开启hdr功能屏幕泛白如何解决?
  20. python中tkinter模块pack_Python tkinter模块和参数

热门文章

  1. windows netshare
  2. 读书报告2000字c语言,读书报告2000字
  3. 世界杯闲话:谁会取代章鱼保罗
  4. 博世BMI088官方代码库解析
  5. 什么蓝牙耳机适合打游戏?打游戏不延迟的蓝牙耳机
  6. poi导出word:包括**普通的段落以及表格**。常用的**api**已经以注释的方式写了进去。
  7. 免费的ERP系统哪个好?这款让管理更高效
  8. redis最全面试题
  9. 如何设置默认使用集成显卡
  10. Python3 定义一个跨越多行的字符串的多种方法