视频地址:https://www.bilibili.com/video/BV1E4411H73v?from=search&seid=13120683720695451628
评价:整个教程的数据结构部分讲的挺好的,知识点全都覆盖了,而且每个数据结构都有代码解释,但是最后20节算法部分讲的有点乱,算法部分我决定直接刷leetcode了
二维数组的省内存的保存方法,一般是n行3列,三列分别为行,列,值。
二维数组转稀疏数组:
稀疏数组转二维数组:
根据稀疏数组第一行建立空二维数组
读取稀疏数组后几行数据,插入二维数组中
代码实现:
package com.dataStructure;
public class sparseArray {
public static void main(String[] args){
//新建数组
int Arr [][] = new int[11][11];
Arr[1][2] = 1;
Arr[2][3] = 2;
Arr[3][4] = 3;
//输出初始数组
for(int[] row : Arr){
for(int data : row){
System.out.printf("%d\t",data);
}
System.out.print("\n");
}
//将二维数组转换为稀疏数组
//1 遍历,得到非0个数
int sum = 0;
for (int i = 0; i < 11; i++){
for (int j = 0; j < 11; j++){
if (Arr[i][j] != 0){
sum++;
}
}
}
//2 创建稀疏数组
int[][] sparsArr = new int[sum+1][3];
//第一行
sparsArr[0][0] = 11;
sparsArr[0][1] = 11;
sparsArr[0][2] = sum;
//输入值
int count = 1; //用于记录sparseArr的行
for (int i = 0; i < 11; i++){
for (int j = 0; j < 11; j++){
if (Arr[i][j] != 0){
sparsArr[count][0] = i;
sparsArr[count][1] = j;
sparsArr[count][2] = Arr[i][j];
count++;
}
}
}
//3 输出稀疏数组
System.out.println();
System.out.println("得到的稀疏数组为~~~");
for(int i = 0 ;i< sparsArr.length ;i++){
System.out.printf("%d\t%d\t%d\t\n",sparsArr[i][0],sparsArr[i][1],sparsArr[i][2]);
}
//将二维数组转换回稀疏数组
//1 新建数组
int[][] Arr_back = new int[sparsArr[0][0]][sparsArr[0][1]];
//2 将有值的位置安回原来的位置
for (int i = 1; i<sparsArr.length;i++){
Arr_back[sparsArr[i][0]][sparsArr[i][1]] = sparsArr[i][2];
}
//输出数组
System.out.println();
System.out.print("Arr_back输出为~~~\n");
for(int i = 0;i<sparsArr[0][0];i++){
for(int j = 0;j<sparsArr[0][1];j++){
System.out.print(Arr_back[i][j]+" ");
}
System.out.println();
}
}
}
队列是一个有序列表,可能用数组或链表来实现。
先进入的数据先取出,后进入的数组后取出。
数组模拟队列的思路:
队列本身就是一个有序列表,maxSize来记录该数组最大容量;front和rear分别记录队列前后端的下标,front随着数据输出而改变,rear随着数据输入而改变。
加入数据:若队列不为满,尾部指针后移:rear+1
拿出数据:若队列不为空,前端指针后移:front+1
代码:
package com.dataStructure;
import java.util.Scanner;
public class arrayQueue {
public static void main(String[] args) {
Queue arrayQueue = new Queue(3);
String key = "";
Scanner scanner = new Scanner(System.in);
boolean loop = true;
while (loop) {
//输出一个菜单
System.out.println("press s to show Queue");
System.out.println("press e to exit program");
System.out.println("press a to add element");
System.out.println("press g to get data");
key = scanner.next();
switch (key) {
case "s":
arrayQueue.showQueue();
break;
case "e":
scanner.close();
loop = false;
break;
case"a":
System.out.println("Input a word:");
int value = scanner.nextInt();
arrayQueue.addQueue(value);
break;
case"g":
System.out.println(arrayQueue.getQueue());
break;
}
}
}
}
class Queue {
//装入需要的私有变量
private int rear = -1;
private int front = -1;
private int maxSize;
private int[] que;
//设置Queue的规格
public Queue(int size) {
maxSize = size;
que = new int[size];
}
//判断Queue是否为空
public boolean isEmpty() {
return rear == front;
}
//判断Queue满了没
public boolean isFull() {
return rear == maxSize - 1;
}
//加入一个新元素
public void addQueue(int n) {
if (isFull()) {
System.out.println("Queue is full! can not add!");
return;
}
rear++;
que[rear] = n;
}
//删除一个元素
public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("Queue is empty! can not delete!");
}
front++;
return que[front];
}
//showQueue
public void showQueue() {
if (isEmpty()) {
System.out.println("队列空,无数据,无法showQueue");
} else {
for (int i = 0; i < que.length; i++) {
System.out.printf("Queue[%d] = %d\n", i, que[i]);
}
}
}
}
这时候存在一个问题,数组使用一次之后就不能用了,没有达到复用的效果,例如,我将位置填满后再全部删去,这时候我就没办法再add新的元素进去了。
使用数组模拟形成一个环形队列,利用取模的方式实现。
思路:
Front作为队列第一个元素的引用,初始front = 0
Rear作为队列最后一个元素的后一个元素的引用,初始rear = 0
队列满时,条件为(rear+1)%maxSize = front。
这里要注意的是,虽然maxSize是5,但是我们实际可以使用的只有4个。当总共有5个格子时,分别为0,1,2,3,4,满了的时候,rear = 4,front = 0,maxSize = 5;带入公示4+1模除5 = front = 0。
当rear-1=front时,实际上只有一个元素,就是front所在的位置;当rear = front的时候,最后一个元素rear-1在最初的元素front之前,所以这种情况不存在。
综合4来理解,rear-1 = front时候,存在一个元素,rear = front的时候,不存在元素,maxSize%maxSize永远为0,所以很显然(rear-front)% maxSize就是存在有效数字的个数。这里一定要加maxSize再取模,因为rear-front可能是负数,所以必加。
代码:
package com.dataStructure;
import java.util.Scanner;
public class arrayCircleQueue {
public static void main(String[] args) {
CircleQueue arrayQueue = new CircleQueue(4);
String key = "";
Scanner scanner = new Scanner(System.in);
boolean loop = true;
while (loop) {
//输出一个菜单
System.out.println("press s to show Queue");
System.out.println("press e to exit program");
System.out.println("press a to add element");
System.out.println("press g to get data");
key = scanner.next();
switch (key) {
case "s":
arrayQueue.showQueue();
break;
case "e":
scanner.close();
loop = false;
break;
case"a":
System.out.println("Input a word:");
int value = scanner.nextInt();
arrayQueue.addQueue(value);
break;
case"g":
System.out.println(arrayQueue.getQueue());
break;
}
}
}
}
class CircleQueue {
//装入需要的私有变量
private int rear = 0;
private int front = 0;
private int maxSize;
private int[] que;
//设置Queue的规格
public CircleQueue(int size) {
maxSize = size;
que = new int[size];
}
//判断Queue是否为空
public boolean isEmpty() {
return rear == front;
}
//判断Queue满了没
public boolean isFull() {
return (rear+1)%maxSize == front;
}
//加入一个新元素
public void addQueue(int n) {
if (isFull()) {
System.out.println("Queue is full! can not add!");
return;
}
que[rear] = n;
rear = (rear+1)%maxSize;
}
//删除一个元素
public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("Queue is empty! can not delete!");
}
int value = que[front];
front = (front+1)%maxSize;
return value;
}
//showQueue
public void showQueue() {
if (isEmpty()) {
System.out.println("队列空,无数据,无法showQueue");
} else {
for (int i = front; i < front+(rear+maxSize-front)%maxSize; i++) {
System.out.printf("Queue[%d] = %d\n", i%maxSize, que[i%maxSize]);
}
}
}
}
链表每个节点包含data域,next域:指向下一个节点
创建HeroNode类,其中包含HeroNode next;
创建LinkedList类,先创建一个头节点
遍历思路:
通过一个辅助变量来遍历
一般来说,展示链表中所有数据要先判断链表是否为空。
添加内容思路:
直接添加到尾部:直接加到最后面(遍历找到最后面,再添加)
按顺序添加:找到要添加的位置,新节点.next = temp.next,temp.next = 新节点。
修改节点的思路:
先找到这个节点(遍历)
修改数值
删除节点的思路:
找到要删除节点的前一个节点
Node.next = node.next.next;
被删除的节点将没有其他引用指向它,会被垃圾回收机制回收
循环要注意的点!!!
一般来说,写while循环的时候,内容的顺序是:1)输出,2)if边界的break测试,3)指针后移
循环代码:
void show(){
NodeStack temp = head.next;
while(true){
System.out.println("栈中的值为"+temp.value);
if(temp.next == null){
break;
}
temp = temp.next;
}
}
单链表代码:
package com.dataStructure;
public class linkedList {
public static void main(String[] args){
HeroNode heroNode1 = new HeroNode(1,"阿毛","amao");
HeroNode heroNode2 = new HeroNode(2,"阿狗","agou");
HeroNode heroNode3 = new HeroNode(3,"阿缺","aque");
singleLinkedList list_test = new singleLinkedList();
//乱序输入结果
list_test.addSpecial(heroNode1);
list_test.addSpecial(heroNode3);
list_test.addSpecial(heroNode2);
list_test.show();
//测试修改
HeroNode heroNode4 = new HeroNode(3,"阿","a");
list_test.update(heroNode4);
System.out.println("修改后的结果~~~");
list_test.show();
//删除测试
int test_num = 1;
list_test.del(test_num);
System.out.println("删除后的结果");
list_test.show();
}
}
class HeroNode{
int no;
String name;
String nickName;
HeroNode next;
public HeroNode(int no,String name,String nickName){
this.no = no;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString(){
return "Hero_no = "+no+" Hero_name = "+name+" Hero_nickname = "+nickName ;
}
}
class singleLinkedList{
private HeroNode head = new HeroNode(0,"","");
public void add(HeroNode heroNode){
HeroNode temp = head;
while(true){
if(temp.next == null){
break;
}
temp = temp.next;
}
temp.next = heroNode;
}
public void addSpecial(HeroNode heroNode){
HeroNode temp = head;
boolean label = false;
while(true){
if(temp.next == null){
break;
}
if(heroNode.no<temp.next.no){
break;
}
if(heroNode.no == temp.next.no){
label = true;
break;
}
temp = temp.next;
}
if(label){
System.out.println("没位置了!爬!");
}else {
heroNode.next = temp.next;
temp.next = heroNode;
}
}
public void update(HeroNode newNode) {
HeroNode temp = head;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.no == newNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if(flag) {
temp.name = newNode.name;
temp.nickName = newNode.nickName;
}else{
System.out.println("can not find this no!");
}
}
public void del(int num){
HeroNode temp = head;
boolean flag = false;
while(true){
if(temp.next == null){
break;
}
if(temp.next.no == num){
flag = true;
break;
}
temp = temp.next;
}
if(flag){
temp.next = temp.next.next;
}else{
System.out.println("没找到要删除的啊?");
}
}
public void show(){
if(head.next == null){
System.out.println("列表为空,输出个犊子");
}else{
HeroNode temp = head.next;
while(true){
if(temp == null){
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
}
思路:
先定义一个revertHead = new Node();
从头到尾遍历原来的表,依次取出节点,并将它放在revertHead的最前端(head后面)
原来的链表的head.next = revertHead.next,将除了head以外的内容还给原来的head
难点:
实现从原来的链表中遍历,并将元素挨个放到新链表的头部,需要多次调整两个链表的指向问题。
代码:
public void reverse(Node head) {
if(head.next == null || head.next.next == null){
return;}
Node cur = head.next;
Node next = null;
Node revertHead = new Node(0);
while(cur!=null){
next = cur.next; //将cur后面的值暂时存储在next中
cur.next = revertHead.next; //让cur指向原来revertHead数组的第一个元素
revertHead.next = cur; //让revertHead指向cur,这样就实现了一种类似中间插入的操作(revertHead列表头处插入)
cur = next; //更新cur
}
head.next = revertHead.next; //将一大串列表还给原来的head
}
思路1: 先反转链表,再打印链表
思路2: 利用栈,将单链表压入栈,再打印输出
思路2代码:
public void revertPrint(){
Stack<Integer> stack = new Stack();
if (head.next == null) {
System.out.println("列表为空,输出个犊子");
} else {
Node temp = head.next;
while (true) {
if (temp == null) {
break;
}
stack.add(temp.value);
temp = temp.next;
}
}
while(stack.size()>0) {
System.out.println(stack.pop());
}
}
包含测试各种刚才提到的功能
package com.dataStructure;
import java.util.Scanner;
import java.util.Stack;
public class linkedList_test {
public static void main(String[] args){
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
linkedList_test1 test1 = new linkedList_test1();
test1.add(node1);
test1.add(node2);
test1.add(node3);
//展示目前的情况
System.out.println("目前链表的情况~~~");
test1.show();
//统计元素个数
System.out.println("链表总数~~~");
int res = test1.count();
System.out.println(res);
//输入一个数k,输出列表中倒数第k个数
// System.out.println("please input a value");
// Scanner scanner = new Scanner(System.in);
// int k = scanner.nextInt();
// System.out.println("倒数第 "+k+" 个数是 "+test1.find(test1,k));
//翻转单链表
// System.out.println("链表翻转结果~~~");
// test1.reverse(test1.head);
// test1.show();
//逆序输出
System.out.println("链表翻转输出结果为~~~");
test1.revertPrint();
}
}
class Node{
int value;
Node next;
public Node(int value){
this.value = value;
}
@Override
public String toString(){
return "my value is "+value;
}
}
class linkedList_test1 {
Node head = new Node(0);
//加入一个元素
public void add(Node nodeIn) {
Node temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = nodeIn;
}
//打印所有元素
public void show() {
if (head.next == null) {
System.out.println("列表为空,输出个犊子");
} else {
Node temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
//计算一共多少个数
public int count() {
int counting = 0;
Node temp = head.next;
if (temp == null) {
System.out.println("啥也没有,共0个元素");
} else {
while (true) {
counting++;
if (temp.next == null) {
break;
}
temp = temp.next;
}
}
return counting;
}
//在test1中寻找倒数第k个数是多少
public int find(linkedList_test1 test1, int k) {
if((k>test1.count()) || (k<=0)){
throw new RuntimeException("input error!");
}else{
Node temp = head;
int sum = test1.count();
int need = sum - k+1; //我们需要正着数到第need个数
int value = 0; //作为一个判定条件
while (true) {
temp = temp.next;
value++;
if (value == need) {
break;
}
}
return temp.value;
}
}
//反转链表
public void reverse(Node head) {
if(head.next == null || head.next.next == null){
return;}
Node cur = head.next;
Node next = null;
Node revertHead = new Node(0);
while(cur!=null){
next = cur.next; //将cur后面的值暂时存储在next中
cur.next = revertHead.next; //让cur指向原来revertHead数组的第一个元素
revertHead.next = cur; //让revertHead指向cur,这样就实现了一种类似中间插入的操作(revertHead列表头处插入)
cur = next; //更新cur
}
head.next = revertHead.next; //将一大串列表还给原来的head
}
//利用栈倒序输出链表
public void revertPrint(){
Stack<Integer> stack = new Stack();
if (head.next == null) {
System.out.println("列表为空,输出个犊子");
} else {
Node temp = head.next;
while (true) {
if (temp == null) {
break;
}
stack.add(temp.value);
temp = temp.next;
}
}
while(stack.size()>0) {
System.out.println(stack.pop());
}
}
双向链表相对于单链表来说:
查找的方向不止是一个方向
可以实现自我删除,不需要像单链表一样寻找要删除节点的前一个节点
思路分析:
遍历:和单向链表一致,可以前向,也可以后向。
添加元素到链表最后:1) 先遍历到这个链表的最后
2) temp.next = newNode; newNode.pre = temp;
按照编号添加元素:1) 找到要添加的位置的前一个元素temp
2) heroNode.next = temp.next;
3) temp.next = heroNode;
4) heroNode.next.pre = heroNode;
5) heroNode.pre = temp; //这些步骤本质上就是将原来的两根指针变成四根
修改:找到这个节点直接修改就可以
自我删除:
1) 找到这个节点
2) temp.pre.next = temp.next;
temp.next.pre. = temp.pre;
(当删除的节点是最后一个时,后面句话不需要,否则会出现空指针)
代码:
//构建一个双向列表的节点
class Node{
int value;
Node next;
Node pre;
public Node(int value){
this.value = value;
}
@Override
public String toString(){
return "my value is "+value;
}
}
//加入一个元素
public void add(Node nodeIn) {
Node temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = nodeIn;
nodeIn.pre = temp;
}
//按序号加入元素
public void addSpecial(Node heroNode){
Node temp = head;
boolean label = false;
while(true){
if(temp.next == null){
break;
}
if(heroNode.value<temp.next.value){
break;
}
if(heroNode.value == temp.next.value){
label = true;
break;
}
temp = temp.next;