文章借鉴于【尚硅谷】数据结构与算法(Java数据结构与算法),在内容方面会做更改,但是核心依然是他们的学习视频。在这里声明。


1. 线性结构和非线性结构

1.1 线性结构

数据结构包括两大部分,一个叫 线性结构,一个叫 非线性结构

  • 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系。 比如数组, var[0] = 20; 存在一对一关系。

  • 线性结构有两种不同的存储结构,即顺序存储结构链式存储结构。顺序存储的线性表称为顺序表,顺序表中的元素是连续的。该元素指的是内存地址

  • 链式存储结构的线性表称为链表链表中存储的元素不一定是连续的, 元素节点中存放数据元素以及相邻元素的地址信息。

  • 线性结构常见的有:数组,队列,链表,栈

1.2 非线性结构

  • 非线性结构包括:二维数组,多维数组,广义表,树结构,图结构

2. 稀疏数组

定义: 当一个数组中,大部分元素都为0,或者大部分元素都为同一个数字的时候,可以使用稀疏数组来保存该数组。

2.1 稀疏数组的介绍

稀疏数组的处理方式是:

  • 记录一个数组有多少行,多少列,有多少个不同的值。
  • 把具有不同元素的行列记录在一个小规模的数组上(这个小规模的数组就是稀疏数组),从而缩小程序的规模。

左侧是原有的二维数组,右侧为压缩后的稀疏数组。在稀疏数组的第[0]索引位,第一个元素代表了这个原有的二维数组有几行,第二个代表了这个原有的数组有几列,第三个代表了除去非0以外的元素,还存在几个其他不同的元素。从第[1]号索引到第[8]号索引,他们分别记录的是该不同元素出现在二维数组上的位置所在。比如 元素22 出现在 索引为0 - 3 的地方,那么对应的具体坐标就为 1 - 4 也就是第一行第四个元素。那么,原有的 6行 7列的数组,就会变成 现在稀疏数组的 九行 三列,对应的元素分别是 6 * 7 = 42(压缩前) 和 3 * 9 = 27(压缩后),大大减少了使用空间。

2.2 稀疏数组的实操

那在具有上面的了解后,就可以来具体的学习一下怎么样把一个二维数组,转换为稀疏数组的思路。

  1. 遍历原始的二维数组,得到有效元素的个数 sum。
  2. 根据sum就可以创建稀疏数组sparseArray其中稀疏数组的大小为
    int[] [] sparseArray = new int[sum + 1][3]
  3. 将二维数组的有效数据,存入到稀疏数组中。

那么存入后,就需要还原二维数组。下面是稀疏数组转二维数组的思路。

  1. 先读取稀疏数组的第一行,第一行里面有二维数组的行和列。根据第一行的数据,创建原始的二维数组。 int[][] array = new int[sparse[0][1]] [sparse[0][2]]
  2. 然后读取稀疏数组后几行的数据,并且赋给原始的二维数组。即可。

现在就可以来写具体的代码实操。新建一个类,类名自定义,我这里的类名叫做SparseArray。接下来我直接粘贴代码。具体内容看代码,代码都具有注释。

package com.data_structure.sparse_array;import java.util.Arrays;/*** 数据结构* 稀疏数组的来回转换*/
public class SparseArray {/**** 对二维数组的转置输出,观看更客观* @param arrays 对应的需要转置的二维数组*/public static void Transpose_2D_Array(int[][] arrays) {for (int[] array : arrays) {System.out.println(Arrays.toString(array));}}/*** 遍历二维数组,得到非0元素的个数* @param arrays 传入的二维数组* @return 返回非0的个数*/public static Integer Get_Oon_Empty_Element(int[][] arrays) {int sum = 0;for (int[] array : arrays) {for (int i : array) {if (i != 0) {sum ++;}}}return sum;}/*** 稀疏数组转换二维数组* @param sparseArray 传入稀疏数组* @return 转换成功的二维数组*/public static int[][] SparseArrayTo2DArray(int[][] sparseArray) {// 根据稀疏数组,还原对应二维数组的大小,当然,前提是判断稀疏数组是否可用if (sparseArray[0][0] != 0 && sparseArray[0][1] != 0) {// 创建一个新的二维数组int[][] new2DArray = new int[sparseArray[0][0]][sparseArray[0][1]];// 此时只需要循环遍历其他的值即可for (int i = 1; i <= sparseArray[0][2]; i++) {new2DArray[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];}return new2DArray;}return null;}/*** 二维数组转换为稀疏数组*/public static void Convert_2D_Array_To_Sparse_Array() {// 创建一个原始的11 * 11 的 二维数组模拟五子棋 0 表示空值, 1表示黑子,2表示白子int[][] arrays = new int[11][11];// 初始化棋盘 假设下了两个棋子,第二行第三个(黑色), 第三行第四个(白色)arrays[1][2] = 1;arrays[2][3] = 2;System.out.println("原始二维数组: ");// 对二维数组进行转置输出Transpose_2D_Array(arrays);// 得到非0的元素个数Integer integer = Get_Oon_Empty_Element(arrays);// 创建对应的稀疏数组 他的行为 对应有元素的个数 + 1 列为固定3int[][] sparseArray = new int[integer+1][3];//给稀疏数组赋值sparseArray[0][0] = 11;sparseArray[0][1] = 11;sparseArray[0][2] = integer;// 遍历原始数组,然后把对应不为0的元素,存储到稀疏数组中。int count = 0;for (int i = 0; i < 11; i++) {for (int j = 0; j < 11; j++) {// 如果不为0 代表有数据if (arrays[i][j] != 0) {count ++;sparseArray[count][0] = i;sparseArray[count][1] = j;sparseArray[count][2] = arrays[i][j];}}}// 输出稀疏数组的形式System.out.println("稀疏数组: ");Transpose_2D_Array(sparseArray);// 稀疏数组转换为 二维数组。以及知道系数组的数据位置,可以直接通过稀疏数组转换二维数组。int[][] newArrays = SparseArrayTo2DArray(sparseArray);System.out.println("由稀疏数组转换为二维数组: ");assert newArrays != null;Transpose_2D_Array(newArrays);}public static void main(String[] args) {Convert_2D_Array_To_Sparse_Array();}}

上述代码是可以直接拷贝运行的,但是注意包结构导入,需要更改。

3.队列

特性: 队列最大的特点就是 先进先出;

