算法和数据结构
数据结构
数据结构包括线性结构和非线性结构
线性结构常见的有:数组、队列、链表和栈
- 线性元素特点是一对一
- 线性结构有顺序存储结构(数组)和链式存储结构(链表)
非线性结构包括:二维数组,多维数组,广义表,树结构,图结构
稀疏数组
首行记录:一个数记录表有多少行,第二行记录表有列,第三行记录表有多少有效数据
需求:棋盘或地图(保存棋盘)达到压缩的效果
代码实现:
public static void main(String[] args) {
// 创建原始的二维数组11*11
// 0:表示没有棋子,1表示黑子 2表示篮子
int chessArr1[][] = new int[11][11];
chessArr1[1][2] = 1;
chessArr1[2][3] = 2;
// 原始二维数组
for (int i = 0; i < chessArr1.length; i++) {
System.out.println(Arrays.toString(chessArr1[i]));
}
// 将二维数组 转为 稀疏数组
// 先循环 获取到非0数据的个数
int sum = 0;
for (int i = 0; i < chessArr1.length; i++) {
for (int j = 0; j < chessArr1[i].length; j++) {
if(chessArr1[i][j] != 0){
sum++;
}
}
}
System.out.println("总共有效数据,sum:"+sum);
// 可以创建稀疏数组 稀疏数组第一列
int[][] sparseArr = new int[sum+1][3];
// 给稀疏数组赋值
sparseArr[0][0] = chessArr1[0].length;
sparseArr[0][1] = chessArr1.length;
sparseArr[0][2] = sum;
int count = 0;
for (int i = 0; i < chessArr1.length; i++) {
for (int j = 0; j < chessArr1[i].length; j++) {
if(chessArr1[i][j] != 0){
count++;
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = chessArr1[i][j];
}
}
}
// 输出稀疏数组
System.out.println("稀疏数组为:");
for (int i = 0; i < sum+1; i++) {
System.out.println(Arrays.toString(sparseArr[i]));
}
// 恢复成原本的二维数组
// 1.先读取稀疏数组第一行数据
// 二维数组原本的行数
int row =sparseArr[0][0];
// 二维数组原本的列数
int column =sparseArr[0][1];
// 有效数据的个数
int data = sparseArr[0][2];
int[][] chessArr2 =new int[row][column] ;
// 不是从首行开始,首行是记录表的整体数据,所以从1开始
// 开始还原二维数组
for (int i = 1; i < data+1; i++) {
int rowArr = sparseArr[i][0];
int columnArr = sparseArr[i][1];
int num = sparseArr[i][2];
chessArr2[rowArr][columnArr]=num;
}
System.out.println("还原结果:");
for (int i = 0; i < chessArr2.length; i++) {
System.out.println(Arrays.toString(chessArr2[i]));
}
}
队列
⭐️是一个有序列表,可以用数组或链表来实现。
原则:先入先出
思路:
代码实现:
class CircleArrayQueue{
private int maxSize;// 最大容量
// front 变量的含义做一个调整: front 就指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素
// front 的初始值 = 0
private int front;
// rear 变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定. //rear 的初始值 = 0
private int rear; // 队列尾
private int[] arr; // 该数据用于存放数据
public CircleArrayQueue(int arrMaxSize){
maxSize =arrMaxSize;
this.arr = new int[maxSize];
this.front = 0;
this.rear = 0;//!指向队列尾,指向队列尾的数据(即就是队列最一个数据)
}
// 判断队列是否满
public boolean isQueueFull(){
return (rear+1)%maxSize == front;
}
// 判断队列是否为空
public boolean isEmpty(){
return front == rear;
}
// 添加数据到队列
public void addQueue(int num){
// 判断队列是否满
if(isQueueFull()){
System.out.println("队列已满,无法添加");
return;
}
arr[rear] = num;
// 将 rear 后移, 这里必须考虑取模
rear = (rear+1) % maxSize;
return ;
}
// 获取队列的数据,出队列
public int popQueue(){
// 判断队列是否为空
if(isEmpty()){
throw new RuntimeException("队列空的");
}
// 这里需要分析front是指向队列的第一个元素
// 1.先把front 对应的值保存到一个临时变量
// 2.将front 后移
// 3.将临时返回的变量返回
int value = arr[front];
front = (front + 1) % maxSize;
return value;
}
// 显示队列的所有数据
public void showQueue(){
// 判断队列是否满
if(isEmpty()){
System.out.println("队列空的,没有数据");
return;
}
// 思考:从 front开始遍历,遍历多少个元素
int number = (rear + maxSize - front)%maxSize;
for (int i = front; i < front + number; i++) {
System.out.println("arr["+i % maxSize+"]="+arr[i % maxSize]);
}
}
// 显示队列的头数据,不是取出只是显示
public int headQueue(){
// 判断队列是否满
if(isEmpty()){
throw new RuntimeException("队列空的,没有数据");
}
return arr[front];
}
}
单链表(链表Linked List)
链表是有序的列表
每个节点
- 有个==data==属性来存放数据
- 有个next属性存放下一个节点
节点存放的下一个数据, 不是 连续存放的
思路:
代码实现:
// 单链表的的节点属性
class SingLinkedListNode{
// 当前存储的数据,就存放int数据类型(也可以改成类来存放)
public int data;
// 指向下一个节点
SingLinkedListNode next;
// 构造器
public SingLinkedListNode(int data){
this.data = data;
}
// 为了显示方法,重写toString
@Override
public String toString() {
return "SingLinkedListNode{" +
", no=" + data +
'}';
}
}
// 单链表
class SingLinkedList{
// 初始化一个头节点,头节点不动,不存放具体数据
private SingLinkedListNode head = new SingLinkedListNode(0);
// 添加节点
// 思路,当不考虑编号顺序时
// 1.当前链表的最后节点
// 2.将最后这个节点的next指向 新的节点
public void add(SingLinkedListNode singLinkedListNode){
// 因为head节点不能动
// 要有一个辅助指针遍历单链表
// 因为单链表,因为我们找的temp是位于添加位置的前一个节点,否则插入不了
SingLinkedListNode temp = head;
boolean flag = false; // 标志添加的编号是否存在,默认为false
while (true){
// 找到最后一个节点
if(temp.next == null){
// 指向新的节点
break;
}
// 找到这个位置就可以插入
if(temp.next.data > singLinkedListNode.data){
break;
}else if(temp.next.data == singLinkedListNode.data){
// 说明该数据已经有了
flag = true;
break;
}
// 没找到辅助指针 往下找
temp = temp.next;
}
if(flag){
// 不能添加,编号已经存在
System.out.println("插入的数据,已经存在了");
return;
}
// 插入到链表中,temp的后面
singLinkedListNode.next = temp.next;
temp.next = singLinkedListNode;
}
// 显示链表 遍历
public void list(){
// 判断链表是否为空
if(head.next == null){
System.out.println("链表为空~");
return;
}
// 因为头节点,不能动,因此我们需要一个辅助变量
SingLinkedListNode temp = head.next;
while(true){
// 判断链表是否到 最后了
if(temp == null){
break;
}
// 输出节点信息
System.out.println(temp);
// 移动到下一位
temp = temp.next;
}
}
//修改节点信息,根据data信息,修改data
public void update(SingLinkedListNode oldNode,SingLinkedListNode newNode){
// 判断是否为空
if(head.next == null){
System.out.println("链表为空!");
return;
}
// 要有一个辅助指针遍历单链表
SingLinkedListNode temp = head;
boolean flag =false; // 表示是是否找到该节点
while(true){
if(temp == null){
break;// 已经遍历完
}
if(temp.data == oldNode.data){
// 找到
flag = true;
break;
}
}
if(flag){
temp.data = newNode.data;
}else{
// 没有找到
System.out.println("没有找到编号为:"+oldNode.data);
}
}
}
双向链表
对上图的说明:
分析 双向链表的遍历,添加,修改,删除的操作思路===》代码实现
-
遍历 方和 单链表一样,只是可以向前,也可以向后查找
-
添加 (默认添加到双向链表的最后)
(1) 先找到双向链表的最后这个节点
(2) temp.next = newHeroNode
(3) newHeroNode.pre = temp;
-
修改 思路和 原来的单向链表一样.
-
删除
(1) 因为是双向链表,因此,我们可以实现自我删除某个节点
(2) 直接找到要删除的这个节点,比如 temp
(3) temp.pre.next = temp.next
(4) temp.next.pre = temp.pre
// 创建一个双向链表的类
class DoubleLinkedList {
// 先初始化一个头节点, 头节点不要动, 不存放具体的数据
private HeroNode2 head = new HeroNode2(0, "", "");
// 返回头节点
public HeroNode2 getHead() {
return head;
}
// 遍历双向链表的方法
// 显示链表[遍历]
public void list() {
// 判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
// 因为头节点,不能动,因此我们需要一个辅助变量来遍历
HeroNode2 temp = head.next;
while (true) {
// 判断是否到链表最后
if (temp == null) {
break;
}
// 输出节点的信息
System.out.println(temp);
// 将 temp 后移, 一定小心
temp = temp.next;
}
}
// 添加一个节点到双向链表的最后.
public void add(HeroNode2 heroNode) {
// 因为 head 节点不能动,因此我们需要一个辅助遍历 temp
HeroNode2 temp = head;
// 遍历链表,找到最后
while (true) {
// 找到链表的最后
if (temp.next == null) {//
break;
}
// 如果没有找到最后, 将将 temp 后
temp = temp.next;
}
// 当退出 while 循环时,temp 就指向了链表的最后
// 形成一个双向链表
temp.next = heroNode;
heroNode.pre = temp;
}
// 修改一个节点的内容, 可以看到双向链表的节点内容修改和单向链表一样
// 只是 节点类型改成 HeroNode2
public void update(HeroNode2 newHeroNode) {
// 判断是否空
if (head.next == null) {
System.out.println("链表为空~");
return;
}
// 找到需要修改的节点, 根据 no 编号
// 定义一个辅助变量
HeroNode2 temp = head.next;
boolean flag = false; // 表示是否找到该节点
while (true) {
if (temp == null) {
break; // 已经遍历完链表
}
if (temp.no == newHeroNode.no) {
// 找到
flag = true;
break;
}
temp = temp.next;
}
// 根据 flag 判断是否找到要修改的节点
if (flag) {
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else { // 没有找到
System.out.printf("没有找到 编号 %d 的节点,不能修改\n", newHeroNode.no);
}
}
// 从双向链表中删除一个节点, // 说明
// 1 对于双向链表,我们可以直接找到要删除的这个节点
// 2 找到后,自我删除即可
public void del(int no) {
// 判断当前链表是否为空
if (head.next == null) {// 空链表
System.out.println("链表为空,无法删除");
return;
}
HeroNode2 temp = head.next; // 辅助变量(指针)
boolean flag = false; // 标志是否找到待删除节点的
while (true) {
if (temp == null) { // 已经到链表的最后
break;
}
if (temp.no == no) {
// 找到的待删除节点的前一个节点 temp
flag = true;
break;
}
temp = temp.next; // temp 后移,遍历
}
}
栈(stack)
特性: 先入后出
是先入后出的有序列表,栈底不变,变化的一端为栈顶
出栈(pop) 入栈(push)
思路:
代码实现(数组模拟):
// 定义一个ArrayStack 表示栈
class ArrayStack{
private int maxSize;// 栈的大小
private int[] stack;// 数组,数组模拟栈,数据就放在该数组
private int top = -1;// top表示栈顶,初始化为-1
// 构造器
public ArrayStack(int maxSize){
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
// 栈满
public boolean isFull(){
return top == this.maxSize-1;
}
// 栈空
public boolean isEmpty(){
return top == -1;
}
// 入栈
public void push(int data){
// 判断是否满
if(isFull()){
System.out.println("栈已满,无法放");
return;
}
stack[++top] = data;
}
// 出栈
public int pop(){
if(isEmpty()){
System.out.println("空的,没有数据");
return -1;
}
int value = stack[top];
top-- ;
return value;
}
// 显示栈的情况(遍历栈),遍历时,需要从栈顶开始显示
public void print(){
if(isEmpty()){
System.out.println("栈空,没有数据");
return;
}
// 需要从栈顶开始显示
for (int i = top; i >= 0 ; i--) {
System.out.println("stack["+top+"]="+stack[top]);
}
}
}
算法
算法的时间复杂度
递归
简单的说: 递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂的问题,同时 可以让代码变得简洁。
public static void main(String[] args) {
// TODO Auto-generated method stub
//通过打印问题,回顾递归调用机制
//test(4);
int res = factorial(3);
System.out.println("res=" + res);
}
//打印问题.
public static void test(int n) {
if (n > 2) {
test(n - 1);
} //else {
System.out.println("n=" + n);
// }
}
//阶乘问题
public static int factorial(int n) {
if (n == 1) {
return 1;
} else {
return factorial(n - 1) * n; // 1 * 2 * 3
}
}
排序算法
冒泡排序(BubbleSort)
时间复杂度O(n2)
代码实现
public static void main(String[] args) {
int[] arr = new int[]{3,9,-1,10,-2};
// 优化 如果某趟排序中,没发生一次交换,可以提前结束排序
boolean flag = true;
for (int i = 0; i < arr.length-1; i++) {
for (int j = 0; j < arr.length-i-1; j++) {
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = false;
}
}
if(flag){
// 再一趟排序中,一次都没有发生交换
break;
}
}
System.out.println(Arrays.toString(arr));
}
选择排序(SelectSort)
时间复杂度O(n2)
/**
* 选择排序
*/
public class SelectSort {
public static void main(String[] args) {
int[] arr = new int[]{3, 9, -1, 10, -2};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
// 选择排序
public static void selectSort(int[] arr) {
int max = 0;
int maxIndex = 0;
for (int i = 0; i < arr.length; i++) {
max = arr[i];
maxIndex = i;
for (int j = i; j < arr.length - 1; j++) {
if (arr[j] > max) {
max = arr[j];
maxIndex = j;
}
}
if(maxIndex != i){
arr[maxIndex] = arr[i];
arr[i] = max;
}
}
}
}
插入排序
代码实现
public static void main(String[] args) {
int[] arr = new int[]{3, 9, -1, 10, -2};
insertSortMethod(arr);
System.out.println(Arrays.toString(arr));
}
public static void insertSortMethod(int[] arr){
int insertVal = 0;
int insertIndex = 0;
for (int i = 1; i < arr.length; i++) {
// 定义待插入的数
insertVal = arr[i];
insertIndex = i -1;
// 给insertVal 找到插入的位置
// 说明
// 1.insertIndex >= 0 保证在给insertVal 找到插入位置,不越界
// 2.insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
// 3.就需要将arr[insertIndex] 后移
while(insertIndex >= 0 && insertVal < arr[insertIndex]){
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
// 当退出while循环时,说明插入的位置找到了,insertIndex + 1
// 这里我们判断是否需要赋值
if(insertIndex + 1 != i){
arr[insertIndex+1] = insertVal;
}
}
}
基数排序
思路:
代码实现:
// 基数排序方法
public static void radixSort(int[] arr){
// 取出最大值
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if(arr[i] > max){
max = arr[i];
}
}
int maxLength = (max+"").length();
int[][] bucket = new int[10][arr.length];
int[] bucketElementCount = new int[10];
for (int i = 0, n = 1; i < maxLength; i++,n*=10) {
for (int j = 0; j < arr.length; j++) {
// 取出每个元素的个位
int digitOfElement = arr[j]/n %10;
bucket[digitOfElement][bucketElementCount[digitOfElement]] = arr[j];
bucketElementCount[digitOfElement]++;
}
// 按照这个桶的顺序
int index = 0;
for (int j = 0; j < bucketElementCount.length; j++) {
if(bucketElementCount[j] != 0){
for (int k = 0; k < bucketElementCount[j]; k++) {
arr[index++] = bucket[j][k];
}
}
bucketElementCount[j] = 0;
}
System.out.println(Arrays.toString(arr));
}
}
查找算法
二分查找法
前提要显示排好序的集合
二分查找的思路分析
1. 首先确定该数组的中间的下标
mid = (left + right) / 2
2. 然后让需要查找的数 findVal 和 arr[mid] 比较
2. 1 findVal > arr[mid] , 说明你要查找的数在mid 的右边, 因此需要递归的向右查找
2.2 findVal < arr[mid], 说明你要查找的数在mid 的左边, 因此需要递归的向左查找
2.3 findVal == arr[mid] 说明找到,就返回
//什么时候我们需要结束递归.
1) 找到就结束递归
2) 递归完整个数组,仍然没有找到findVal ,也需要结束递归 当 left > right 就需要退出
============================================================================
插值查找算法的 举例说明
数组 arr = [1, 2, 3, ......., 100]
假如我们需要查找的值 1
使用二分查找的话,我们需要多次递归,才能找到 1
使用插值查找算法
int mid = left + (right – left) * (findVal – arr[left]) / (arr[right] – arr[left])
int mid = 0 + (99 - 0) * (1 - 1)/ (100 - 1) = 0 + 99 * 0 / 99 = 0
比如我们查找的值 100
int mid = 0 + (99 - 0) * (100 - 1) / (100 - 1) = 0 + 99 * 99 / 99 = 0 + 99 = 99
代码实现:
/**
* 二分查找算法
* @param arr 数组
* @param left 左边索引
* @param right 右边索引
* @param findVal 要查找的值
* @return 如果找到就返回下标,如果没有找到,就返回-1
*/
public static int binarySearch(int[] arr, int left, int right, int findVal){
// 当left > right 时,说明递归整个数组,但是没有找到
if(left > right){
return -1;
}
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) {
// 向右递归
return binarySearch(arr, mid + 1, right, findVal);
}else if( findVal < midVal){
// 向左递归
return binarySearch(arr, left, mid - 1, findVal);
}else {
return mid;
}
}
问题
单链表的常见面试题有如下:
1)求单链表中有效节点的个数
2)查找单链表中的倒数第k个结点 【新浪面试题】
3)单链表的反转【腾讯面试题,有点难度】
4)从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
5)合并两个有序的单链表,合并之后的链表依然有序【课后练习.】
栈实现综合计算器(中缀表达式)
思路:
迷宫回溯
// 使用递归回溯来给小球找路
// 说明
// 1.map 表示地图
// 2.i,j 表示从地图的哪个位置开始出发(1,1)
// 3.如果小球能到map[6][5] 位置,则说明通路找到。
// 4.约定: 当map[i][j] 为0表示该点没有走过 当为1表示墙;2 表示通路可以走;3表示该点已经走过,但是走不通
// 5.在走迷宫时,需要确认一个策略(方法)下->右->上->左
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;
}if(setWay(map, i, j+1)){
// 向右走
return true;
}if(setWay(map,i-1, j)){
// 向上走
return true;
}if(setWay(map,i, j-1)){
// 向上走
return true;
}else{
// 说明该点走不通,是死路
map[i][j] = 3;
return false;
}
}else {
// 如果map[i][j] != 0 ,可能是 1,2,3
return false;
}
}
}
public static void main(String[] args) {
// 先创建一个二维数组,模拟迷宫
// 地图
int[][] map = new int[8][7];
// 使用1 表示墙
// 上下全部置为1
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
// 左右全部置为1
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
//设置挡板, 1 表示
map[3][1] = 1;
map[3][2] = 1;
// 输出地图
System.out.println("地图的情况");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
// 使用递归给小球找路
setWay(map,1,1);
// 输出新的地图,小球走过,并标识过的地图
System.out.println("输出新的地图,小球走过,并标识过的地图");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
八皇后问题
代码实现
public class Queue8 {
// 定义一个max表示共有多少个皇后
int max = 8;
// 定义数组array, 保存皇后放位置的结果,比如 arr = {0, 4, 5, 2, 6, 1, 3}
int[] array = new int[max];
static int count = 0;
public static void main(String[] args) {
Queue8 queue8 = new Queue8();
queue8.check(0);
System.out.println("一共有【"+count+"】解法");
}
// 编写一个方法,放置第n个皇后
private void check(int n){
if(n == max) { // n = 8,其实8个皇后就已然放好
print();
return;
}
// 依次放入皇后,并判断是否冲突
for (int i = 0; i < max; i++) {
// 先把当前这个皇后 n , 放到改行的第1列
array[n] = i;
// 判断当放置第n个皇后到i列时,是否冲突
if(judge(n)){ // 不冲突
// 接着放n+1个皇后,既开始递归
check(n + 1);
}
// 如果冲突,就继续执行array[n] = i; 既将第n个皇后,放置本行的后移一个位置
}
}
// 查看当我们放置第n个皇后,就去检查该皇后是否和前面已经摆放的皇后冲突
private boolean judge(int n){
for (int i = 0; i < n; i++) {
// 说明
// 1.array[i] == array[n] 表示判断第n个皇后是否和前面的n-1个皇后在同一列
// 2.Math.abs(n-1) == Math.abs(array[n]-array[i]) 表示判断第n个皇后是否和第i个皇后在同一斜线
// 3.判断是否在同一行,没有必要, n每次都在递增
if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i]) ){
return false;
}
}
return true;
}
// 写一个方法,可以将皇后摆放的位置输出
private void print(){
count++;
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
}
}