Files
hexo_blog/source/_posts/Java/数据结构和算法.md
T
2022-04-29 16:16:54 +08:00

14 KiB
Raw Blame History

title, author, top, cover, toc, mathjax, summary, tags, categories, reprintPolicy, abbrlink, date, coverImg, img, password
title author top cover toc mathjax summary tags categories reprintPolicy abbrlink date coverImg img password
数据结构和算法-未完成 TianZD true true true false 基于Java的数据结构和算法,未完成,粗略学了一下,没有参考价值
数据结构
算法
数据结构和算法
cc_by b1f89a38 2022-04-29 10:42:56

[TOC]

概述:学习《漫画算法:小灰的算法之旅》

1、数据结构基础

1.1、数据结构分类

数据结构是以某种特定的布局方式可存储数据的容器,布局方式决定了数据结构对于某些操作是高效的,对于其他操作是低效的。

数据结构 = 逻辑结构+物理结构(顺序、链式、索引、散列)

  • 逻辑结构

    • 线性结构:顺序表、栈、队列
    • 非线性结构:树、图
  • 物理结构:(存储结构),在计算机存储器中的存储形式

    • 顺序存储结构:数组
    • 链式存储结构:链表

逻辑分类:

  1. 线性结构:结构中的元素存在一对一的相互关系(元素之间有序,一个挨一个),常见的有:线性表、栈、队列、串(一维数组);
  2. 树形结构:元素存在一对多的相互关系;常见的有:二叉树、红黑树、B树、哈夫曼树等;
  3. 图形结构:元素存在多对多的关系;常见:有向图、无向图、简单图;

1.2、数组

概述

  • 有限个相同类型的变量组成的有序集合
  • 物理结构:顺序存储结构,在内存中顺序存储
  • 逻辑结构:线性结构

基本操作

读取元素

可以直接通过下标进行随机读取,时间复杂度O(1)

更新元素

通过下标直接进行赋值替换,时间复杂度O(1)

插入元素

涉及到元素的移动,时间复杂度O(n)

  • 尾部插入

  • 中间插入

  • 超范围插入(扩容)

删除元素

和插入相反,时间复杂度O(n)

优劣势

优点:

  • 数据访问:可以通过下标进行,效率高

缺点:

  • 插入删除:在内存中采用顺序存储,插入和删除导致大量元素移动,效率低

总结:适用于读操作多、写操作少的场景

1.3、链表

概述

  • 分为单向链表和双向链表
  • 物理上非连续、非顺序,在内存中随机存储
  • 由若干节点node组成
  • 物理结构:链式存储结构
  • 逻辑结构:线性结构

单向链表

每一个node包含两个属性:

  • data:存放数据的变量
  • node节点指针next:指向下一个节点
private static class Node{
    int data;
    Node next;
}

单向链表只能通过next指针找到关联的下一个节点,逐一寻找,只需要知道第一个节点,便可以根据找到所有的节点,因此用第一个节点标识链表,单向链表的第一个节点又称为头节点

头尾节点:

  • 头节点Head:链表的第一个节点
  • 尾节点:链表的最后一个节点,next指针指向空,null

双向链表

双向链表每一个node包含三个属性:

  • data
  • node节点指针next
  • node节点指针prev:指向前一个节点

基本操作

查找节点

从头节点开始,向后逐一查找,时间复杂度O(n)

更新节点

找到后直接将node节点数据进行替换,查找时间复杂度O(n),替换复杂度O(1)

插入节点

如果不考虑查找元素的过程,时间复杂度O(1)

  • 尾部插入

将尾节点的next指针指向新节点

  • 头部插入

1.把新节点的next指向原来的头节点

2.把新节点变为头节点

  • 中间插入

1.将新节点的next指向插入位置的节点

2.将插入位置前的节点的next指向新节点

删除元素

如果不考虑查找元素的过程,时间复杂度O(1)

  • 尾部删除

把倒数第二个node的next指向空

  • 头部删除

把链表的头节点设为原来的第二个节点

  • 中间删除

把要删除节点的前一个节点的next指向后一个节点

优劣势

优势:

  • 在于能够灵活地进行插入和删除操作

劣势:

  • 查找需要从头部开始逐一遍历

1.4、栈

概述

  • 逻辑结构:线性存储结构
  • 物理结构:可以用数组实现也可以用链表
  • 先入后出FILO
    • 栈顶:最后进入元素的位置
    • 栈底:最先进入元素的位置

基本操作

入栈push

把新元素放入栈中,只能从栈顶放入,作为新的栈顶

时间复杂度O(1)

出栈pop