  • 队列是一个有序列表,可以用数组或者链表来实现。
  • 遵循先入先出的原则。即先存入的数据,先取出,后存入的数据,后取出。

3.1 队列的代码实现

package com.data_structure.queue;/*** 队列,且是用数组模拟队列*/
public class ArrayQueueDemo {public static void main(String[] args) {ArrayQueue arrayQueue = new ArrayQueue(5);arrayQueue.addQueue(5);arrayQueue.addQueue(1);arrayQueue.addQueue(7);arrayQueue.addQueue(9);arrayQueue.addQueue(4);arrayQueue.addQueue(4);System.out.println();arrayQueue.show();System.out.println();System.out.println(arrayQueue.getQueue());System.out.println(arrayQueue.getQueue());System.out.println(arrayQueue.getQueue());System.out.println(arrayQueue.getQueue());arrayQueue.show();}}// 新建一个数组模拟队列,编写一个ArrayQueue
class ArrayQueue {// 表示数组的最大容量private final int maxSize;// 指向队列头private int front;// 指向队列尾private int rear;// 队列数组private final int[] array;/*** 构造器,初始化数据* @param arrSize 队列的大小*/public ArrayQueue(int arrSize) {this.maxSize = arrSize;   // 获取到队列的最大容量this.array = new int[maxSize];   // 根据最大容量创建数组队列this.front = -1;   // 指针头,指向队列头部的前一个位置this.rear = -1;  // 指针尾,指向队列尾部的最后一个位置}/*** 判断队列是否为空,只需要检查 指针头和指针尾是否相撞就可以了* @return 空为true  反之false*/public boolean queueIsEmpty() {return this.front == this.rear;}/*** 判断队列是否已满,只需要检查队列尾部指针是否和最大容量 -1(下标) 相撞* @return 满了返回true ,反之反之*/public boolean queueIsFull() {return this.rear == this.maxSize - 1;}/*** 添加队列* @param num 向队列里添加的值*/public void addQueue(int num) {// 判断队列是否已满if (!queueIsFull()) {this.rear ++;array[rear] = num;} else {System.out.println("队列已满,无法添加数值。");}}/*** 获取队列元素* @return 队列元素*/public int getQueue() {// 判断队列是否为空if (!queueIsEmpty()) {front++;return array[front];}throw new RuntimeException("队列为空。");}public void show() {for (int i = this.front + 1; i <= this.rear; i++) {System.out.println("数据: " + this.array[i]);}}}

3.2 环形队列

如果按照上面的方式来模拟队列,就会出现用过一次的位置不能第二次去使用。没法达到复用效果。

我们希望取出的空间还可以重复的去使用

对比上面的基本队列,环形队列实现的基本思路做出了调整

