1. 数据结构与算法入门
前言
很早之前做的笔记了,做一个备忘
1.1 经典算法问题:
- 汉诺塔
- 八皇后问题
- 马踏棋盘
1.2 字符串匹配
1.2.1 暴力匹配
1.2.2 KMP算法
1.3 数据结构和算法重要性
- 算法是程序灵魂
- 内存计算框架
1.4 数据结构与算法关系
2. 实际算法问题:
2.1 str.replaceAll( str )
2.1.1 问题:
试写出单链表表示的字符串类以及字符串结点类的定义,并且依次实现它的构造函数,以及计算串的长度,串赋值,判断两串相等,求子串,两串连接,求子串在串中位置等七个成员函数
2.2 其他几个问题:
- 丢手帕问题
- 磁盘问题
- 公交车
- 画图
- 球和篮子
- 扔石头
- 修路问题,最小路径
- 最短路径问题
- 汉诺塔
- 八皇后
2.3 线性结构与非线性结构
- 数据与元素一对一的线性关系
- 顺序存储,元素都是连续的
- 链式存储,元素是不连续的
- 数组,队列,链表和栈
2.3.1 非线性结构
二维数组,多维数组,广义表,树结构,图结构
3. 稀疏数组和队列
3.1 稀疏数组的处理方法:
- 记录数组一共几行几列,有多少个不同的值
- 把具有不同值的元素的行列记录在一个小规模数组
3.1.1 二维数组转稀疏数组的方法
- 遍历原始二维数组,保留有效个数
- 根据sum创建稀疏数组spareArr int[sum+1][3]
- 二维数组的有效数据存入到稀疏数组
3.2 稀疏数组转二维数组:
- 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组,
- 读取稀疏数组的后几行数据,并赋值给原始的二维数组
4. 稀疏数组的Java代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| package array;
import java.util.ArrayList; import java.util.List;
public class SpareceArray {
public static void main(String[] args) { int[][] twoArrayConvertSparecArray = twoArrayConvertSparecArray();
SparecArrayConverttwoArray(twoArrayConvertSparecArray); }
private static void SparecArrayConverttwoArray(int[][] array) { int[][] result = new int[array[0][0]][array[0][1]];
for (int i = 1; i < array.length; i++) { result[array[i][0]][array[i][1]] = array[i][2]; }
printArray(array); printArray(result); }
private static int[][] twoArrayConvertSparecArray() { int[][] array = new int[8][8]; List list = new ArrayList(); array[4][5] = 2; array[3][7] = 11; array[2][3] = 11; printArray(array); int sumCount = 0; sumCount = calcuArrSize(array, sumCount); int[][] spareceArray = new int[sumCount+1][3];
spareceArray[0][0] = array.length; spareceArray[0][1] = array[0].length; spareceArray[0][2] = sumCount;
int noZeroCount = 0; for (int i = 0; i < array.length; i++) { for (int j = 0; j < array[i].length; j++) { if(array[i][j] != 0) { noZeroCount++; spareceArray[noZeroCount][0] = i; spareceArray[noZeroCount][1] = j; spareceArray[noZeroCount][2] = array[i][j];
} } } return spareceArray; }
private static int calcuArrSize(int[][] array, int sumCount) { for (int i = 0; i < array.length; i++) { for (int j = 0; j < array[i].length; j++) { if(array[i][j] != 0) { sumCount++; } } } return sumCount; }
private static void printArray(int[][] array) { System.err.println("-----------------我是分割线-----------------"); for (int i = 0; i < array.length; i++) { for (int j = 0; j < array[i].length; j++) { System.out.print(array[i][j] + "\t" ); } System.out.println(); } System.err.println("-----------------我是分割线-----------------");
} }
|
5. 队列:
5.1 数组模拟队列
5.1.1 思路分析:addQueue
- 将尾指针 rear + 1 , 表示入队。当 rear == front 表示空队列
- 如果rear 等于队列 maxSize - 1, 表示队列满,否则可以增加元素
5.1.2 思路分析:removeQueue
- 将队头指针进行出队操作
- 每次移除都需要队头指针 +1
- 将队列后面的数据向前拷贝
5.2 JAVA代码实现数组底层的队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| package array;
import javax.sql.rowset.serial.SerialArray; import java.util.Arrays;
public class MyQueue {
private int rear;
private int front;
private int maxSize;
private int[] arr;
public MyQueue(int size) { if (size <= 0) { System.err.println("队列不能小于或者等于 0"); return; } rear = front = -1; this.arr = new int[size]; this.maxSize = size; }
public int removeQueue() { if (isEmpty()) { System.err.println("队列为空,不能移除"); return -1; } for (int i = 1; i < rear; i++) { arr[i - 1] = arr[i]; }
return arr[++front]; }
public void addQueue(int val) { boolean empty = isFull(); if (!empty) { System.err.println("队列已满,无法添加"); return; } arr[++rear] = val; }
private boolean isFull() { return rear != maxSize - 1; }
private boolean isEmpty() { return rear == front; }
public void showQueue(){ System.err.println(Arrays.toString(this.arr)); }
public static void main(String[] args) { MyQueue myQueue = new MyQueue(3); myQueue.addQueue(2); myQueue.addQueue(5); myQueue.addQueue(6); myQueue.removeQueue(); myQueue.removeQueue(); myQueue.removeQueue(); myQueue.addQueue(6); myQueue.removeQueue();
myQueue.showQueue(); } }
|
5.2.1 数组队列的弊端
- 队头和队尾都执行队列的尾部时候,无法添加也无法删除
- 数组复用性不好
5.3 数组实现循环队列
5.3.1 如何判定队列已经满了
算法
( rear + 1 ) % maxSize == front
5.3.2 如何判定队列是空的
rear == front
5.3.3 如何判定有效个数
举例:
maxSize = 7
front = 5
rear = 5
maxSize + (rear+1) - front % maxSize
5.3.4 使用数组实现循环队列的方式2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
| package array;
public class MyQueue {
private int rear;
private int front;
private int maxSize;
private int[] arr;
public MyQueue(int size) { if (size <= 0) { System.err.println("队列不能小于或者等于 0"); return; } rear = front = 0; this.arr = new int[size]; this.maxSize = size; }
public int removeQueue() { if (isEmpty()) { System.err.println("队列为空,不能移除"); return -1; }
return arr[++front]; }
public void addQueue(int val) { boolean empty = isFull(); if (empty) { System.err.println("队列已满,无法添加"); return; } arr[rear] = val; rear = (rear + 1) % maxSize; }
private boolean isFull() {
return (rear + 1) % maxSize == front; }
private boolean isEmpty() { return rear == front; }
public void showQueue(){ if(arr == null || arr.length == 0){ System.err.println("队列为空,不能遍历"); return; }
for (int i = front; i < front + size(); i++) { System.err.print(arr[i % maxSize] + "\t");
}
}
public int size(){ return (rear + maxSize - front) % maxSize; }
public static void main(String[] args) { MyQueue myQueue = new MyQueue(8); myQueue.addQueue(2); myQueue.addQueue(55); myQueue.addQueue(35); myQueue.addQueue(25); myQueue.addQueue(65); myQueue.addQueue(85); myQueue.addQueue(75);
myQueue.removeQueue(); myQueue.removeQueue(); myQueue.removeQueue();
myQueue.showQueue(); } }
|
6. 链表
6.1 使用自制的链表处理
6.1.1 使用java代码实现链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
| package queue;
public class SingelQueue {
public static void main(String[] args) { HeroNode heroNode1 = new HeroNode(0, "111", "111"); HeroNode heroNode2 = new HeroNode(0, "222", "222"); HeroNode heroNode3 = new HeroNode(0, "333", "333"); HeroNode heroNode4 = new HeroNode(0, "444", "444"); SingelLinkedList singelLinkedList = new SingelLinkedList(); singelLinkedList.add(heroNode1); singelLinkedList.add(heroNode2); singelLinkedList.add(heroNode3); singelLinkedList.add(heroNode4); singelLinkedList.list();
} }
class SingelLinkedList { private HeroNode head = new HeroNode(0, "", "");
public void add(HeroNode node) { HeroNode heroNode = head;
while (true) { if(heroNode.getNext() == null){ break; } heroNode = heroNode.getNext(); } heroNode.setNext(node);
}
public void list(){ HeroNode heroNode = head;
while (true) { if(heroNode.getNext() == null){ break; } System.err.println(heroNode); heroNode = heroNode.getNext(); }
} }
class HeroNode { private int no; private String name; private String nickName; private HeroNode next;
public HeroNode(int no, String name, String nickName) { this.no = no; this.name = name; this.nickName = nickName; }
public int getNo() { return no; }
public void setNo(int no) { this.no = no; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getNickName() { return nickName; }
public void setNickName(String nickName) { this.nickName = nickName; }
public HeroNode getNext() { return next; }
public void setNext(HeroNode next) { this.next = next; }
@Override public String toString() { return "HeroNode{" + "no=" + no + ", name='" + name + '\'' + ", nickName='" + nickName + '\'' + ", next=" + next + '}'; } }
|
6.1.2 使用排序的方式插入链表改进上述链表
- 这里使用了查找的方式,判断是否相等,遍历的时候使用头指针的下一个节点进行遍历操作
- 插入节点使用被插入的节点的下一个节点指向当前遍历节点的下一个节点,当前循环节点指向指向当前被插入的节点
- 如果节点相等,给出提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public void addByOrder(HeroNode node) { HeroNode heroNode = head; boolean flag = false; while (true) { if(heroNode.getNext() == null){ break; } if(heroNode.getNext().getNo() > node.getNo()){ break; }else if(heroNode.getNext().getNo() == node.getNo()){ flag = true; break; } heroNode = heroNode.getNext(); }
if(flag) { System.err.println("被插入的节点已经存在"); }else{ node.setNext(heroNode.getNext()); heroNode.setNext(node); }
}
|
6.1.3 删除节点有多少种情况
如果删除的是头节点
如果是中间的节点,需要将被删除的上一个节点执行被删除节点的下一个节点
如果在尾部,删除尾部节点
6.1.4 删除的第一种方式实现办法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
public void del(int no) { HeroNode temp = head; if (temp.getNext().getNo() == no) { head.setNext(head.getNext().getNext()); return; } boolean flag = false; while (true) { if (temp.getNext().getNo() == no) { flag = true; break; } temp = temp.getNext(); }
if(flag){ temp.setNext(temp.getNext().getNext()); }else{ System.err.println("没有找到对应编号的英雄"); }
}
|
6.1.5 删除的第二种方式分析:
删除根据第几个来删除,必须我想删除第一个节点
- 第一个节点对应了头节点,需要对于头指针指向进行后移
其余根据no编号删除雷同,只需要编号判断改为第几个判断即可
6.2 关于链表的面试题(重点)
单链表当中有效节点的个数
获取倒数第n个节点的节点
反转链表
- 定义一个节点:反转用的头
- 从头到尾遍历原来链表,每遍历一个节点,就取出,放到新的链表前面
- 将旧链表的下一个节点指向逆序链表的下一个节点
从尾部到头部逆序打印链表
- 第一种方法:显逆序,逆序之后打印节点(不可取)
- 使用栈实现:将所有的节点加入栈当中,然后使用栈进行打印
6.2.1 节点个数的实现
较为简单,遍历统计遍历几次即可
6.2.2 倒数第N个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
public HeroNode getLastIndexHeroNode(int index){ int count = count(); if(index <= 0 || index > count){ return null; } HeroNode temp = head.getNext(); int i = 0; int size = count-index; while (true) { if (i == size) { break; } i++; temp = temp.getNext(); } return temp; }
|
6.2.3 反转链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public void reverse(){ if(head == null || head.getNext() == null || head.getNext().getNext() == null){ System.err.println("无需反转"); return; } HeroNode reverseHead = new HeroNode(0, "", ""); HeroNode temp = head.getNext(); HeroNode next = null; while (temp != null) { next = temp.getNext(); temp.setNext(reverseHead.getNext()); reverseHead.setNext(temp); temp = next; } head.setNext(reverseHead.getNext()); }
|
6.2.4 反序打印链表
- 需要使用到栈这种结构,将节点压进栈当中,然后从栈中取数据进行遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
public void reversePrint() { System.err.println(); if (head == null || head.getNext() == null || head.getNext().getNext() == null) { System.err.println("无需反转打印"); return; } HeroNode hero = head.getNext(); Stack<HeroNode> stack = new Stack<>(); while (hero != null) { stack.push(hero); hero = hero.getNext(); } while(stack.size()>0) { System.out.println(stack.pop()); } }
|
6.2.5 合并两个链表
- 遍历两个链表,先将第一个链表进行插入,然后根据第二个链表进行相同的插入操作s
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
public static SingelLinkedList mergeList(SingelLinkedList list1, SingelLinkedList list2) { if(list1 == null && list2 == null){ return null; } if(list1 == null) { return list2; } if(list2 == null) { return list1; } SingelLinkedList result = new SingelLinkedList(); HeroNode next1 = list1.getHead(); while (true) { if (next1 == null) { break; } HeroNode next = next1.getNext(); SingelLinkedList.addByOrder(result, next1); next1 = next;
} HeroNode next2 = list2.getHead().getNext(); while(true){ if(next2 == null) { break; } HeroNode next = next2.getNext(); SingelLinkedList.addByOrder(result, next2); next2 = next;
} return result; }
|
6.3 分析双向链表的遍历,添加、删除的操作思路
- 遍历方和单链表一样只是可以朝前,可以向后
- 添加
- 默认添加到双向链表的最后
- 先找到双向链表的最后这个节点
- temp.next = new heronode
- newheronode.pre = temp
- 删除
- 因为是双向的,可以自我删除
- 找到要删除的这个节点,temp
- temp.next.pre = temp.pre
- temp.pre.next = temp.next
- 修改
- 不需要太大变动,和原来的链表类似
6.4 循环链表(约瑟夫问题)
6.4.1 什么是循环链表
无论是加入还是删除节点,最后一个节点要么指向自己,要么指向链表的头节点,形成环状的一个链表
6.4.2 构建一个环形链表的思路
- 先创建一个节点,first 指向改节点, 并且形成环形
- 后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表当中即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
public void add(int size){ if(size < 1){ System.out.println("size的值不正确"); return; }
CircularNode cur = null; for (int i = 1; i <= size; i++) { CircularNode newcur = new CircularNode(i, "小孩"+i, "小孩昵称"+i); if(i == 1){ first = newcur; first.setNext(first); cur = first; }else{ cur.setNext(newcur); newcur.setNext(first); cur = newcur; } }
}
|
6.4.3 遍历环形链表
- 先让一个辅助指针 curBoy , 指向first
- 通过while循环,遍历环形链表,当curBoy.next == first 结束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public void list(){ if(first == null){ System.err.println("循环链表为空"); return; } CircularNode circularQueue = first; while(true){ System.err.println(circularQueue); if(circularQueue.getNext() == first){ break; } circularQueue = circularQueue.getNext(); } }
|
6.4.4 模拟约瑟夫问题的实现(重点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
public void count(int begin, int countNum, int nums){ if (first == null || begin < 1 || countNum > nums) { System.out.println("参数输入有误, 请重新输入"); return; } CircularNode helper = first; while(helper.getNext() != first){ helper = helper.getNext(); } for (int i = 0; i < begin; i++) { first = first.getNext(); helper = helper.getNext(); } while(true){ if(helper == first) { break; } for (int i = 0; i < countNum - 1; i++) { first = first.getNext(); helper = helper.getNext(); } System.out.printf("小孩%d出圈\n", first.getNo()); first = first.getNext(); helper.setNext(first); } System.out.printf("最后留在圈中的小孩编号%d \n", first.getNo()); }
|
7. 栈
7.1 如何实现一个栈结构
使用一个top 初始化为 -1 表示栈定, 压入数据 为 push + 1
使用 top – 表示出栈
由于栈帧只能从一个方向操作,则需要对于栈帧进行一下判断是否为空或者已满
7.2 使用栈模拟一个表达式计算(重点)
- 首先:使用一个索引扫描整个表达式,用于往两个栈中添加数据
- 如果是数字,则直接加入到数栈当中
- 如果是操作符,则分为以下两种情况
- 如果是加法和减法,则直接加入到栈顶
- 如果是乘法或者除法,则压如下一个表达式索引值