zoukankan      html  css  js  c++  java
  • 算法

    算法解决问题
    在研究一个新的问题时,按照下面的步骤识别目标并使用数据结构抽象解决问题:

    • 1.定义API;
    • 2.根据特定的应用场景开发用例代码;
    • 3.描述一种数据结构,并在API所对应的抽象数据类型的实现中根据它定义类的实例变量;
    • 4.描述算法(实现一组操作的方式),并根据它实现类中的实例方法;
    • 5.分析算法的性能特点.

    抽象数据类型
    算法一般都是某个抽象数据类型的一个实例方法的实现.
    抽象数据类型是一种向用例隐藏内部表示的数据类型.

    实现抽象数据类型:

    • 封装
    • API设计,只为用例提供它们所需要的,仅此而已.

    数据抽象的作用:

    • 1.准确的定义算法能够为用例做什么
    • 2.隔离算法的实现和用例代码
    • 3.实现多层抽象,用已知算法实现其他算法.

    关于图的一些知识

    • 图:
      是由一组顶点和一组能将两个顶点相连的边组成的.
      图的定义和绘制出来的图像是无关的

    泛型

    集合类的抽象数据类型的一个关键特性是我们应该可以用它们存储任意类型的数据.也叫参数化类型.
    泛型是指API的泛型,这样API可以面对所有的抽象数据类型定义.

    算术表达式的实现问题

    这是一个很有意思的问题,可以实现的方法有很多种:

    • Dijkstra算法实现,利用双栈将表达式进行一定的括号"("处理.
    • 还有一种可直接使用的就是,先将算术表达式(中缀表达式)转换成后缀表达式.利用栈的特性来计算后缀表达式.

    数组表示栈,可调整数组的大小

    • 防止溢出和栈空间地址的浪费.
    public void resize(int max)
    {
    	Item[] temp=(Item[])new object[max]; // this temp array can change size
    	for (int i=0;i<N;i++) 
    		temp[i]=a[i];
    	a=temp;
    }
    
    public void push(Item item)
    {
    	if(N==a.length) resize(int N*2);// resize(2*a.length);
    	a[N++]=temp;
    }
    public Item pop()
    {
    	Item item a[--N];
    	a[N]=null;
    	if(N>0&&N==a.length/4) resize(a.length/2);
    	return item;
    }
    

    这是集合类抽象数据类型实现的模板.

    链表

    • 链表是一个基础数据结构.是其他复杂数据结构构造代码的模板.

    算法分析

    • 复杂度分为时间复杂度和空间复杂度:
      表示一段程序运行的效率(程序运行解决问题所用的时间和内存占用的多少)

    • 运行时间,一般是指时间复杂度:
      1.时间频度 一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)
      2.时间复杂度 在刚才提到的时间频度中,n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。但有时我们想知道它变化时呈现什么规律。为此,我们引入时间复杂度概念。 一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。

    • 占用内存,一般是指空间复杂度.:
      一个程序的空间复杂度是指运行完一个程序所需内存的大小。利用程序的空间复杂度,可以对程序的运行所需要的内存多少有个预先估计。一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。程序执行时所需存储空间包括以下两部分
      1.固定部分。这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间
      2.可变空间,这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。一个算法所需的存储空间用f(n)表示。S(n)=O(f(n))  其中n为问题的规模,S(n)表示空间复杂度。

    • 具体的分析思想:我们可以构造一个数学模型来描述任意程序的运行时间.
      1.确定输入模型,定义问题的规模
      2.识别内循环
      3.根据内循环中的操作确定成本模型
      4.对于给定的输入,判断这些操作的执行频率.进行数学分析

    设计算法的思路及步骤

    • 第一个比较重要的任务就是精确定义问题.
    • 数据结构的性质直接影响算法的效率.
    • 主要还是分析每个实例方法的重要性.去实现实例方法.

    排序

    • 排序算法的目标就是将所有元素的主键按照某种方式排列(大小或者字母顺序)
    • 元素和主键的具体性质在不同的应用中差别很大,要注意区分
    • 除了函数调用所需的栈和固定数目的实例变量之外无需额外的内存的,原地排序算法.
    • 需要额外内存空间来进行存储的另一份数组副本的,其他排序算法.

    算法之间比较的科学方法

    • 实现并调试
    • 分析它们的基本性质
    • 对相对性能作出猜想
    • 用实验验证思想
    • 具体代码实现 见SortCompare.java

    选择排序

    • 整体思路:
      1.找到数组中的那个最小的元素,将它和第一个元素交换位置
      2.继续循环,找到剩余数组中的最小元素和数组的第二个元素进行交换
    • 排序特点:
      1.运行时间和输入无关
      2.数据移动是最少的,一共会有N次交换,(frac{N^{2}}{2}) 次比较

    插入排序

    • 整体思路:
      1.外循环先将所有的数组元素进行遍历
      2.内循环中从外循环的那个元素一直到数组的第一个元素进行进行大小的比对,交换
    • 排序特点:
      1.插入排序所需要的时间取决于输入中元素的初始顺序
      2.平均情况下插入排序需要 (frac{N^{2}}{4}) 次比较 和 (frac{N^{2}}{4}) 交换
      3.插入排序适用于部分有序的小规模数组
    • 部分有序数组:
      1.数组中每个元素距离它的最终位置都不太远
      2.一个有序的大数组接一个小数组
      3.数组中只有几个元素的位置不确定

    希尔排序
    基于插入排序的快速排序算法

    • 整体思路:
      1.交换不相邻的元素对数组的局部进行排序
      2.最终用插入排序将局部有序的数组排序
    • 具体实现:
      1.要采用跳跃分割的策略,将相距某个增量的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后的结果是基本有序而不是局部有序
      2.将关键字较小的记录,而不是一步一步的往前挪动,而是跳跃式地往前移,是得完成一轮循环后,整个序列就朝着有序迈进一步.
    • 适用范围:
      1.对于中等大小的数组运行时间是可以接受的,代码量小,且不需要使用额外的内存空间.
      2.直接接触硬件,运行于嵌入式系统中的代码等当要解决一个排序问题而又没有系统的排算法可用,可以先用希尔排序.

    归并排序
    利用完全二叉树来实现排序

    • 整体思路:
      利用子序列迭代归并的思想来实现归并排序
    • 抽象方法:
      原地归并的抽象方法.这个是理解算法的关键基础. 自顶向下和自底向上的归并排序的区分

    快速排序
    一种原地排序的分治算法

    • 整体思路:
      1.利用递归的思想,先将要排序的数组找到切分点进行分割,左侧的数组元素都小于切分点,右侧的数组元素都大于切分点.分别在再将两个数组进行递归调要排序的程序
      2.主要就是切分功能的实现
    • 切分实现:
      1.先将数组的第一个元素设置为切分点(也就是比较元素).
      2.设置两个指针,分别从数组头和数组尾进行递增递减循环
      3.当数组头的指针指向一个大于切分点的元素(不该出现在左侧的大元素),数组尾指向一个小于切分点的元素(不该出现在右侧的小元素).交换两个元素的位置.
      4.当两个指针重合时,将切分点元素和指针元素进行互换.
      需要注意,在循环的进行中要保证小指针要小于大指针
    • 知识拓展:
      注意区分三取样切分快速排序和应对于存在大量重复元素的数组的三向切分.
      当然还有一个更厉害的就是,已经有了完全不需要比较的排序算法.

    知识点补充:
    优先队列

    • 概念:
      一个抽象的数据类型
      合理的分配优先级,减少不必要的操作
      支持两种基本操作,删除最大元素和插入元素.
    • 一个基础的数据结构:

      当一颗二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序.
    • 注意:堆有序和二叉树的层级有序是有区别的.堆有序只满足结点大于等于它的子结点.
      堆的两个重要操作,由上至下的堆有序化(下沉),由下至上的堆有序化(上浮)
      下沉主要用于删除元素,而上浮主要用以插入元素
    • 面试中的问题
      当面对的数据量太大,无法排序,甚至无法装进内存当中.例如将十亿个元素中找出最大的十个元素.这个时候优先队列就可以发挥作用.

    堆排序
    基于二叉堆实现的算法

    • 堆算法:
      1.插入元素:二叉堆插入新的元素(数组末尾)打破来堆的有序性,这个元素进行上浮swim操作
      2.删除最大元素:将数组末尾的元素和二叉树的顶点元素进行交换,删除数组末尾的元素.堆的有序性被打乱,顶点元素进行下沉sink操作.
    • 主要特点:
      可以从未排序的数组中找到最大元素
      利用二叉堆实现,存储在数组[1,N]中,数组第一个元素a[0]没有使用
      原因是二叉堆(树)的结构问题
    • 堆分为大顶点堆和小顶点堆.在Java中默认为大顶点堆来实现算法.(只定义了大顶点堆)
    • 步骤:
      堆的构造
      下沉排序  
    • 上浮和下沉排序要去理解清晰,理解其中的原理
      主要工作还是在下沉排序中,和优先队列的删除最大元素相同.
      二叉树性质
      1.在二叉树的第i层上至多有(2^{i-1})个结点
      2.深度为k的二叉树至多有(2^k-1)个结点
      3.具有n个结点的完全二叉树的深度为([log_{2} n]+1)

    查找

    • 链表:
      看在上帝的面子上,不要试图将链表进行排序
    • 符号表的三种基本实现:
      1.基于链表,插入的灵活性
      2.基于有序数组,查找的高效性
      3.基于二叉查找树,结合链表的灵活性和有序数组的高效性.
    • 符号表,有时也称为字典的实现.
    • 二叉查找树,每个结点还包含一个键和一个值,键之间也有顺序之分(支持高效查找)
      BST 结点:一个键,一个值,一个条左链接,一条右链接,一个计数器.
    • 二叉查找树的操作:
      较难的(即时)删除键操作,不仅要改变链接点的指向还有更新计数君size().
      使用右子树的的最小结点(后继结点),使用后继结点来替代要删除的结点,一般与deleteMin()和Min()函数相结合.
      使用左子树的最大结点(前驱结点),同样的,使用deleteMax() ,Max()函数结合来使用.
    • 二叉查找树中,所有操作在最环情况下所需的时间都和树的高度成正比.
    • 平衡查找树:
      一个完美平衡的2-3查找树中所有的空链接到根结点的距离都该是相同的.
      所有的局部变换不会对影响整个树的有序性和平衡性.
      平衡二叉树的表示方法大多树的操作不是很简单.需要处理的不同情况比较多(2-结点,3-结点的混合).
    • 红黑二叉查找树:
      利用这种数据结构来高效实现二叉树增删查
      主要思想:找到一个方法替换3-结点,并可以用简洁的代码进行实现
    • 红黑树的定义:
      1.红链接必须都为左链接
      2.一个结点不能同时链接两个红链接
      3.黑色完全平衡,除去红链接,任意空链到根结点的路径上黑色链接数量相同.
      满足这几个条件,红黑树和2-3树是一一对应的.
    • 红黑树的实现:
      使用左旋转+右旋转+颜色变换将红链接在树中向上传递.
      1.右子结点为红,左子结点为黑,进行左旋转.
      2.左子结点为红,且其的左子结点也为红,进行右旋转.
      3.左右子结点均为红色,进行颜色变换.
    • 散列表(HashTable):
      利用算术运算将转换成数组的索引来访问数组中的值.
      就是实现符号表快速查找的一种基本数据类型.
      1.要知道散列函数的设计
      2.拉链法散列函数:数组是少于键值对的,利用链表来存储有相同键的元素
      3.线性探测法散列表:数组是多于键值对的,相同键的元素可以储存在空数组内存中.
    • 散列表的具体数据结构和代码实现还有一点问题,需要接着理解.
      例如删除delete() 和put()实现的代码相互嵌套问题
      hashcode()的实现也是一团浆糊.
    • 查找的应用:
      1.基于Dedup过滤器的黑名单,白名单.
      2.在实际的应用中可以用于 信用卡盗用问题和防火墙问题.
      3.符号表满足输入无限并且响应时间严格的实现.
    • 应用方面存在的问题:
      1.文件的输入和输出问题,这是一个大问题!!!!!
      2.矩阵,向量的计算

    • 图:
      1.无向图,有向图,无向有权图,无向无权图
      2.无向图的基本概念,邻接表的实现.
      3.存在的问题,环个数以及边表的实现.
    • 功能的实现:
      1.深度优先搜索可用来查找图中的路径;广度优先搜索可用来查找图中的最短路径
    • 无向图:
      连通分量的实现
      判断图中有无环
      判断图是否为二分图(任何无回路的图均是二分图)
      实现符号图,并用符号图来表达间隔的度数.
    • 有向图:
      关于有向图,肯能在实现方面可以比无向图的实现要更简单一点.
      有向无环图 (DAG)就是一副不含有有向环的有向图.
      拓扑排序:基于有向无环图来实现.在有向无环图的顶点组成的序列,每个顶点出现有且只有一次;若A在序列中排在B的前面,则在图中不存在从B到A的路径.满足上述条件,称为该图的一个拓扑排序.
    • 拓扑排序:
      一幅有向无环图的拓扑排序即为所有顶点的逆后序排列
      结合前面的排序算法来看.拓扑排序的优缺点?
    • 任务调度问题:
      任务调度就是拓扑排序需要解决的问题.
      步骤:
      1.指明任务和优先级条件
      2.不断检测并去除有向图中的所有环
      3.使用拓扑排序解决调度问题
    • 有向图的强连通性:
      两个顶点之间是互相可达的,称为强连通.如果一幅图中任意两个顶点都是强连通的,则这幅图也是强连通的.
    • 有向图的传递闭包:
      有向图G的传递闭包是由相同的一组顶点组成的另一幅有向图,在传递闭包中存在一条从V指向W的的边.当且仅当在G中w是从v可达的.
    • 生成树:
      图的生成树是它的一颗含有其所有顶点的无环连通子图.
      最小生成树:一幅加权图中,最小生成树是树中所有边权值之和最小的生成树.
      切分定理:会把加权图中的所有顶点分成两个集合,检查横跨两个集合的所有边并识别那条边应属于图的最小生成树.
      贪心算法
      贪心算法是一种在每一步选择中都采取在当前状态下最好或是最优(最有利)的选择,从而导致结果是最好或者最优的算法.
    • 加权无向图:
      带权重边的数据类型实现,每个边的权重能不能直接进行return?
    • Prim 算法:
      1.思路:利用贪心算法,每一步都会为一颗生长中的树添加一条边,给定一个vertex,添加与这个顶点的所有边,并每次都是将下一条链接树中的顶点与不再树中的顶点且权重最小的边加入树中.
      2.算法可以得到任意加权连通图的最小生成树.
      3.prim算法即时版本的过程原理不是很明白.
    • Kruskal算法:
      1.思路:按照边的权重顺序(由小到大)进行处理,将边加入最小生成树中,加入的边不会与已经加入的边构成环,直到树中含有V-1个边为止.
      2.利用union-find()算法中的connected()实现是否会在最小生成树中产生环.
    • Dijkstra算法:
      1.作用:实现最短路径的算法
      2.思路:首先将distTo[s]初始化为0,distTo[]中的其他元素初始化为正无穷.然后将distTo[]最小的非树结点放松并加入树中,直到所有的顶点都在树中或者所有的非树顶点的distTo[]值均为无穷大.
      3.实现算法的先决条件比较多,要仔细理解.

    字符串

    • 字符串 看似简单,实则最难
    • 字符串排序:
      Alphabet 字母表,基本不咋用.String字符串的表示方法.
    • 低位优先:从右到左检测键中的字符.
      高位优先:从左到右检测键中的字符
    • 高位优先:
      高位优先的代码实现描述是真的很难让人理解,只能先将整体的思路进行理解.
    • 由于难度原因,先跳过这一部分内容

    后面的内容及下一章所交代的应用被背景,暂缓一段时间再看.

    不要用狭隘的眼光看待不了解的事物,自己没有涉及到的领域不要急于否定. 每天学习一点,努力过好平凡的生活.
  • 相关阅读:
    org.apache.maven.archiver.MavenArchiver.getManifest错误
    常见电商项目的数据库表设计(MySQL版)
    二、Log4j基本使用方法
    Java Dao设计模式
    JavaBean简单及使用
    jsp的9个内置对象
    JSP+MySQL实例
    JSP指令--include指令(静态包含)
    三种Scriptlet总结
    ***mysql 用一个表的一列,去更新另一表的一列
  • 原文地址:https://www.cnblogs.com/GeekDanny/p/9260502.html
Copyright © 2011-2022 走看看