  1. front变量的含义做出调整,front调整为指向队列的第一个元素,也就是说,array[front]时,获取到的是队列的第一个元素。front的初始值为 0;不再是 -1。
  2. rear变量的含义做出调整,rear调整为指向队列最后一个元素 + 1 的位置。因为希望空留出一个空间作为约定。rear的初始值也 = 0。
  3. 当队列满的情况下,原先的条件是, rear == maxSize - 1; 但是现在 front和 rear的条件做了调整,变为 (rear + 1) % maxSize == front; 这就是满的条件。
  4. 当队列为空的条件没有发生变化,如果 rear == front 就是空。
  5. 当按照上面走的时候,队列中有效的数据个数,永远是(rear + maxSize - front) % maxSize

3.3 环形队列代码的实现

package com.data_structure.queue;public class CircleArrayQueue {public static void main(String[] args) {CircleQueue circleQueue = new CircleQueue(5);circleQueue.addQueue(5);circleQueue.addQueue(8);circleQueue.addQueue(1);circleQueue.addQueue(2);circleQueue.addQueue(9);circleQueue.addQueue(4);System.out.print("\n展示此时的队列内容: ");circleQueue.show();System.out.println();System.out.println("取出的内容: ");System.out.println(circleQueue.getQueue());System.out.println(circleQueue.getQueue());System.out.println(circleQueue.getQueue());System.out.println(circleQueue.getQueue());System.out.println("添加三个队列: ");circleQueue.addQueue(9);circleQueue.addQueue(1);circleQueue.addQueue(5);circleQueue.show();}}class CircleQueue {private final int maxSize;private final int[] array;private int front;private int rear;public CircleQueue(int maxSize) {this.maxSize = maxSize;this.array = new int[maxSize];this.front = 0;this.rear = 0;}public boolean isEmpty() {return this.front == this.rear;}public boolean isFull() {// 队列已满情况下。 最大值 % (尾指针 + 1) 如果 == front 就说明元素已经还差一个就满了 ,这一个是约定需要留下来的return ((this.rear + 1) % this.maxSize) == this.front;}public void addQueue(int number) {if (!isFull()) {// 因为它本身就是在元素的后一个位置,所以我们不需要对他先进行指针后移操作,在添加完成数据后,进行指针后移,保证空留一个,// 这里必须考虑取模,如果rear到顶层,切前面有空间,就需要让rear到前面去this.array[this.rear] = number;// 假设最大容量为5。此时rear为4.我们必须约定空留一个元素空间,this.rear = (this.rear + 1) % maxSize;} else {System.out.println("队列已满");}}public int getQueue() {// 不为空的情况下才能取数据if (!isEmpty()) {// front是指向队列的第一个元素,我们需要先把front的值存储到一个临时变量中,把front后移,考虑取模,然后将临时的变量返回int value = this.array[this.front];this.front = (front + 1) % maxSize;return value;}throw new RuntimeException("已经空掉了");}public void show() {if (!isEmpty()) {// 从front开始遍历, 遍历多少个元素。for (int i = this.front; i < front + (rear + maxSize - front) % maxSize; i++) {System.out.printf("数据下标arr[%d] = %d", i % maxSize, this.array[i % maxSize]);}}}}

4. 链表 (Linked List)


head 为头指针,就是第一个数据,然后data域为数据,next域,就是指向下一个节点的地方。

  • 链表是以节点的方式来存储数据的。
  • 每个节点包含data域,next域
  • 链表的各个节点并不一定是连续的。
  • 链表分带头节点的链表和没有头节点的链表,根据实际需求来确定

4.1 单链表的代码实现(不带排序)

package com.data_structure.linkedList;public class SingleLinkedListDemo {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, "鲁智深", "及时雨");SingleLinkedList singleLinkedList = new SingleLinkedList();singleLinkedList.add(heroNode1);singleLinkedList.add(heroNode2);singleLinkedList.add(heroNode3);singleLinkedList.list();}}class SingleLinkedList {// 初始化头节点,头节点不动,不存放具体数据private final HeroNode herd = new HeroNode(0, "", "");// 添加节点public void add(HeroNode heroNode) {// 添加的原理就是,当有新节点时,找到原有链表的最后一个节点,让最后一个节点指向该新节点。// 但是头节点不动,就可以使用一个引用来代替头节点HeroNode tempHead = this.herd;// 遍历链表,找到最后一个元素while (tempHead.next != null) {// 如果没有找到最后一个元素,就将temp后移tempHead = tempHead.next;}// 当退出while循环时,tempHead就指向了链表的最后。tempHead.next = heroNode;}// 显示链表public void list() {// 先判断链表是否为空。if (herd.next == null) {System.out.println("链表为空: []");return;}// 因为头节点不能动,因此我们需要一个辅助变量来遍历// 因为他不为空,所以说明他至少有一个数据HeroNode temp = this.herd.next;while (true) {// 判断是否到链表的最后if (temp.next == null) {// 最后一个元素也是有数据的System.out.println(temp);break;}// 输出System.out.println(temp);// 将temp后移temp = temp.next;}}}/*** 创建英雄节点* 每一个HeroNode就是一个节点*/
class HeroNode {public int no;  // 英雄编号public String name;  // 英雄名称public String nickname;  // 英雄绰号public HeroNode next;  // 指向下一个节点public HeroNode(int heroNo, String hName, String nickName) {this. no = heroNo;this.name = hName;this.nickname = nickName;}/*** 为了显示方便 重写toString*/@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickname='" + nickname + '\'' +", next=" + next +'}';}
}

4.1.1 – 4.1的代码拆解

首先我们需要创建英雄节点,每一个英雄除去他们基本的属性以外,还需要有一个节点属性,来对应下一个元素所处于的位置,如果next节点为null,则说明他这个英雄是最后一个节点。

所以我们只需要先实现这个英雄节点功能即可。

// 英雄节点类
class HeroNode {// 直接使用public 方便访问public int noId;  // 英雄的编号public String name; // 英雄名称public String nickname; // 英雄绰号// 存放下一个英雄的位置public HeroNode next;    // next域// 使用构造器来构造Hero英雄public HeroNode(int noId, String name, String nickname) {this.noId = noId;this.name = name;this.nickname = nickname;}// 重写toString方法,方便查询@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickname='" + nickname + '\'' +", next=" + next +'}';}}

到这一步为止。我们的英雄节点就已经创造完成,接下来,就可以去实现我们的链表逻辑。

class SingleLinkedList {// 因为头部节点不动, 所以我们先把头部节点创建好,以后使用一个节点引用到头部节点即可。private final HeroNode herd = new HeroNod(0, "", "");// 用于添加节点元素, 需要传入一个节点public void add(HeroNode heroNode) {// 创建一个引用指向头部节点, 因为头节点的信息是不变的HeroNode temp = this.herd;// 遍历循环,判断是否指向的是最后一个节点// 判断最后一个节点的逻辑是 节点的 next域 == nullwhile(temp.next != null) {// 如果不是最后一个元素,就让节点后移temp = temp.next;}// 此时跳出循环的时候,temp指向的就是最后一个节点元素temp.next = heroNode;   }// 查询链表内容public void list() {// 查询前先判断链表是否为空// 只需要判断头节点的next是否为null就可以if (this.herd.next == null) {System.out.println("链表为空");return;}// 头节点是不允许动的。HeroNode temp = this.herd;while(true) {if(temp.next == null){// 虽然为null,但是他也是最后一个元素System.out.println(temp);// 跳出循环break;}System.out.println(temp);// 输出当前节点元素后,还需要把这个节点指向下一个内容。temp = temp.next;}}}

至此完成。测试内容自己写

4.2 单链表的代码实现(附带排序,按照顺序插入节点)

思路实现:

  • 首先找到新添加的节点位置。是通过辅助指针来找的, 通过遍历
  • 新的节点的.next域 = temp的.next域
  • 然后将temp.next域指向新的节点

4.2.1 代码的实现

题目,在添加英雄时,根据排名将英雄插入到指定位置,如果有这个排名,则添加失败,并给出提示。

代码在原有基础上进行添加,也就是在上述代码中,在SingleLinkedList.class类中,新增加一个方法,叫addByOrder(HeroNode heroNode); 里面要传递的参数是新增的英雄节点。


package com.data_structure.linkedList;public class SingleLinkedListDemo {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, "鲁智深", "及时雨");SingleLinkedList singleLinkedList = new SingleLinkedList();singleLinkedList.addByOrder(heroNode3);singleLinkedList.addByOrder(heroNode1);singleLinkedList.addByOrder(heroNode2);singleLinkedList.addByOrder(heroNode4);singleLinkedList.list();}}class SingleLinkedList {// 初始化头节点,头节点不动,不存放具体数据private final HeroNode head = new HeroNode(0, "", "");// 添加节点public void add(HeroNode heroNode) {// 添加的原理就是,当有新节点时,找到原有链表的最后一个节点,让最后一个节点指向该新节点。// 但是头节点不动,就可以使用一个引用来代替头节点HeroNode tempHead = this.head;// 遍历链表,找到最后一个元素while (tempHead.next != null) {// 如果没有找到最后一个元素,就将temp后移tempHead = tempHead.next;}// 当退出while循环时,tempHead就指向了链表的最后。tempHead.next = heroNode;}// 根据编号按升序排序来排序链表节点。 如: 1, 2, 3, 4, 5, 6, 7public void addByOrder(HeroNode heroNode) {// 因为头节点是固定的,不可以改动的,所以我们需要创建一个引用对象来引用头节点HeroNode temp = this.head;// 定义一个boolean的变量,用来检查元素是否重复。默认为falseboolean flag = false;// 开始遍历节点while(true) {// 判断链表是否为空,如果为空的话,可以直接的去做一个添加操作if(temp.next == null) {break;}// 如果不为空的情况下,判断节点应该添加在那个位置// 逻辑就是,如果temp的下一个节点的编号,大于当前要添加的编号,说明找到位置了// 我们就是应该添加到 比他大的前面, 比他小的后面位置。else if(temp.next.no > heroNode.no) {break;} else if (temp.next.no == heroNode.no) {// 说明当前被添加的节点的编号已经存在于链表中设置为falseflag = true;break;} // 如果都不成立的话,仍然需要让节点元素后移。temp = temp.next;}// 在跳出循环后,首先判断 flag是否为trueif (flag) {// 如果为true的情况下,则说明已经有重复的编号。System.out.println("该节点编号重复: " + heroNode.to);} else {// 则说明编号不重复,具体的添加逻辑是// 让当前要添加的英雄的节点位置,指向比他编号大的节点位置,也就是原先 temp 所// 指向的节点heroNode.next = temp.next;// 然后,让原先的temp的下一个节点指向当前的新添加的节点。temp.next = heroNode;}}// 显示链表public void list() {// 先判断链表是否为空。if (head.next == null) {System.out.println("链表为空: []");return;}// 因为头节点不能动,因此我们需要一个辅助变量来遍历// 因为他不为空,所以说明他至少有一个数据HeroNode temp = this.head.next;while (true) {// 判断是否到链表的最后if (temp.next == null) {// 最后一个元素也是有数据的System.out.println(temp);break;}// 输出System.out.println(temp);// 将temp后移temp = temp.next;}}}/*** 创建英雄节点* 每一个HeroNode就是一个节点*/
class HeroNode {public int no;  // 英雄编号public String name;  // 英雄名称public String nickname;  // 英雄绰号public HeroNode next;  // 指向下一个节点public HeroNode(int heroNo, String hName, String nickName) {this. no = heroNo;this.name = hName;this.nickname = nickName;}/*** 为了显示方便 重写toString*/@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickname='" + nickname + '\'' +", next=" + next +'}';}
}

他的核心内容依然是那个方法(addByOrder),其他的只是借用一下,不需要重复的去书写。
具体的解释,代码里的注释已经全部帮我做了。好吧虽然也是我自己写的。但是一边看注释一边看代码,要胜过看完代码之后看解析。(个人感觉)。

4.3 单链表节点的修改

链表内节点的修改,还是依赖在上述代码内,定义一个方法 update(HeroNode newHeroNode); 其中参数就是要修改的内容,节点的id是不变的,也就是说,是通过id来修改节点。

4.3.1 代码的实现

 // 用于修改节点内容public void update(HeroNode newHeroNode) {// 首先判断头节点内是否有下一个节点if (this.herd.next == null) {// 链表内没有数据,没有办法更改,直接返回即可。System.out.println("链表内没有数据,没有办法更改内容。");return;}// 在链表不为空的情况下,定义一个辅助节点来操作,然后定义一个boolean变量来代表是否找到// true为找到了no 相同的节点,false为没有找到no相同的节点HeroNode temp = this.herd.next;boolean flag = false;// 开始遍历每一个节点while(true) {// 判断temp域是否为空,如果为空的话则说明遍历到了最后一个元素.// 并且已经遍历完毕,因为指针永远指向下一个next temp = herd.nextif (temp == null) {// 直接退出即可break;}// 还有一种情况就是找到了no编号相对等的元素,仍然直接退出即可// 因为此时 temp 指向的就是 那个对象if (temp.no == newHeroNode.no) {// 并且flag设为trueflag = true;break;}// 最后如果两种条件都不成立,别忘记进行下一个节点的遍历temp = temp.next;}// 跳出循环以后,无非就是两种可能性,一种flag为 true, 一种为false// 在flag为true的情况下,说明找到了对应的节点元素。反之没有if (flag) {// 因为编号是不允许改变的,我们只需要改变这个节点的其他内容即可。指向仍然不变temp.name = newHeroNode.name;temp.nickname = newHeroNode.nickname;} else {System.out.println("没有找到对应匹配的节点元素, 无法更改");}}

4.4 单向链表的删除

这一块是我自己的思路,因为前面添加的时候规定过,首先,头节点不做添加的内容,也就是说 0 号no 也是一样可以进行添加的,第二,编号 no 是唯一的!, 那么现在就可以跟操作数据库一样,思路逐渐清晰,通过 no 编号来删除,就是因为他是唯一的。

4.4.1 代码的实现

依然是在以上的代码上做添加方法内容。 方法名称叫做 HeroNode deleteByNo(Integer ino); 参数就是通过ino来删除节点内容。 返回值是HeroNode,来返回一个被删除的元素内容。

// 通过该方法来进行节点的删除。
public HeroNode deleteByNo(Integer ino) {// 首先必要的就是一个辅助指针。该指针指向头元素,并且不做一下一个指向// 就仅仅是指向头元素本身HeroNode temp = this.herd;// 创建一个boolean类型的变量,后续判断是否找到对应匹配ino的节点,默认为fales;boolean flag = fales;// 创建一个对象,用于返回被删除的内容HeroNode deleteNode = null;// 遍历循环节点。因为while循环总是在为true的时候去执行,所以可以直接判断// temp.next != nullwhile (temp.next != null) {// 此时只需要在循环内判断 ino是否和 temp.next.no 匹配,如果匹配,直接跳出循环if (temp.next.no == ino) {// 设置flag为trueflag = true;break;}// 如果没有,继续向下一个节点走去temp = temp.next;}// 在跳出循环之后,只会存在两种结果,flag = true/falseif (flag) {// 我们需要在修改节点内容以前,把被删除的节点内容赋值deleteNode = temp.next;// 说明找到了该节点内容,在这里可以做分析,因为此时的temp指针,指向的是被删除的节点的// 前一个节点内容。也就是说,temp.next 指向的是被删除的节点元素,而// temp.next.next 指向的是被删除节点指向的下一个节点。我们需要做的就是把当前// temp.next 的节点,指向被删除的下一个节点temp.next = temp.next.next;} else {System.out.println("没有找到对应编号的节点,无法删除");}return deleteNode;
}

4.5 链表面试题(新浪,腾讯,百度)

已有代码结构为

package com.data_structure.linkedList;public class SingleLinkedListDemo {public static void main(String[] args) {HeroNode heroNode1 = new HeroNode(0, "宋江", "及时雨");HeroNode heroNode2 = new HeroNode(2, "李逵", "黑旋风");HeroNode heroNode3 = new HeroNode(3, "林冲", "及时雨");HeroNode heroNode4 = new HeroNode(4, "鲁智深", "及时雨");SingleLinkedList singleLinkedList = new SingleLinkedList();singleLinkedList.addByOrder(heroNode3);singleLinkedList.addByOrder(heroNode1);singleLinkedList.addByOrder(heroNode2);singleLinkedList.addByOrder(heroNode4);System.out.println("-------------------------------");System.out.println(singleLinkedList.deleteByNo(3));singleLinkedList.list();}}class SingleLinkedList {// 初始化头节点,头节点不动,不存放具体数据private final HeroNode head = new HeroNode(0, "", "");public HeroNode getHead() {return this.head;}// 添加节点public void add(HeroNode heroNode) {// 添加的原理就是,当有新节点时,找到原有链表的最后一个节点,让最后一个节点指向该新节点。// 但是头节点不动,就可以使用一个引用来代替头节点HeroNode tempHead = this.head;// 遍历链表,找到最后一个元素while (tempHead.next != null) {// 如果没有找到最后一个元素,就将temp后移tempHead = tempHead.next;}// 当退出while循环时,tempHead就指向了链表的最后。tempHead.next = heroNode;}public void addByOrder(HeroNode heroNode) {// 因为头节点不能动,因此我们仍然需要一个辅助指针来帮助我们添加位置。// 因为是单链表,因此我们找的temp是处于新增节点的前一个节点。否则添加不了。HeroNode temp = this.head;boolean flag = false; // 表示添加的编号是否存在,默认为不存在。while (true) {// 如果temp.next == null 则说明 此时处于链表的末尾。说明链表为空if (temp.next == null) {break; // 不管找不找得到 都需要break}// 如果是这种情况,则说明,位置找到了,我们本来需要添加的就是比他大的前一位。temp的后一位if (temp.next.no > heroNode.no) {break;}  else if (temp.next.no == heroNode.no) {// 说明希望添加的heroNode的编号已经存在了flag = true;  // 说明编号存在break;}temp = temp.next;}// 退出循环后,我们首先需要判断flag的值if (flag) {// 不能添加,说明编号存在System.out.println("准备插入的这个英雄的编号: " + heroNode.no + " 已经存在。");} else  {// 插入到链表中,temp的后面heroNode.next = temp.next;temp.next = heroNode;}}// 修改节点的信息, 根据编号来修改,即编号无法做出更改。public void update(HeroNode newHeroNode) {// 判断链表是否为空if (this.head.next == null) {System.out.println("链表为空");return;}// 如果不为空,就找到需要修改的节点// 定义一个辅助节点HeroNode temp = this.head.next;// 表示是否找到该节点boolean flag = false;while (true) {if (temp == null) {// 就说明,已经遍历完链表break;}if (temp.no == newHeroNode.no) {// 则说明找到了,直接设置好flag 然后返回flag = true;break;}temp = temp.next;}// 一共就两种可能,第一种是找放到了,退出了循环,此时flag是true,第二种是找不到,flag是false。if (flag) {// 这种情况说明找到了temp.name = newHeroNode.name;temp.nickname = newHeroNode.nickname;} else {// 说明没找到节点System.out.println("没有找到该节点: " + newHeroNode.no);}}// 删除节点, 通过编号 no 来删除, 因为no 是唯一的。public HeroNode deleteByNo(Integer ino) {// 创建一个辅助指针,来辅助循环HeroNode temp = this.head;// 创建一个boolean 来记录是否找到boolean flag = false;// 遍历循环temp.next 来寻找与之对应的节点while (temp.next != null) {//  这种情况下,则说明,下一个节点就是要被删除掉的节点。直接返回if (temp.next.no == ino) {flag = true;break;}// 对指针进行后移temp = temp.next;}// 创建一个节点元素,表示被删除的节点HeroNode deleteNode = null;// 判断flag是否为true,如果为true,说明找到了,此时temp的位置,处于被删除元素的上一个,那么被删除元素的下一个就是 temp.next.next 只// 需要让 temp.next = temp.next.nextif (flag) {deleteNode = temp.next;temp.next = temp.next.next;} else {System.out.println("找不到对应的ino编号。无法删除。");}return deleteNode;}// 显示链表public void list() {// 先判断链表是否为空。if (head.next == null) {System.out.println("链表为空: []");return;}// 因为头节点不能动,因此我们需要一个辅助变量来遍历// 因为他不为空,所以说明他至少有一个数据HeroNode temp = this.head.next;while (true) {// 判断是否到链表的最后if (temp.next == null) {// 最后一个元素也是有数据的System.out.println(temp);break;}// 输出System.out.println(temp);// 将temp后移temp = temp.next;}}
}/*** 创建英雄节点* 每一个HeroNode就是一个节点*/
class HeroNode {public int no;  // 英雄编号public String name;  // 英雄名称public String nickname;  // 英雄绰号public HeroNode next;  // 指向下一个节点public HeroNode(int heroNo, String hName, String nickName) {this. no = heroNo;this.name = hName;this.nickname = nickName;}/*** 为了显示方便 重写toString*/@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickname='" + nickname + '\'' +", next=" + next +'}';}
}

4.5.1 求单链表中节点的个数

这个很简单,直接上代码

 /*** 获取单链表节点的个数(如果是带头节点的链表,需求不统计。* @param heroNode 链表的头节点* @return 返回有效的节点个数。*/
public int getLength(HerdNode herd) {// 判断头节点内是否有下一个节点数据if (herd.next == null) {System.out.println("没有有效的数据个数。");return 0;}// 创建记录节点int count = 0;// 开始遍历,设置代替变量。有效数据不包含头节点HerdNode temp = herd.next;while(temp.next != null) {count ++;temp = temp.next;}return count;
}

4.5.2 查找单链表中倒数第k个节点(新浪面试题)

思路:

  1. 编写一个方法,,接受head节点,同时接受一个index
  2. index表示的是倒数第index个节点
  3. 就可以得到如下算法
  4. 有效的节点数 - index = 正数的第几个节点
  5. 然后我们从第一个开始遍历,到正数的第几个节点即可。
/***  查找单链表中倒数第k个节点(新浪面试题)*  1. 编写一个方法,,接受head节点,同时接受一个index*  2. index表示的是倒数第index个节点*  3. 就可以得到如下算法*  4. 有效的节点数 - index = 正数的第几个节点*  5. 然后我们从第一个开始遍历,到正数的第几个节点即可。* @param head 需要遍历的节点* @param index 倒数第几个* @return 如果找到返回节点,找不到返回null*/
public static HeroNode findLastNode(HeroNode head, int index){// 在已经得到index的情况下,先不要急着去取值,先判断节点是否为空if (head.next == null || index < 0) {return null;}// 开始获取总有效的元素 - index 得到需要遍历到第几个元素int length = getLength(head) - index;// 创建一个辅助节点。HeroNode temp = head.next;for (int i = 0; i < length; i++) {temp = temp.next;}return temp;
}

4.5.3 单链表的反转(腾讯面试题)

实现思路如下

  1. 先定义一个节点,resverseHead = new HeroNode();
  2. 从头到尾遍历,每遍历一个节点,就将其取出,并且放在新的链表的最前端。
  3. 原来链表的 head.next = resverseHead.next;

其实说白了就是头插法,下面上代码,注释一一标出。

/*** 链表的逆转* @param head 传入的头链表*/
public static void singleLinkedListT(HeroNode head) {// 首先判断链表是否为空,或者是否只存在一个节点if(head.next == null || head.next.next == null) {// 直接返回就可以了return;}// 现在定义一个辅助指针,让他指向头部的下一个节点。HeroNode temp = head.next;// 定义一个next链表,用来暂时存储数据,防止链表丢失HeroNode next = null;// 定义一个新的链表,用来逆序节点, 也就是这里采用头插法, 带有一个默认的头指针HeroNode reverseHead = new HeroNode(0, "", "");// 开始遍历,只要辅助节点本身不等于null,就可以确定没有到链表尾部。// 因为temp开始就是指向了 head.nextwhile(temp != null) {// 首先,让next链表,指向temp的下一个节点, 需要把数据暂时保留next = temp.next;// 然后让temp的下一个节点,指向新链表的头节点的下一个,后续可以拼接到// 新链表的头节点后面去(头插法)temp.next = reverseHead.next;// 然后让新链表的头节点位置的下一个节点等于这个temp本身reverseHead.next = temp;// 让temp进行下一次遍历, 因为实现存储了next中。temp = next;}// 然后让头节点引用指向新的链表head.next = reverseHead.next;
}

4.5.4 从尾到头打印单链表(百度面试题,要求方式1,反向遍历。方式2,Stack栈)

栈方式,可以利用栈的先入后出原则,完成逆序打印而不改变链表原有的结构。这里我们要借助一个类,Stack。也就是栈的意思。先上代码演示如何使用

package stack;import java.util.Stack;public class StackTest {public static void main(String[] args) {Stack<String > stack = new Stack<>();// 入栈stack.add("1");stack.add("2");stack.add("3");stack.add("4");// 出栈while (stack.size() > 0) {// pop() 就是将栈顶的数据取出System.out.println(stack.pop());}}}

实际逻辑的实现

 /*** 使用栈来逆序打印链表*/public static void reversePrint(HeroNode head) {// 判断头节点内是否有数据if (head.next == null) {// 直接退出return;}// 创建Stack栈Stack<HeroNode> stackHero = new Stack<>();// 创建辅助指针HeroNode temp = head.next;// 遍历到最后一个while(temp != null) {stackHero.push(temp);// 添加完成后后移temp = temp.next;}// 遍历取出栈内元素while(staciHero.size() != 0) {System.out.println(stackHero.pop());}}

4.5.5 合并两个有序的单链表,合并之后的链表依然有序(课后练习)

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:

0 <= 链表长度 <= 1000

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode(int x) { val = x; }* }*/
class Solution {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {// 建立虚拟头节点ListNode dummy = new ListNode(0);// 把cur指针先放到dummy上ListNode cur = dummy;// 基于比较遍历两个链表while(l1 != null && l2 != null) {// 对l1和l2的值做一个比较,看看哪一个的值最小。// 如果l1比l2小。走if,如果l2比l1小。走elseif(l1.val < l2.val) {// 让cur的下一个节点指向l1,并且让cur移动一下cur.next = l1;  // 指向更小的cur = cur.next;  // cur起到串联作用l1 = l1.next;   // l1 是比较的基准} else {cur.next = l2;cur = cur.next;l2 = l2.next;}}if(l1 != null) cur.next = l1;else cur.next = l2;return dummy.next;}
}

4.6 双向链表

目标为 使用带head头的双向链表实现水浒传排行。
缺优点分析。

  1. 单向链表,查找的方向只能是一个方向,而双向链表可以从前向后找也可以从后向前找,因为他多了一个尾指针 pre。
  2. 单链表不可以自我删除,需要依靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除节点时,总是需要找到temp,temp是待删除节点的前一个节点。

在这里我们需要用到的最基础的代码为

package com.data_structure.linkedList;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** 双向链表*/
public class DoubleLinkedListDemo {public static void main(String[] args) {}
}@Data
@NoArgsConstructor
@AllArgsConstructor
class ListNode {public int no;public String name;public String nickname;public HeroNode next;    // 指向下一个节点  默认为nullpublic HeroNode pre;     // 指向前一个节点  默认为null@Overridepublic String toString() {return "ListNode{" +"no=" + no +", name='" + name + '\'' +", nickname='" + nickname + '\'' +'}';}
}

完结代码,所有的解释都存在于注释中。认真阅读注释

package com.data_structure.linkedList;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** 双向链表*/
public class DoubleLinkedListDemo {public static void main(String[] args) {ListNode listNode1 = new ListNode(1, "宋江", "及时雨");ListNode listNode2 = new ListNode(2, "李逵", "黑旋风");ListNode listNode3 = new ListNode(3, "林冲", "豹子头");ListNode listNode4 = new ListNode(4, "鲁智深", "垂杨柳");DoubleLinkedList doubleLinkedList = new DoubleLinkedList();System.out.println("------------打印空数据-------------");doubleLinkedList.list();System.out.println("---------------------------------");System.out.println();doubleLinkedList.add(listNode1);System.out.println("------------添加一个节点-------------");doubleLinkedList.list();System.out.println("---------------------------------");doubleLinkedList.add(listNode2);doubleLinkedList.add(listNode3);doubleLinkedList.add(listNode4);System.out.println();System.out.println("------------添加四个节点-------------");doubleLinkedList.list();System.out.println("---------------------------------");ListNode updateList = new ListNode(4, "盖伦", "德玛西亚");doubleLinkedList.update(updateList);System.out.println("------------修改编号四-------------");doubleLinkedList.list();System.out.println("---------------------------------");System.out.println();System.out.println("------------删除一个不存在的节点-------------");doubleLinkedList.delete(9);System.out.println("---------------------------------");System.out.println();System.out.println("------------删除一个存在的节点-------------");doubleLinkedList.delete(4);doubleLinkedList.list();System.out.println("---------------------------------");}
}// 链表
class DoubleLinkedList {// 初始化头private final ListNode head = new ListNode();/*** 返回头节点* @return 头节点*/public ListNode getHead() {return head;}/*** 遍历双向链表,和单向链表无异,直接可以用上面的,但是还是建议手写一遍,加深记忆*/public void list() {// 判断节点内是否有元素。if (this.head.next == null) {System.out.println("节点内没有数据");return;}// 判断节点是否只存在于一个数据if (this.head.next.next == null) {System.out.println(this.head.next);return;}// 创建辅助节点开始辅助遍历, 直接从具体的元素数据开始遍历ListNode temp = this.head.next;while (temp != null) {System.out.println(temp);temp = temp.next;}}/*** 添加链表,因为是一个双向链表,所以需要在原有的单向链表的基础上,进行整改。* 在next指向下一个对象后,需要让该对象的pre指向该对象的前一个temp;*/public void add(ListNode listNode) {// 判断链表是否为空。if (this.head.next == null) {// 可以直接做添加this.head.next = listNode;listNode.pre = this.head;return;}// 创建辅助节点,遍历到最后一个ListNode temp = this.head;while (temp.next != null) {temp = temp.next;}// 遍历出来后temp一定是指向最后一个节点元素的temp.next = listNode;listNode.pre = temp;}/*** 修改一个节点的内容。和原先地修改是一样的*/public void update(ListNode listNode) {// 先判断节点是否为空,不包含头节点if (this.head.next == null) {System.out.println("链表内没有数,没有办法修改对应的节点。");return;}// 创建辅助节点ListNode temp = this.head.next;while (temp != null) {// 逐步判断是否有编号配对的,如果有多个编号,则总是修改第一个遇到的if (temp.no == listNode.no) {temp.name = listNode.name;temp.nickname = listNode.nickname;return;}temp = temp.next;}System.out.println("没有找到对应编号的节点");}/*** 删除一个节点来使用, 因为他是双向链表,我们可以直接找到待删除节点的本身。然后进行自我删除。* 不管有几个重复的节点no,我们只删除第一个出现的no*/public void delete(int no) {// 判断节点内是否有元素。if (this.head.next == null) {// 说明没有元素。System.out.println("节点内没有数据无法删除.");return;}// 创建复制节点直接遍历ListNode temp = this.head.next;while (temp != null) {// 寻找节点是否匹配if (temp.no == no) {if (temp.next == null) {// 说明在最后一个 ,直接删除即可。temp.pre.next = null;return;}// 说明找到该被删除的元素// temp本身的上一个节点的下一个节点要指向temp本身的下一个节点。temp.pre.next = temp.next;// temp下一个节点的上一个节点需要指向temp的上一个节点temp.next.pre = temp.pre;return;}temp = temp.next;}System.out.println("找不到编号为: " + no + " 的节点。");}
}@Data
@NoArgsConstructor
@AllArgsConstructor
class ListNode {public int no;public String name;public String nickname;public ListNode next;    // 指向下一个节点  默认为nullpublic ListNode pre;     // 指向前一个节点  默认为nullpublic ListNode(int no, String name, String nickname) {this.no = no;this.name = name;this.nickname = nickname;}@Overridepublic String toString() {return "ListNode{" +"no=" + no +", name='" + name + '\'' +", nickname='" + nickname + '\'' +'}';}
}

4.7 单向环形链表

4.7.1 单向环形链表的介绍

构建一个单向环形链表的基本思路

  • 先创建第一个节点,让first指向该节点,形成环形。
  • 后面我们每创建一个新的节点,就把该节点添加到环形链表中即可。

遍历环形链表

  • 先让辅助指针 cur,指向first节点。
  • 然后通过一个while循环遍历该环形链表即可。
  • 在cur.next == first时,遍历一整个结束。

代码的实现

package com.data_structure.linkedList;import lombok.Data;
import lombok.ToString;/*** 约瑟夫问题*/
public class Josepfu {public static void main(String[] args) {CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();circleSingleLinkedList.addBoy(10);circleSingleLinkedList.showBoy();}}// 创建一个环形的单向链表
class CircleSingleLinkedList {// 创建第一个first节点。当前没有编号private Boy first = null;// 添加小孩节点。构成一个环形链表。传入你要添加几个小孩public void addBoy(int nums) {// 判断这个数是否具有效果,比如最少需要添加一个小孩if (nums < 1) {System.out.println("输入的数字不对, 没办法创建小于 1 的孩子数量!");return;}// 创建辅助指针Boy cur = null;// 通过for循环来添加节点for (int i = 1; i <= nums; i++) {Boy boy = new Boy(i);// 在添加第一个的时候,直接的让第一个的next指向它本身if (i == 1) {// 总是需要添加第一个小孩的,直接让first等于第一个创建的小孩。this.first = boy;// 然后把添加的第一个小孩的下一个指向它自己,形成一个人的环状this.first.setNext(first);// 然后让cur指向第一个小孩cur = this.first;} else {// 如果是第二个小孩开始// 让辅助指针先指向新的boy,也就是做后移操作cur.setNext(boy);// 然后把当前boy的下一个节点指向first,连接到头部boy.setNext(this.first);// 然后cur后移到boy本身的节点上cur = boy;}}}// 遍历当前的环形链表public void showBoy() {// 判断当前链表内是否有内容。if (this.first == null) {System.out.println("链表为空。无法打印内容。");return;}// 创建一个辅助节点。开始辅助遍历Boy temp = this.first;while (true) {System.out.println("小孩的编号为: " +temp.getNo());if (temp.getNext() == this.first) {return;}temp = temp.getNext();}}}// 创建一个Boy类。表示一个节点。
@Data
class Boy {private int no; // 编号private Boy next; // 指向下一个节点。默认为空public Boy(int no) {this.no = no;}@Overridepublic String toString() {return "Boy{" +"no=" + no +'}';}
}

4.7.2 实现约瑟夫问题

思路

  1. 需要创建一个辅助指针helper,指向first节点的上一个节点。也就是整个链表的最后一个节点。
  2. 当开始报数的时候,让first指针开始移动,helper指针需要一直跟在first后面(也就是同时移动),移动m-1次。因为当前节点自己也需要报数一次。
  3. 这时就可以将first指向的节点,开始出圈。也就是,先让first向前移动一次。helper.next = first; 那么原来要被出圈的first就没有了任何引用,就会被垃圾回收机制给回收。

所有的注释注意看


public void main(String[] args) {CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();circleSingleLinkedList.addBoy(10);circleSingleLinkedList.showBoy();System.out.println(circleSingleLinkedList.getCountBoy());System.out.println("约瑟夫取出问题: " + circleSingleLinkedList.countBoy(1, 3, 10));
}/*** 经典约瑟夫问题,根据用户的输入,计算出出圈的一个顺序。* @param startNo 表示从第几个小孩开始数数* @param countNumber 表示数几下* @param nums 表示最初有多少小孩在圈内*/
public List<Integer > countBoy(int startNo, int countNumber, int nums) {// 首先判断first是否为空,然后判断开始人是否小于1 然后判断是否超出最大数if (this.first == null || startNo < 1 || startNo > nums || nums != getCountBoy()) {System.out.println("参数有误,请重新输入。");return null;}// 首先创建一个辅助指针,然后让其遍历至最后一个。达到最后一个,也就是指向链表的尾部(虽然环形链表不存在尾部,但是也是意义上的尾部)Boy helper = this.first;while (helper.getNext() != this.first) {helper = helper.getNext();}// 此时出来以后,helper所处的位置已经到了first的前一个位置。现在开始确定从哪一个小孩开始数数,for循环解决for (int i = 1; i < startNo; i++) {// 如果此时从1号开始数,则根本进不来该循环。无需担心会额外跳出的问题。// 现在解决的就是指向问题。this.first = this.first.getNext();helper = helper.getNext();}// 跳出循环后,无论怎么样,都会解决掉从那个位置开始。两个指针都会放置到他们应该出现的位置。且 helper永远在first前一个。// 当他指向这里之后,我们就开始看,每几下。出一个圈。然后仍然使用for 解决。// 假设这里需要每五人出一下,但是,第一个数的人也包含在内。所以需要循环四次。因为指向的是next//我们要做的是把他取空,所以使用while// 创建集合,依次保存取出的值。ArrayList插入快。List<Integer > josepfu = new ArrayList<>();while (this.first != null) {// 说明到了最后一个,直接添加并且返回if (this.first.getNext() == this.first) {System.out.println("被移除了节点元素: " + first.getNo());josepfu.add(first.getNo());break;}for (int i = 1; i < countNumber; i++) {this.first = first.getNext();helper = helper.getNext();}// 当出来for循环之后,他们会分别停在对应的位置上,first会停在被删除的节点的位置上,helper会停留在first的上一个节点上。// 此时first后移,然后helper指向fistjosepfu.add(first.getNo());System.out.println("被移除了节点元素: " + first.getNo());first = first.getNext();helper.setNext(first);}return josepfu;
}

Java数据结构与算法学习 目前30170字相关推荐

  1. Java数据结构和算法(一)——简介

    本系列博客我们将学习数据结构和算法,为什么要学习数据结构和算法,这里我举个简单的例子. 编程好比是一辆汽车,而数据结构和算法是汽车内部的变速箱.一个开车的人不懂变速箱的原理也是能开车的,同理一个不懂数 ...

  2. JAVA数据结构与算法【简单介绍】

    前几天去面一个大厂,面试官特别好,面试官说到,我们的学习不能本末倒置,数据结构和算法是程序的基础,如果数据结构你没有学好,你真正意义上不算会写代码.你的代码是各处粘贴,杂乱无章的. 由于现在大多用JA ...

  3. 数据结构与算法学习笔记之 从0编号的数组

    数据结构与算法学习笔记之 从0编号的数组 前言 数组看似简单,但掌握精髓的却没有多少:他既是编程语言中的数据类型,又是最基础的数据结构: 一个小问题: 为什么数据要从0开始编号,而不是 从1开始呢? ...

  4. 七桥问题c语言程序数据结构,数据结构与算法学习——图论

    什么是图? 在计算机程序设计中,图结构也是一种非常常见的数据结构 但是图论其实是一个非常大的话题 图结构是一种与树结构有些相似的数据结构 图论是数学的一个分支,并且在数学概念上,树是图的一种 它以图为 ...

  5. java算法概述,Java数据结构与算法基础(一)概述与线性结构

    Java数据结构与算法基础(二)递归算法 Java数据结构与算法基础(一)概述与线性结构 学习目的:为了能更顺畅的读很多底层API代码和拓宽解决问题的思路 一.数据结构概述 1.数据结构是什么?数据与 ...

  6. 数据结构与算法学习笔记之 提高读取性能的链表(上)

    数据结构与算法学习笔记之 提高读取性能的链表(上) 前言 链表(Linked list)比数组稍微复杂一点,在我们生活中用到最常见的应该是缓存,它是一种提高数据读取性能的技术,常见的如cpu缓存,浏览 ...

  7. Java数据结构和算法(四)--链表

    日常开发中,数组和集合使用的很多,而数组的无序插入和删除效率都是偏低的,这点在学习ArrayList源码的时候就知道了,因为需要把要 插入索引后面的所以元素全部后移一位. 而本文会详细讲解链表,可以解 ...

  8. 书籍推荐:《Java数据结构与算法》

    Data Structures and Algorithms in Java (2nd Edition) 没错,这本书的代码都是用Java写的. 现在市面上关于数据结构和算法的书的描述语言一般是C.C ...

  9. java数据结构与算法之顺序表与链表深入分析

    转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/52953190 出自[zejian的博客] 关联文章: java数据结 ...

最新文章

  1. (转)搭建企业内部yum仓库(centos6+centos7+epel源)
  2. 区块链相关论文研读1- 关于边缘计算
  3. MyISAM与InnoDB的索引实现
  4. OSI 网络协议模型为什么是 7 层?
  5. python读取字典元素笔记_Python 学习笔记 - 字典
  6. 负数如何归一化处理_机器学习之数据预处理
  7. 高级会计可以用计算机,高会无纸化考试计算器不好用 建excel计算可以吗?官方回复!...
  8. TensorFlow中读取图像数据的三种方式(转)
  9. 熊海博客php版本,熊海CMS xhcms v1.0代码审计
  10. 蚂蚁课堂视频笔记思维导图-4期 七、Docker
  11. 营业执照15位注册号码含义和查询规则
  12. Q学习(Q learning) 强化学习的简单例子 Matlab实现 可视化
  13. AWS云lamda实时判断IoTCore上传的数据并插入RDS中
  14. word2007制作目录
  15. jQuery插入QuickTime视频播放器
  16. Flutter Align控件用法
  17. YML(YAML)语法(文件后缀为.yml格式)
  18. 深度学习AI美颜系列----人像静态/动态贴纸特效算法实现
  19. Python爬虫以及数据可视化分析
  20. android gallary demo

热门文章

  1. rssi用matlab编写,matlab实现RSSI定位
  2. C#托盘控件notifyIcon的使用
  3. 羊皮卷-gt;羊皮卷之五(世界上最伟大的推销员)
  4. Java实现 蓝桥杯 基础练习 字母图形
  5. [51单片机]学电开发板-一块值得拥有的学习利器
  6. 查看端口号的命令,android SDK启动模拟器的方法
  7. 将自己电脑设置成无线路由器
  8. HTML垂直翻页公告
  9. Ext.isEmpty( Mixed value, [Boolean allowBlank] ) 用法
  10. AMPIRE 128X64驱动程序