把元素弹出,只能弹出栈顶元素

时间复杂度O(1)

应用

输出顺序和输入顺序相反,通常用于对“历史”的回溯

  • 代替递归
  • 面包屑导航(浏览页面)

1.5、队列

概述

  • 逻辑结构:线性存储结构
  • 物理结构:可以用数组实现也可以用链表
  • 先入先出FIFO
    • 对头front
    • 对尾rear

基本操作

入队enqueue

把新元素放入队尾

时间复杂度O(1)

出队dequeue

把对头元素移出队列

时间复杂度O(1)

循环队列

数组实现的队列为了防止出队时,对头左边的空间失去作用,可以采用循环队列的方式维持队列容量的恒定

  • 入队:
    • 队列满:(rear+1)%array.length==front
    • rear=(rear+1)%array.length;
  • 出队:
    • 队列空:rear==front
    • front=(front+1)%array.length;

应用

输出顺序和输入顺序相同,用于对历史的回放

  • 多线程中争夺锁
  • 网络爬虫抓取

1.6、散列表(哈希表)

概述

  • 散列表提供了键(key 和**值(value)**的映射关系,可以根据key快速找到对应的value,时间复杂度接近O(1)
  • 本质上是数组
  • 通过哈希函数将key转换为数组中的下标

哈希函数

hashcode

在java等面向对象的语言中,每一个对象都有自己的hashcode,hashcode是区分不同对象的重要标识,不论对象的类型是什么,hashcode都是一个整型变量

转化

通常采用hashcode对数组长度的取模运算

index = HashCode(key) % Array.length

java中的哈希函数采用的是位运算的方式,将数组长度取为2的次方,提高性能。

基本操作

写操作

  1. 通过哈希函数,将key转化为数组下标
  2. 放入元素
    1. 如果对应的下标没有元素,直接放入
    2. 如果已有元素,产生哈希冲突

哈希冲突解决:

  • 开放寻址法:已有元素时,index++,放入下一个,直至对应的下标没有元素占用,ThreadLocal采用
  • 链表法HashMap采用,其数组中的每一个元素对应链表的一个节点,最先放入的为头节点,后续放入的通过将上一个节点的next指向该元素,插入到链表中

读操作

  1. 通过哈希函数,将key转化为数组下标
  2. 找到对应下标对应的元素
    1. 如果只有一个,则找到
    2. 如果对应的链表,遍历链表,直到key值对应

扩容

当多次插入后,key映射位置发生冲突的概率提高,大量元素拥挤在相同的位置,形成链表很长,导致查询和写入效率低下

条件:

HashMap.Size >= Capacity * LoadFactor

步骤

  • 扩容:新建一个空数组,长度为两倍
  • 重新Hash:数组长度变化,需要重新根据哈希函数计算哈希值

1.7、树和二叉树

概述

  • 逻辑结构:非线性结构

二叉树

二叉树应用

查找

维持相对顺序

二叉树遍历

从节点位置:

  • 前序遍历
  • 中序遍历
  • 后序遍历
  • 层序遍历

从宏观角度:

  • 深度优先遍历:前序、中序、后序
  • 广度优先遍历:层序

深度优先遍历

广度优先遍历

1.8、二叉堆

概述

  • 最大堆
  • 最小堆

自我调整

插入

删除

构建

代码

1.9、优先队列

概述

实现

2、排序算法

2.1、分类

2.2、冒泡排序

基本冒泡排序

交换排序,两两比较,两层循环,第一层为遍历的轮数:array.length-1

时间复杂度O(n^2)

优化冒泡排序

第一轮

防止数列已经有序后仍然进行下一轮排序

如{58639217}

第二轮

现有逻辑:从for(int j=0;j<array.length-1;i++)可以看出有序区的长度为以进行排序的轮数i

当后面大部分已经有序后,仍然会进行比较,对此进行优化

如{34215678}

鸡尾酒排序

双向交换,奇数轮从左向右,偶数轮从右向左

解决大部分已经有序的情况,如{2,3,4,5,6,7,8,1}

2.3、快速排序

概述

核心:分治思想

选定基准元素,大的放右边,小的放左边,一分为2

时间复杂度O(nlogn)

基准元素选择

  • 默认数列的第一个元素
  • 可以随机选择一个元素作为基准,然后和首元素交换位置

元素交换

双边循环法

  • 基准元素pivot
  • 左指针left
  • 右指针right

单边循环法

  • 基准元素pivot
  • 元素区域边界mark

递归、非递归实现

2.4、堆排序

2.5、计数排序和桶排序

2.6、排序算法复杂度分析

排序算法 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定
冒泡排序 O(n^2) O(n^2) O(1) 稳定
鸡尾酒排序 O(n^2) O(n^2) O(1) 稳定
快速排序 O(nlogn) O(n^2) O(logn) 不稳定
堆排序 O(nlogn) O(nlogn) O(1) 不稳定
计数排序 O(n+m) O(n+m) O(m) 稳定
桶排序 O(n) O(nlogn) O(n) 稳定

3、面试算法

3.1、判断链表是否有环

基础版

问题

如何判断一个链表是否有环

思路

链表若有环,两个指针分别不同速度,速度快的会追上速度慢的

  • 两个指针指向头节点:Node p1 = head; Node p2 = head;
    • p1每次移动一个位置:p1=p1.next;
    • p2每次移动两个位置p2=p2.next.next;
  • p1和p2相遇则有环:p1==p2

代码

扩展

问题

若有环,求环的长度和入环节点

思路

环的长度:p1和p2的速度相差1,因此二次相遇之间走过的步数就是环的长度

入环节点:通过数学计算可以得到,入环点到头节点的距离==首次相遇点到入环点之间的距离,因此首次相遇后,把一个指针重新指向头节点,两个指针同时一次走一步,再次相遇的点就是入环节点

3.2、最小栈

问题

实现一个栈,有出栈、入栈,另外要有取最小值方法,同时三个方法的时间复杂度都为O(1)

思路

代码

3.3、求最大公约数

问题

求两个整数的最大公约数

思路

辗转相除法(欧几里得算法):两个正整数a和b(a>b)的最大公约数等于a除以b的余数c和b之间的最大公约数

更相减损法:两个正整数a和b(a>b)的最大公约数等于a-b的差值c和b之间的最大公约数

代码

优化

位运算

3.4、判断一个数是否是2的整数次幂

问题

判断一个数是否是2的整数次幂

思路

  • 2的整数次幂装换位二进制时,首位为1,其余位为0
  • 2的整数次幂减去1,转换为二进制时,全部为1,首位为0
  • 与运算&

代码

3.5、无序数组排序后的最大相邻差

问题

求出数组排序后的任意两个相邻元素的最大差值

思路

采用桶排序的思想,

  • 分桶
  • 入桶,记录每一个桶的最大值和最小值
  • 统计相邻两个桶的最大值和最小值的差

代码

3.6、用栈实现队列

问题

用栈模拟队列,实现:入队、出队

思路

  • 两个栈A和B
  • 入队在A中压栈
  • 出队
    • B是否为空,空的话首先将A中的元素弹出到B中
    • B弹栈

代码

3.7、寻找全排列的下一个数

问题

给一个正整数,找出全排列的下一个数

用这个数包含的全部数字重新排列,找出一个大于且仅大于原数的新整数

思路

固定的几个数字进行排列,逆序排列时数字最大,顺序排列时数字最小,同时为了保持重新排列的数仅比原数大,要尽可能保持高位不变,仅仅改变低位的数字

  • 从后找,找到当前整数的逆序区域(逆序区域已经是最大的了,要找到不是逆序区域的数字的位置),如12354,只变换5和4不能满足比原数大,要从3开始变
  • 更换找到的位置的数字和最末尾数字,变为12435
  • 把逆序区域变为顺序,即交换5和4,保持最小

代码

3.9、删除k个数字后的最小值

问题

给一个整数,删除k个数字,要求剩下的数字尽可能小

思路

删除掉一个数字后,整数的位数减少1

需要从高位起,若该位置数字比后一位大,则需要去掉

代码

3.9、实现大整数相加

问题

实现两个很大的整数相加

思路

根据手动相加思路,低位相加,大于10后,高位加1

  • 数字逆序存到两个数组中
  • 遍历,若两个数组对应的位置值相加大于10,则保留个位,且高位加1
  • 下一位相加,同时要加上低位进的1

代码

3.10、金矿问题

问题

10名工人,5堆金矿,价值{500400350300200},需要人力{55343}

求解如何最大收益

思路

动态规划

代码

3.11、寻找缺失的整数

基本问题

问题

一个无序数组,有99个不重复的正整数,范围1-100,求出100中缺少的那个

思路

先计算累加和,然后减去有的99个数,得到的即为缺少的

扩展1

问题

数量若干,同时99个整数出现了偶数次,1个出现了奇数次,求出这一个数

思路

异或运算

扩展2

问题

2个数字出现了奇数次,求这两个数

思路

分治法

4、算法的应用

4.1、Bitmap

4.2、LRU

4.3、A星寻路算法

4.4、红包算法