目录
1、概述
1.1、什么是数据结构
数据与数据之间的关系
1.2、数据的存储结构
顺序存储、链式存储
1.3、数据的逻辑结构
集合、线性、树形、图形
数据的逻辑结构:表示数据之间的关系
数据的存储结构:数据的逻辑结构在计算机内存中的实现
数据的运算(算法):对数据的一系列操作就是算法
1.4、什么是算法
解决问题的思路
1.5、算法的特性
输入、输出、有穷性、确定性、可行性
1.6、算法的基本要求
正确性、可读性、健壮性、时间复杂度、空间复杂度
2、线性结构
数组、栈、队列、单链表、循环链表、双链表、递归、排序算法
数组、栈、队列问题:插入元素比较麻烦
链表问题:查找元素比较麻烦
2.1、栈
先进后出、数组实现
应用场景:1、对字符串返序输出
2、像xml中分隔符<>、[]、{}......这些分隔符个数是否匹配、是否嵌套交叉
ArrayDeque虽然是双端队列,但是如果用push方法、pop方法就是栈(先进后出),
push方法实际调用的是addFirst方法,pop方法实际调用的是removeFirst方法。
2.2、队列
先进先出、数组实现
2.2.1、单向队列
只能在一端插入数据、另一端删除数据
ArrayDeque虽然是双端队列,但是如果用add方法、remove方法就是单向队列(先进先出),
add方法实际调用的是addLast方法,remove方法实际调用的是removeFirst方法。
2.2.2、双端队列
每一端都可以进行插入数据和删除数据:ArrayDeque
2.2.3、优先级队列
数据项按照关键字进行排序,关键字的数据项往往在队列的最前面
2.3、单链表
节点只有next,没有prev
如果只有一个对象,next指向自己(this)
2.4、循环链表
没有末尾,因为末尾的node的next指向第一个,形成了一个环形
2.5、双链表
节点有next、prev
如果只有一个对象,next、prev都指向自己(this)
2.6、递归
斐波那契数列:后一项等于前两项之和。eg:1 1 2 3 5 8 13......
汉罗塔问题:
/**
* 汉罗塔问题
* @createTime 2020年04月08日 16:37.
*/
public class TestHanRota {
public static void main(String[] args) {
hanRota(2,'A','B','C');
}
/**
* 无论有多少个盘子,都认为只有两个。
* 上面的所有盘子和最下面一个盘子
* @param n 盘子的总数
* @param from 开始的柱子
* @param in 中间柱子
* @param to 目标柱子
*/
public static void hanRota(int n,char from,char in,char to){
if(n==1){
System.out.println("第1个盘子从"+from+"移动"+to);
}else {
//移动上面所有的盘子到中间位置
hanRota(n-1,from,to,in);
//移动最下面的一个盘子
System.out.println("第"+n+"个盘子从"+from+"移动"+to);
//把上面的所有盘子从中间位置移动到目标位置
hanRota(n-1,in,from,to);
}
}
}
3、常见的时间复杂度
1、常数阶:O(1)
2、对数阶:O(log2 n)
3、线性阶:O(n)
4、线性对数阶:O(n log2 n)
5、平方阶:O(n²)
6、立方阶:O(n³)
7、k次方阶:O(n^k)
7、指数阶:O(2^n)
8、阶乘阶:O(n!)
随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。
3.1、算术表达式
3.1.1、前缀表达式
操作符在操作数的前面(是一种没有括号的算术表达式)
eg:-1+23,等价于1-(2+3)
3.1.2、中缀表达式
操作符在操作数的中间(人类最容易识别的算术表达式)
eg:3+4-5
3.1.3、后缀表达式
操作符在操作数的后面(是一种没有括号的算术表达式)
eg:34+5-,等价于(3+4)-5
3.2、中缀表达式转换为后缀表达式
1、初始化两个栈,运算符栈s1,中间结果栈s2
2、从左到右扫描表达式
3、遇到操作数压入s2
4、遇到运算符,比较其与s1栈顶运算符的优先级
第一步、如果s1栈不是空的,并且栈顶元素不是左括号(,并且优先级比栈顶元素低或相等,就弹出s1栈顶的元素,压入s2栈,递归这一步
第二步、然后将运算符压入s1栈
5、遇到括号
a、遇到左括号(,直接压入s1栈
b、遇到右括号),依次弹出s1栈顶的元素,压入s2栈,直到遇到左括号为止,然后将这对括号丢掉
6、重复2~5,扫描完所有表达式
7、将s1中剩余的运算符压入s2栈
package test.expression;
import java.util.ArrayDeque;
import java.util.Stack;
/**
* 中缀表达式转换为后缀表达式
*
* @createTime 2020年04月14日 9:43.
*/
public class InfixToSuffixTest {
public static void main(String[] args) {
String s = "(34.5+4)*2+59*9+24/6+3";
System.out.println("中缀表达式:");
System.out.println(s);
ArrayDeque<String> s2 = infixToSuffix(s);
System.out.println("后缀表达式:");
ArrayDeque<String> s3 = new ArrayDeque<>();
while (!s2.isEmpty()) {
System.out.print(s2.peek() + " ");
s3.add(s2.remove());
}
}
public static ArrayDeque<String> infixToSuffix(String exp) {
Stack<Character> s1 = new Stack<>();
ArrayDeque<String> s2 = new ArrayDeque<>();
char c;
int lastIndex;
for (int i = 0; i < exp.length(); i++) {
if (Character.isDigit(exp.charAt(i))) {//如果是数字
lastIndex = getLastIndex(exp, i);
s2.add(exp.substring(i, lastIndex));
i = lastIndex - 1;
} else if (isOperator(exp.charAt(i))) {
while (!s1.isEmpty() && '(' != s1.peek() && operatorCompare(exp.charAt(i), s1.peek()) <= 0) {
if (isOperator(s1.peek())) {
s2.add(s1.pop().toString());
} else {
s1.pop();
}
}
s1.push(exp.charAt(i));
} else if (exp.charAt(i) == '(') {
s1.push(exp.charAt(i));
} else if (exp.charAt(i) == ')') {
while (s1.peek() != '(') {
s2.add(s1.pop().toString());
}
if (s1.peek() == '(') {
s1.pop();//移除左括号
}
} else {
//其他情况忽略
}
}
while (!s1.isEmpty()) {
s2.add(s1.pop().toString());
}
return s2;
}
public static int operatorCompare(char op1, char op2) {
int r = -1;
if (op1 == '+' || op1 == '-') {
r = (op2 == '*' || op2 == '/') ? -1 : 0;
} else if (op2 == '+' || op2 == '-') {
r = (op1 == '*' || op1 == '/') ? 1 : 0;
}
return r;
}
/**
* 判断是否是操作符号
*
* @param c
* @return
*/
public static boolean isOperator(char c) {
return (c == '+' || c == '-' || c == '*' || c == '/') ? true : false;
}
/**
* 获取操作数后面的符号对应在表达式中的索引值
*
* @param exp
* @param start
* @return
*/
public static int getLastIndex(String exp, int start) {
int rIndex = start;
for (int i = start; i < exp.length(); i++) {
if (exp.charAt(i) == '.') {
continue;
} else if (!Character.isDigit(exp.charAt(i))) {
rIndex = i;
break;
}
}
if (rIndex == start) {
rIndex = exp.length();//当前操作数是最后一个数,后面就没有符号
}
return rIndex;
}
private static double calculation(double num1, double num2, char a) {
switch (a) {
case '+':
System.out.print("计算:" + num1 + "+" + num2 + "=");
num2 = num1 + num2;
System.out.println(num2);
break;
case '-':
System.out.print("计算:" + num1 + "-" + num2 + "=");
num2 = num1 - num2;
System.out.println(num2);
break;
case '*':
System.out.print("计算:" + num1 + "*" + num2 + "=");
num2 = num1 * num2;
System.out.println(num2);
break;
case '/':
System.out.print("计算:" + num1 + "/" + num2 + "=");
num2 = num1 / num2;
System.out.println(num2);
break;
}
return num2;
}
}
3.3、抽象数据类型ADT
ADT:Abstract Data Type(抽象数据类型)
抽象:是抽取事物具有的普遍性本质,是对事物的一个概括,是一种思考问题的方式。
抽象数据类型:是指一个数学模型及定义在该模型上的一组操作。
4、排序算法
资料显示,在数据量小于20时,插入排序具有最好的性能。当大于20时,快速排序具有最好的性能。
4.1、交换排序
4.1.1、冒泡排序
int[] arr = {6, 3, 9, 55, 4, 7, 2};
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
4.1.2、快速排序
package com.study.test;
import java.util.Arrays;
/**
* 快速排序
* Created by Administrator on 2020/4/8 0008.
*/
public class TestQuick {
public static void main(String[] args){
int[] arr={0,2,4,1,3,7,9,6,5,10,8};
quickSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr,int start,int end){
if(start<end){
//把数组中的第0个【开始位置的】元素作为标准数
int stard = arr[start];
//记录需要排序的下标
int low=start;
int high=end;
while (low<high){//找比stard大的数、比stard小的数
while (low<high && stard<=arr[high]){//右边的数字比stard大
high--;
}
arr[low]=arr[high];//用右边的数替换左边的数
while (low<high && stard>=arr[low]){//左边的数比stard小
low++;
}
arr[high]=arr[low];//用左边的数替换右边的数
}
arr[low]=stard;//把stard放入数组【这个时候low==high】
//对比stard小的再排序
quickSort(arr,start,low-1);
//对比stard大的再排序
quickSort(arr,low+1,end);
}
}
}
4.2、插入排序
4.2.1、直接插入排序
package com.study.test;
import java.util.Arrays;
/**
* 直接插入排序
* Created by Administrator on 2020/4/8 0008.
*/
public class TestInsertSort {
public static void main(String[] args){
int[] arr={0,2,4,1,3,7,9,6,5,10,8,99,18,28};
insertSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void insertSort(int[] arr){
//从第二个元素开始遍历
for(int i=1;i<arr.length;i++){
if(arr[i-1]>arr[i]){//如果当前数字比前一个数字小
int temp =arr[i];//把当前数字缓存起来
int j;
//遍历当前数字前面的所有数字
for (j=i-1;j>=0&&temp<arr[j];j--){
arr[j+1]=arr[j];//把前一个数字赋给后一个数字
}
arr[j+1]=temp;
}
}
}
}
4.2.2、希尔排序
遍历的步长(间隔)更高效的取值方式一:是用2.2来整除每个间隔。
eg:n=100的数组,步长依次是45、20、9、4、1,这比用2整除改善排序效果显著
遍历的步长(间隔)更高效的取值方式二:在不小于n/3下间隔序列step*3+1,每次循环后间隔(step-1)/3。
逻辑代码如下:
int step=1;
while (step<=arr.length/3){
step=step*3+1;//找到这个数组的最大间隔
}
//然后用最大步长进行数组遍历
//每一次遍历完后,step=(step-1)/3
while (step>0){
//.....逻辑比较代码
step=(step-1)/3;
}
总之,无论是什么间隔序列,间隔最后一定要等于1,也就是最后一趟排序一定是简单的插入排序。
import java.util.Arrays;
/**
* 希尔排序
*
* @createTime 2020年04月16日 10:35.
*/
public class ShellSortTest {
public static void main(String[] args) {
int[] arr = {23, 3, 199, 2, 34, 5, 8, -12, 1, 5, 4, 4, 99, 2, 6, 44, 3};//
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void shellSort(int[] arr) {
int inner;
int temp;
//遍历所有的步长
double tempStep = arr.length / 2.2;
int step;
if (tempStep < 1) {
step = 1;//保证“最后一定”有一轮间隔为1的排序
} else {
step = (int) tempStep;
}
while (step > 0) {
for (int outer = step; outer < arr.length; outer++) {
//遍历本组中所有的元素
inner = outer;
temp = arr[inner];
while (inner - step >= 0 && temp < arr[inner - step]) {
//inner-step==0:表示已经是第一个元素了
//temp<arr[inner-step]当前位置元素小于当前位置-步长位置的元素,就将大的元素往后移
arr[inner] = arr[inner - step];
inner = inner - step;//位置往前移
}
arr[inner] = temp;//将小的元素放在最前面的位置
}
System.out.println("间隔数是" + step + "时的排序:" + Arrays.toString(arr));
//计算下一个间隔
tempStep = step / 2.2;
if (step != 1 && tempStep < 1) {
step = 1;//保证“最后一定”有一轮间隔为1的排序
} else {
step = (int) tempStep;
}
}
}
}
4.3、选择排序
4.3.1、简单选择排序
import java.util.Arrays;
/**
* 简单选择排序
*
* @createTime 2020年04月09日 10:17.
*/
public class SelectSortTest {
public static void main(String[] args) {
int[] arr = {3, 199, 2, 34, 5, 8, -12, 1, 4, 99};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void selectSort(int[] arr) {
//遍历所有的数
for (int i = 0; i < arr.length; i++) {
int minIndex = i;//假设i位置是最小的数
//把i位置后面的所有数依次进行比较
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
//如果比当前的最小数还小,记录下这个位置
minIndex = j;
}
}
if (minIndex != i) {//如果最小数位置不是i,就交换
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
}
4.3.2、堆排序
4.4、归并排序
归并算法的中心是:归并两个已经有序的数组。
eg:归并两个有序数组A、B,就生成了第三个有序数组C。数组C包含数组A和B的所有数据项。
4.5、基数排序
5、树
https://blog.csdn.net/pyl574069214/article/details/105556667
6、堆
https://blog.csdn.net/pyl574069214/article/details/105763965
7、图
https://blog.csdn.net/pyl574069214/article/details/105491670