算法--线性结构
数据的存储结构:顺序存储(ArrayList)、链式存储(LinkList)。
数据的逻辑结构:集合结构、线性结构、树形结构、图形结构。
二、算法
算法:解决问题的方法。
算法的特性:输入、输出、有穷、确定性、可行性。
算法的基本要求:正确性、可读性、健壮性、时间复杂度、空间复杂度。
三、线性结构
1、数组
普通数组:类似Java基本类型数组
对象数组:类似ArrayList在对象里面定义一个数组类型的成员变量。
数组中的算法:线性查找和二分法查找。
//线性查找
public static void queryByIndex(int[] arr){
int target = 4;
int index = -1;
for(int i=0;i<arr.length;i++){
if(target == arr[i]){
index = i;
break;
}
}
System.out.println(index);
}
/** * 二分法查找,前提是数组的内容是有序的 * @param arr 数组 */ public static void queryByMid(int[] arr){ int target = 4; int index = -1; int begin = 0; int end = arr.length - 1; int mid; while(true){ mid = (begin+end)/2; if(target == arr[mid]){ index = mid; break; }else if(target < arr[mid]){ end = mid - 1; }else{ begin = mid + 1; } //不加这行会死循环 if(begin >= end){ break; } } System.out.println(index); }
2、栈
特点:先入后出,可以使用数组对象模拟。
class Stack{ int[] elements = new int[0]; /** * 数组越界空指针异常不考虑 * @param element */ public void push(int element){ int[] arr = new int[elements.length + 1]; System.arraycopy(elements,0,arr,0,elements.length); arr[arr.length -1] = element; elements = arr; } /** * 数组越界空指针异常不考虑 * @return */ public int pop(){ int[] arr = new int[elements.length - 1]; int len = elements.length; int value = elements[len -1]; System.arraycopy(elements,0,arr,0,arr.length); elements = arr; return value; } public static void main(String[] args){ Stack stack = new Stack(); stack.push(1); stack.push(2); stack.push(3); stack.push(4); System.out.println(stack.pop()); System.out.println(stack.pop()); } }
3、队列
特点:先进先出,可以使用数组对象模拟。
class Queue{ int[] elements = new int[0]; /** * 数组越界空指针异常不考虑 * @param element */ public void push(int element){ int[] arr = new int[elements.length + 1]; System.arraycopy(elements,0,arr,0,elements.length); arr[arr.length -1] = element; elements = arr; } /** * 数组越界空指针异常不考虑 * @return */ public int pop(){ int[] arr = new int[elements.length - 1]; int value = elements[0]; System.arraycopy(elements,1,arr,0,arr.length); elements = arr; return value; } public static void main(String[] args){ Queue queue = new Queue(); queue.push(2); queue.push(3); queue.push(4); System.out.println(queue.pop()); System.out.println(queue.pop()); System.out.println(queue.elements.length); } }
4、单向链表
特点:一个节点除存储数据以外,还需存储下一个节点的指向。
class Node{ //节点数据 int data; /** * java对象在内存中是地址,可以理解下个节点的地址 */ Node next; public Node(int data){ this.data = data; } public Node append(Node node){ Node currentNode = this; while(true){ Node nextNode = currentNode.next; if(null == nextNode){ break; } currentNode = nextNode; } //当前节点是可变的,因为循环中重新赋值了 currentNode.next = node; return this; } public Node next(){ return this.next; } public int getData(){ return this.data; } public boolean isLast(){ if(null == this.next){ return true; } return false; } /** * 移除也只能移除当前节点的下个节点 */ public void removeNext(){ Node deleteNode = this.next; Node newNext = deleteNode.next(); this.next = newNext; } /** * 只能插入当前节点后面 * @param node */ public void inset(Node node){ Node next = this.next; this.next = node; node.next = next; } public static void main(String[] args) { Node node =new Node(1); node.append(new Node(2)).append(new Node(3)); System.out.println(node.next().getData()); } }
5、单项循环链表
特点:和单链表相比,尾节点的下个节点指向首节点,构成一个单向循环。
class Node{
//节点数据
int data;
//java对象在内存中是地址,可以理解下个节点的地址
Node next = this;
public Node(int data){
this.data = data;
}
public Node next(){
return this.next;
}
public int getData(){
return this.data;
}
/**
* 移除也只能移除当前节点的下个节点
*/
public void removeNext(){
Node deleteNode = this.next;
Node newNext = deleteNode.next();
this.next = newNext;
}
/**
* 只能插入当前节点后面
* @param node
*/
public void inset(Node node){
Node next = this.next;
this.next = node;
node.next = next;
}
public static void main(String[] args) {
Node node =new Node(1);
Node node2 =new Node(2);
Node node3 =new Node(3);
System.out.println(node.next().getData());
node.inset(node2);
node.inset(node3);
System.out.println(node.next().getData());
System.out.println(node3.next().getData());
}
}
6、双向循环链表
特点:由数据、前一个节点和下个节点做成
class DoubleNode{ DoubleNode pre = this; DoubleNode next = this; int data; public DoubleNode(int data){ this.data = data; } /** * 往后增加节点 * @param node */ public void after(DoubleNode node){ //当前节点的下个节点 DoubleNode oldNext = this.next; //新增的节点 this.next = node; node.pre = this; node.next = oldNext; oldNext.pre = node; } }
7、递归
特点:在一个方法或者函数内部调用该方法,记得留出口,不然会栈溢出(StackOverflowError)。
/** * 斐波那契数列 1 1 2 3 5 8 ... */ public static int test(int i) { if (1 == i || 2 == i) { return 1; } return test(i - 1) + test(i - 2); }
汉诺塔
/** * 汉诺塔 * 无论有多少盘子,都认为只有两个,上面的所有盘子和下面一个盘子。 * * @param n 共n个盘子 * @param from 开始位置 * @param in 中间位置 * @param to 目标位置 */ public static void test2(int n, char from, char in, char to) { if (1 == n) { System.out.println("第1个盘子从" + from + "移动到 " + to); } else { //移动上面的盘子到中间位置 test2(n - 1, from, to, in); System.out.println("第" + n + "盘子从" + from + "移动到 " + to); //把上面的所有盘子从中间位置移动到目标位置 test2(n - 1, in, from, to); } }
四、算法
特点:时间复杂度、空间复杂度。
常见的时间复杂度
常数阶O(1)、对数阶O(log2n)、线性阶O(n)、线性对数阶O(nlog2n)
平方阶O(n^2)、立方阶O(n^3)、k次方阶O(n^k)、指数阶O(2n)
随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。
计算时间复杂度:用常数1代替运行时间中所有加法常数,修改后的运行次数函数中,只保留高阶项,去除最高阶项的系数。列如T(n) = n^2+5n+6与T(n)=3n^2+3n+2这两个函数,他们的复杂度都是O(n^2)。
平均时间复杂度:是指所有可能的输入实列均以等概率出现的情况下,该算法的运行时间。
最坏时间复杂度:是算法在任何输入实列上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。
八种常用排序算法
交换排序:冒泡排序和快速排序
冒泡排序
/** * 就像水中的泡泡每次都是最大的先出水面 * @param arr */ public static void bubbleSort(int[] arr){ int swap = arr[0]; //控制比较多少轮 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]){ swap = arr[j]; arr[j] = arr[j + 1]; arr[j+1] = swap; } } } }
快速排序 思想类似递归
public static void quickSort(int[] arr,int low,int high){ int i,j,temp,t; if(low>high){ return; } i=low; j=high; //temp就是基准位 temp = arr[low]; while (i<j) { //先看右边,依次往左递减 while (temp<=arr[j]&&i<j) { j--; } //再看左边,依次往右递增 while (temp>=arr[i]&&i<j) { i++; } //如果满足条件则交换 if (i<j) { t = arr[j]; arr[j] = arr[i]; arr[i] = t; } } //最后将基准为与i和j相等位置的数字交换 arr[low] = arr[i]; arr[i] = temp; //递归调用左半数组 quickSort(arr, low, j-1); //递归调用右半数组 quickSort(arr, j+1, high); } public static void main(String[] args) { int[] arr = new int[]{3,2,6,4,2,1}; Test.quickSort(arr,0,arr.length-1); Arrays.stream(arr).forEach(item -> System.out.println(item)); }
插入排序:直接插入排序和希尔排序
//插入排序,下标前面的数据都是有序的 public static void insertSort(int[] arr){ //编列所有数字 for(int i=1;i<arr.length;i++){ //如果当前数字比前一个数字小 if(arr[i]<arr[i-1]){ int temp = arr[i]; int j; //遍历当前数字前面的所有数字 for(j = i-1;j>=0 && temp<arr[j];j--){ //把前一个数字赋值给后一个数字 arr[j+1] = arr[j]; } 把临时变量(外层for循环的当前元素)赋给不满足条件的后一个元素 arr[j+1] = temp; } } } //希尔排序 最要概念是步长 public static void shellSort(int[] arr){ int k=1; //编列所有的步长 for(int d = arr.length/2;d > 0;d/=2){ //遍历所有元素 for(int i=d;i<arr.length;i++){ //编列本组中所有元素 for(int j = i-d;j>=0;j-=d){ if(arr[j]>arr[j+d]){ int temp = arr[j]; arr[j] = arr[j+d]; arr[j+d] = temp; } } } System.out.println(k+Arrays.toString(arr)); k++; } }
选择排序:简单选择排序
//选择排序 每次循环记录最小下标 public static void selectSort(int[] arr){ //控制比较多少轮 for(int i= 0;i < arr.length -1;i++){ int minIndex=i; //把当前遍历的数和后面所有的数依次比较,并记录最小的下标 for(int j = i+1;j < arr.length-i;j++){ if(arr[minIndex] > arr[j]){ //记录下最小的下标数 minIndex = j; } } //交换位置 if(i!=minIndex){ int temp = arr[i]; arr[i]=arr[minIndex]; arr[minIndex]=arr[i]; } } } //归并排序 public static void merge(int[] arr,int low,int middle,int hight){ //用于存储归并后的临时数组 int[] temp = new int[hight-low+1]; //记录第一个数组中需要遍历的下标 int i = low; //记录第二个数组中需要遍历的下标 int j = middle+1; //用于记录在临时数组中存放的下标 int index = 0; //遍历两个数组取出小的数字,放入临时数组 while(i<=middle&&j<=hight){ //第一个数组的数据更小 if(arr[i]<=arr[j]){ //把小的数据放入临时数组中 temp[index] = arr[i]; //让下标向后移动一位 i++; }else{ temp[index] = arr[j]; j++; } index++; } //处理多余的数据 while(j<=hight){ temp[index] = arr[i]; j++; index++; } while(i<-middle){ temp[index]=arr[i]; i++; index++; } //把临时数组中的数组重新存入原数组 for(int k=0;k<temp.length;k++){ arr[k+low] = temp[k]; } }
基数排序
//基数排序,可支持队列 public static void radixSort(int[] arr){ int max = Integer.MIN_VALUE; for(int i=0;i<arr.length;i++){ if(arr[i]>max){ max = arr[i]; } } int maxLength = (max+"").length(); //用于临时存储数据的二维数组 int[][] temp = new int[10][arr.length]; int[] count = new int[10]; for(int i=0;i<maxLength;i++,n*=10){ for(in j=0;j<arr.length;j++){ //计算余数 int ys = arr[j]/n%10; temp[ys][count[ys]] = arr[j]; //记录数量 count[ys]++; } //记录取的元素需要放的位置 int index=0; //把数字取出 for(int k=0;k<count.length;k++){ if(count[k]!=0){ for(int l=0;l<count[k];l++){ arr[index++] = temp[k][l]; } } count[k] = 0; } } }