概述
算法=输入+程序+输出
- 针对某个特定问题的解决方案
- 先有解决问题的主体思路
- 功能分解,逐步求精
算法是有好坏之分的 ,主要使用O进行标识
排序
- 简单排序:冒泡、选择、插入
- 高级排序:希尔、归并、计数、快速、堆
- 查询
- 二分查找法
算法的特征
- 有穷性
- 有效性
- 确定性
- 输入性
- 输出性
算法的基本要求
- 正确性
- 可读性
- 健壮性
- 时间复杂度
- 空间复杂度
数据结构的概述
数据结构:就是在数据中存储的方式
三种简单排序的区别,时间复杂性:O(n^2)
- 冒泡
- 两个临近的数值进行对比,将大的数值放到集合尾部
- 遍历次数多,交换次数多
- 选择
- 从未排序的集合中选择最小或者最大值,放入到已经排序好的集合中
- 遍历次数多,但是交换的次数少
- 插入
- 从未排序的集合中获取一个值,然后从已经排序好了的集合中找到插入的位置进行插入
- 遍历次数少,交换次数多
- 冒泡
高级排序(速度比较快速)
传统的查询算法需要遍历整个集合,时间复杂性O(n)
使用二分查找法之后,时间复杂度O(logN)
二分查找法只要针对有序的集合
- 获取数组的中间值下标
- 将中间值和目标值进行对比
- 判断大小选择左右查找

