数据的存储结构:顺序存储(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;
}
}
}