基本代码
public static void binarySeach(int[] arrs,int target,int start,int end){//1,获取数组的中间值下标int middle = (start+end)/2;//2,将中间值和目标进行比较if(target == arrs[middle]){System.out.println("找到目标,下标是:"+middle);}else if(target > arrs[middle]){//从右边开始找binarySeach(arrs,target,middle+1,end);}else{//从左边开始找binarySeach(arrs,target,start,middle-1);}}
添加递归退出条件


if(end<start){//递归退出条件//没有找到目标System.out.println("没有找到目标");return;}
6. 带返回结果的二分查找法
public static Integer binarySeach(int[] arrs,int target,int start,int end){if(end<start){//递归退出条件//没有找到目标//System.out.println("没有找到目标");return null;}//1,获取数组的中间值下标int middle = (start+end)/2;//2,将中间值和目标进行比较if(target == arrs[middle]){//System.out.println("找到目标,下标是:"+middle);return middle;}else if(target > arrs[middle]){//从右边开始找return binarySeach(arrs,target,middle+1,end);}else{//从左边开始找return binarySeach(arrs,target,start,middle-1);}}
两种物理结构
- 数组
- 是内存中的连续空间,在创建的时候大小是固定的
- 一般编程语言都直接支持
- 通过下标可以直接获取数组相对应的值
- 会有扩容的问题
- 链表
- 在内存中不是连续空间,大小也不是固定的
- 可以通过下标查找数值但是需要从开始链表一个一个查询
- 插入和删除速度很快,没有扩容的问题,但是查询速度慢
链表的基本实现
创建一个节点类
//给链表设计一个节点public class Node<T>{T data;//存储的数据Node next;//指向下一个节点的引用public Node(T data, Node next) {this.data = data;this.next = next;}public T getData() {return data;}public void setData(T data) {this.data = data;}}
实现新增和查询
//新增方法public void add(T t){if(head==null){head = new Node(t,null);}else{Node temp = head;//找到最后一个节点while(temp.next!=null){temp = temp.next;}//将新增的节点放入到最后一个节点的next引用temp.next = new Node(t,null);}size++;}//查询方法public T get(int pos){if(pos>=size){throw new IndexOutOfBoundsException();}int cnt = 0;Node<T> temp = head;//从头部开始//向下移动while(cnt != pos){temp = temp.next;cnt++;}return temp.getData();}
指定位置的新增
public void add(int pos, T t) {//创建新的节点Node newNode = new Node(t, null);if(pos == 0){//在头部插入//先将新的节点的next指向头部newNode.next = head;head = newNode;}else{//当前位置的Node current = getNode(pos-1);//将新节点的下一个位置,执行当前节点的下一个位置newNode.next = current.next;//将当前位置的节点指向新增的节点current.next = newNode;}size++;}
指定位置的删除
//删除指定位置的数据public void remove(int i) {if(i==0){//将头节点直接移动到下一个节点head = head.next;}else{//找到节点Node current = getNode(i);Node preNode = getNode(i-1);//前面的节点,直接指向下后面的节点preNode.next = current.next;}//删除size--;}
栈与队列
栈:先进后出
自定义实现栈
public class MyStack<T> {MyLinkedList<T> datas = new MyLinkedList<>();//压入栈public void push(T t){//放在链表的尾部datas.add(t);}//出栈public T pop(){//获取头部T t = datas.get(datas.size-1);datas.remove(datas.size-1);return t;}}
jdk中自带的Stack类
Stack<String> myStack = new Stack<>();myStack.push("aa");myStack.push("bb");myStack.push("cc");System.out.println(myStack.pop());System.out.println(myStack.pop());
队列:先进先出
是一种非线性结构
- 既有数组查询的效率,又有链表增删的性能
树的基本特点
实现树节点
public class Node<T>{T data;//数据Node<T> left;//左子节点Node<T> right;//右子节点}
创建一颗树
//树的根节点Node root;//创建一颗树public void createTree(){//创建根节点root = new Node(1);//左子节点Node left = new Node(2);root.setLeft(left);//右边子节点Node right= new Node(3);root.setRight(right);//左子左节点Node leftLeft = new Node(4);left.setLeft(leftLeft);//左子右节点Node leftRight = new Node(5);left.setRight(leftRight);//右子左节点Node rightLeft = new Node(6);right.setLeft(rightLeft);//右子右节点Node rightRight = new Node(7);right.setRight(rightRight);}
树的遍历
先序遍历:根节点-》左节点-》右节点
//实现先序遍历public void preTrival(Node node){if(node ==null){return;}//直接输出根节点System.out.print(node.data+",");//遍历左子节点preTrival(node.left);//遍历右子节点preTrival(node.right);}
中序遍历:左节点-》根节点-》右节点
//实现中序遍历public void midTrival(Node node){if(node==null){return;}//遍历左子节点midTrival(node.left);//打印根节点System.out.print(node.data+",");//遍历右子节点midTrival(node.right);}
后序遍历:左节点-》右节点-》根节点
//实现后序遍历public void postTrival(Node node){if(node==null){return;}//遍历左子节点postTrival(node.left);//遍历右子节点postTrival(node.right);//打印根节点System.out.print(node.data+",");}
数组实现树
基本规则
- 使用数组存储
- 只能构造完全二叉树
- 左子节点 = 当前节点下标*2 +1
- 右子节点 = 当前节点下标*2 +2
遍历
//实现先序遍历public void preTrival(int index){if(index>= datas.length){return;}//打印根节点System.out.print(datas[index]+",");//遍历左子节点preTrival(2*index+1);//遍历右子节点preTrival(2*index+2);}//实现先序遍历public void midTrival(int index){if(index>= datas.length){return;}//遍历左子节点midTrival(2*index+1);//打印根节点System.out.print(datas[index]+",");//遍历右子节点midTrival(2*index+2);}
堆排序
树既有链表插入删除的优势,又有数组的查询的效率
- 一般的树是不具备快速查询的能力的,只有二分查找树,可以天然的支持二分查找法,以快速实现数据的查询
- 查找树特点
- 左子树的节点要比右边子树要小
- 在构造查询树的时候,有可能会出现左右失去平衡的问题
- 二叉平衡树(AVL)
- 是一个特殊的查找树
- 在构造树的时候,会通过旋转算法,使得树达到平衡
- 当左子树和右子树的高度差大于1的时候,树就失去了平衡,需要旋转来达到平衡
- 由于平衡失去平衡的规则限制太高,会导致树在旋转的时候,次数增加,从而影响性能
红黑树(RBTree)
二叉查找树的问题,在于数据很多时候,树高度会很高,从而影响的性能
- 多路查找树,不像二叉树之后两子节点,它具备多个子节点,可以有效降低树的高度提高查询效率
多路查找树就是B树,B+树就是在B的基础,将所以的叶子节点通过链表链接起来了
霍夫曼树
是带权重的树
-
哈希表
哈希表的查询速度非常,可以到达O(1)
- 主要原理
- 创建一个Entry用于存储数据的key 和 value值
- 主要是通过hash散列算法,计算出插入的元素所在位置
- 然后根据计算出的问题,将Entry插入到对应数组中
基本实现
public class MyHashTable<K,V> {Entry<K,V>[] elements ;//用于存储Entry的数组public MyHashTable(){//创建Entry的数组elements = new Entry[10];}//计算所在的位置,散列算法public int hash(K k){return k.hashCode()%elements.length;}//插入public void put(K k ,V v){//使用K执行相关散列算法,获取存储在位置int pos = hash(k);//创建Entry放入到指定的位置elements[pos] = new Entry<>(k,v);}//获取public V get(K k){//通过散列算法获取位置int pos = hash(k);//通过位置获取EntryEntry<K,V> entry = elements[pos];return entry.getValue();}//定义一个Entry用于存储key和valuepublic static class Entry<K,V>{K key;V value;public Entry(K key, V value) {this.key = key;this.value = value;}public K getKey() {return key;}public void setKey(K key) {this.key = key;}public V getValue() {return value;}public void setValue(V value) {this.value = value;}}}
散列冲突问题
- 由于数组长度是有限的,在计算entry插入的位置的时候,必然会出现冲突的问题
- 解决方案1:开放寻址法
- 在发生冲突的时候,通过重新计算,或者是扩容的方式,来解决冲突的问题
- 好处可以保证entry是在数组中,方便序列化
- 缺点,会浪费更多空间,而且在重写计算的时候可能会影响效率
- 适合数据量不大的数据
解决方案2:链表式
- 在发生冲突的时候,在冲突的位置上,通过链表添加新的数据
- 好处是插入的时候不需要考虑数组扩容的问题
- 坏处是链表逐步增大,会导致性能问题
将entry转换成链表
public static class Entry<K,V>{K key;V value;Entry<K,V> next;//指向下个节点的引用}
修改插入和查询方法 ```java //插入 public void put(K k ,V v){ //使用K执行相关散列算法,获取存储在位置 int pos = hash(k);
//判断是否发生碰撞 if(elements[pos]==null){ //创建Entry放入到指定的位置 elements[pos] = new Entry<>(k,v); }else{ Entry temp = elements[pos]; //链表的遍历 while(temp.next!=null){
temp = temp.next;
} //插入到链表下一个为null的位置 temp.next = new Entry<>(k,v); }
}//获取public V get(K k){//通过散列算法获取位置int pos = hash(k);//通过位置获取EntryEntry<K,V> entry = elements[pos];//判断Entry的key的值是否一致if(entry.getKey().equals(k)){return entry.getValue();}else{//遍历链表,根据key判断while(entry.next!=null){entry = entry.next;//判断key值是否相同if(entry.getKey().equals(k)){return entry.getValue();}}}return null;}
5. JDK中的哈希表1. 在jdk早期哈希表主要是Hashtable1. 之后为了提高效率,jdk提供HashMap1. 没有使用锁了1. 可以对空值进行处理1. 提高的了hash算法的效率1. 在处理哈希冲突的时候,也使用链表法,但是在链表数量超过8个,会将链表转换成红黑树<a name="SVhKX"></a>## 图1. 图是一种网状结构,和树结构最大区别在于图可以有多个父类节点1. 图主要由顶点 (Vertex) 和边 (Edge) 构成1. 边是顶点和顶点之间的连线1. 边可以是有向的, 也可以是无向的.(比如A --- B, 通常表示无向. A --> B, 通常表示有向)21. 图可以带权重,例如A->B花费的时间1. 路径是一个顶点到另一个顶点所经过的边的序列2. 通过二维数组定义图```javapublic class MyGraph {//存储顶点Vertex[] vertices;//需要一个矩阵用于存储边int[][] adjMat;int verNum;//顶点的数量public MyGraph(int num){//初始化数组存储顶点vertices = new Vertex[num];//初始化边adjMat = new int[num][num];verNum=0;}//添加顶点public void addVertex(String label){//创建订单对象Vertex vertex = new Vertex(label);vertices[verNum++] = vertex;}//添加边public void addEdge(String start,String end){int sIndex =0;//start这个顶点的坐标int eIndex =0;//遍历顶点for (int i=0;i<vertices.length;i++){if(vertices[i].label.equals(start)){//第一个顶点的坐标sIndex =i;}//找到第二个顶点的坐标if(vertices[i].label.equals(end)){eIndex =i;}}//定义边adjMat[sIndex][eIndex] = 1;adjMat[eIndex][sIndex] = 1;}//定义顶点类public static class Vertex{String label;//存储顶点的标签public Vertex(String label){this.label= label;}}}
两种遍历方式
深度优先遍历:其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次(可以栈来实现)
Stack stack = new Stack<>();//当前值已经被遍历了vertices[pos].isVisited = true;int current = pos;//将位置入栈stack.push(pos);System.out.println(vertices[pos].label);out:while(!stack.empty()) {for (int i=0;i< vertices.length; i++) {if(adjMat[current][i]==1&&vertices[i].isVisited==false) {//入栈stack.push(i);vertices[i].isVisited= true;current = i;System.out.println(vertices[current].label);continue out;}}//没有找到可以连接的位置的时候,那么就出栈stack.pop();//出栈后要重新设置栈顶的位置if(!stack.empty()) {current = (int) stack.peek();}}
广度优先遍历:其过程检验来说是对每一层节点依次访问,访问完一层进入下一层,而且每个节点只能访问一次(队列实现)
//广度优先遍历public void bfs(int pos) {//创建队列Queue queue = new LinkedList<>();queue.add(pos);//入队列System.out.println(vertices[pos].label);//打印vertices[pos].isVisited = true;int current = pos;while(!queue.isEmpty()) {current = (int) queue.poll();//出列for (int i=0;i< vertices.length; i++) {if(adjMat[current][i]==1&&vertices[i].isVisited==false) {//入队列queue.add(i);//入队列System.out.println(vertices[i].label);//打印vertices[i].isVisited = true;}}}}

