java数据结构和算法

来源:互联网 发布:老师知乎 编辑:程序博客网 时间:2024/05/21 05:43

java数据结构与算法

写给读者的话:

本人是一个刚刚毕业的程序员,大学期间数据结构学的比较扎实,来工作后发现虽然概念都知道,但是应用不是很熟练,所以打算重新撸几遍数据结构,正好在写java,这里就用java描述数据结构了;然后有几个要点:

1)实践永远是检验真理的唯一标准,要想知道自己学的好不好,锻炼提升自己,就需要多多练习了,本人在工作中,都会去思考代码的优化问题,看能够有更好的办法解决;2)温故而知新,很多东西看一遍是不可能理解透彻的。3)共享精神,要乐于去分享知识,不要怕被别人超越(有句话说得好,一直被模仿,从未被超越,精髓不是都能学来的而是自己总结体会出来的)。4)今日更新一下一个优美的品质。我和我同事同样都是看书,我的同事显得仔细的多,很有质疑精神,但是过多的仔细就显得固执了。希望我在以后的学习中能尽量细致一点吧。

引论

书中讲述了大量的数学公式,指数,对数,二项式,推理方法以及java的泛型和函数对象,这里不做整理了。有兴趣的可以去看看书,不是说这一块不重要,而是很基础(装13了,哈哈)!

算法分析

看来到哪里都离不开数学,真是学会数理化走遍天下都不怕。这一部分主要讲述了算法的时间复杂度,看的我头要炸了。

表、栈和队列

本章主要讲述三种基本的数据结构,每一个有意义的程序都应该显式地至少使用一种这样的数据。

抽象数据类型(ADT)

定义:是带有一组操作的一些对象的集合。

表 ADT

定义在表上的基本操作

printList();makeEmpty();find();返回某一项首次出现的位置insert();链表中插入元素remove();链表中删除元素findKth();返回索引next操作;previous操作;

这里讲一下java里的链表类:LinkedList

  • 简介

    LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。

    LinkedList 实现 List 接口,能对它进行队列操作。

    LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。

    LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。

    LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

    LinkedList 是非同步的。

  • 继承关系

    java.lang.Object   ↳     java.util.AbstractCollection<E>         ↳     java.util.AbstractList<E>               ↳     java.util.AbstractSequentialList<E>                     ↳     java.util.LinkedList<E>public class LinkedList<E>    extends AbstractSequentialList<E>    implements List<E>, Deque<E>, Cloneable, java.io.Serializable {}

LinkedList的本质是双向链表。

1)LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。

2)LinkedList包含两个重要成员:header和size。

header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。 size是双向链表中节点的个数。
  • LinkedList类中的APIhttp://www.yiibai.com/java/java_linkedlist_class.html

  • LinkedList的遍历方式(比较实用)

1)迭代器遍历,即通过Iterator去遍历

for(Iterator iter = list.iterator(); iter.hasNext();)    iter.next();

2)普普通通的遍历

for (int i=0; i<size; i++) {list.get(i);        }

3)另类for循环

for (Integer integ:list)     ;

4)pollFirst遍历

while(list.pollFirst() != null)    ;

5)pollLast遍历

while(list.pollLast() != null)    ;

6)removeFirst()遍历

try {    while(list.removeFirst() != null)        ;} catch (NoSuchElementException e) {}

7)removeLast()遍历

try {    while(list.removeLast() != null)        ;} catch (NoSuchElementException e) {}

结果示意:

Java collections API 中的表

在类库中,java语言包含了一些普通数据类型的实现。该语言的一部分通常叫做collections API。表ADT是Collections API中实现的数据结构之一;

集合( collection)是将其他对象组织到一起的一个对象。集合也叫作容器(container),它提供了一种方法来存储、访问和操作其元素。集合帮助 Java 程序员很容易地管理对象。Java程序员应该熟悉集合框架中一些重要的类型,它们在java.util包中。

这里挪用一下网上的继承关系图,从表中可以看出,set,list,quene这几种数据结构是继承了collection接口的。

继承关系图

下面介绍一下collection的API函数:

可以看到collection接口扩展了Iterabale接口。实现Iterable接口的类可以拥有增强的for循环。实现Iterable接口的类必须实现一个Iterator的方法,改方法返回一个Iterator类型对象,该Iterator类定义在java.util包中,如下图。这里强调一个小技巧,用Iterator迭代器进行remove操作的效率要比用collection的remove操作高,但是在迭代器操作的过程中,如果collection对象发生了结构变化,就会呆滞迭代器报错,这里需要注意。

List接口、ArrayList类和LinkedList类

ArrayList是list的可变数组实现,LinkedList是list的双向链表实现。数组的优点在于遍历速度快,删除添加开销较大;链表的优点在于添加和删除速度快,遍历开销大;但是有了迭代器,对于LinkedList是非常有利的,加快了遍历速度,但是对于ArrayList没有很大的提升,因为添加和删除操作数组总是要整体移动的,除非是在末端操作。

这里写一个小插曲,其实看一个类或者接口怎么用,最好的办法是去阅读JDK的文档,上边说的很清楚,现在也有汉化版的,不过英文版的可能描述更清晰一些。http://tool.oschina.net/apidocs/apidoc?api=jdk-zh

ListIterator接口(仅限List)

本宝宝用的markDown软件画图操作不是很友好,在word里画了一下单向链表的移动流程。不论是Iterator还是ListIterator都是在对象间移动;next()函数取值是取的当前最后一次访问的值,next()函数调用一次既会向后移动一次。remove操作是删除最近访问的元素;如果要逆向遍历列表要首先正向遍历一遍;

下面展示一下ListIterator接口的API函数:nextIndex()和previousIndex()函数返回前后位置的索引,如果前后节点为空也会返回索引

程序实例:

public class LinkIterator {    public static void main(String[] args) {        List<String> Horde = new LinkedList<>();        Horde.add("Great  ");        Horde.add("Wall  ");        Horde.add("is  ");        Horde.add("beautiful!");        Iterator it = Horde.iterator();        while (it.hasNext()){            String temp =it.next().toString();            if (temp.contains("ea")){                it.remove();            }        }        System.out.println(StringUtils.join(Horde,""));        //建议两段程序分别运行        ListIterator Lit = Horde.listIterator();        while (Lit.hasNext()){            String temp =Lit.next().toString();            System.out.println(Lit.hasNext());            System.out.println(Lit.hasPrevious());            System.out.println(Lit.nextIndex());            System.out.println(Lit.previousIndex());            if (temp.contains("ll")){                Lit.remove();            }        }        System.out.println(StringUtils.join(Horde,""));    }}

Iterator运行结果:

ListIterator运行结果:

ArrayList类的实现

这一部分较为复杂,不是很明白,作者自已写了一个ArrayList类的实现,这里涉及内部类的一些问题。

LinkedList类的实现

作者自已写了一个LinkedList类的实现

栈 ADT

定义:限制插入和删除只在一个位置上进行的表,叫做栈的顶端,是一种先进后出队列。对空栈的pop(出栈)操作是一种错误。ArrayList和LinkedList都支持栈的操作

应用:字符的匹配,后缀表达式

队列 ADT

定义:插入在一端,删除在另一端的表。

小结

本章结束了。这里还建议看一下map,map.entry和Queue类的介绍,参照jdk1.6中文版

预备知识

本章将讨论树这种非常有效的数据结构,其大部分的运行时间为O(logN),这里涉及的树叫做二叉树;是两种类集合库TreeSet和TreeMap实现的基础。

这里不再介绍树的基础预备知识,有不清楚的同学可以看看这个https://www.cnblogs.com/polly333/p/4740355.html

二叉树

应用—表达式树

查找树ADT —二叉查找树

定义:使二叉树成为二叉查找树的性质是,对于树中的每一个节点X,它的左子树中所有项的值都小于X中的项,而它右子树中所有项的值大于X中的项。二叉查找树没有直接的类予以支持, 需要自己编写。http://blog.csdn.net/a19881029/article/details/24379339

这里介绍一下TreeSet类的方法摘要:


在介绍一下TreeMap类的方法摘要:



AVL树

AVL树的特性:一棵AVL树是其每个结点的左子树和右子树的高度最多相差1的二叉查找树(空树的高度为-1),这个差值也称为平衡因子(其取值可以是1,0,-1,平衡因子是某个结点左右子树层数的差值,有的书上定义是左边减去右边,有的书上定义是右边减去左边,这样可能会有正负的区别,但是这个并不影响我们对平衡二叉树的讨论)。

看了看大神的博客,才发现自己写的文章很菜啊,可能是学习的收获没有那么深刻吧,这里附上链接,向前辈致敬!http://blog.csdn.net/javazejian/article/details/53892797

插入等操作对AVL树的影响,通过单旋转双旋转解决。附上链接:

http://blog.csdn.net/pacosonswjtu/article/details/50522677

http://blog.csdn.net/liyong199012/article/details/29219261

伸展树

伸展树是基于二叉查找树的,它不保证树一直是平衡的,但是各种操作的平均复杂读是 O(logN) 。

伸展树的设计是具体考虑到了局部性原理 (刚被访问的内容下次可能还被访问,查找次数多的内容可能下次还被访问),为了使整个的查询时间更小,查询频率高的那些结点应当处于
靠近树根的位置。

这样,一个比较好的解决方案就是:每次查找就结点之后对树进行重新构造。把查找的结点搬移到树根的位置,以这种方式自调整形式的二叉查找树就是伸展树。

http://blog.csdn.net/u012124438/article/details/78067998

http://blog.51cto.com/kiritor/1226766

散列

散列表(hash table)ADT,只支持二叉查找树所允许的一部分操作。散列表的实现常常叫做散列(hashing

本人是垃圾,看的懂书中关于散列的种种叙述却无法写出来,只能整理一点实用的标准类库。

散列函数

分离链接法

数据结构(Java语言)——HashTable(分离链接法)简单实现http://blog.csdn.net/zhang_zp2014/article/details/47980563

开放定址发

线性探测法

平方探测法

双散列法

再散列法

标准库的散列表

标准库包含set和map的哈希实现,即hashSet和hashMap类,必须提供equals()和hashCode()方法,通常是使用分离链接散列法实现的。

  • hashSet类

  • hashMap类


示例程序:

public class HashTable <AnyType>{    private static final int DEFAULT_TABLE_SIZE = 10;//默认容量    private List<AnyType>[] theLists;//散列表的数组    private int currentSize;//当前数据个数    public HashTable() {        this(DEFAULT_TABLE_SIZE);    }    public HashTable(int size) {        theLists = new LinkedList[nextPrime(size)];        for (int i = 0; i < theLists.length; i++) {            theLists[i] = new LinkedList<AnyType>();        }    }    /**     * 使哈希表变空     */    public void makeEmpty() {        for (List<AnyType> list : theLists) {            list.clear();        }        currentSize = 0;    }    /**     * 哈希表是否包含某元素     * @param x 查询元素     * @return 查询结果     */    public boolean contains(AnyType x) {        List<AnyType> whichList = theLists[myhash(x)];        return whichList.contains(x);    }    /**     * 向哈希表中插入某元素,若存在则不操作     * @param x 插入元素     */    public void insert(AnyType x) {        List<AnyType> whichList = theLists[myhash(x)];        if (!whichList.contains(x)) {            whichList.add(x);            if (++currentSize > theLists.length) {                rehash();            }        } else {        }    }    /**     * 向哈希表中删除某元素,若不存在则不操作     * @param x 删除元素     */    public void remove(AnyType x) {        List<AnyType> whichList = theLists[myhash(x)];        if (whichList.contains(x)) {            whichList.remove(x);            currentSize--;        } else {        }    }    /**     * 哈希算法,有多种实现方法     * @param x 元素     * @return 哈希值     */    private int myhash(AnyType x) {        int hashVal = x.hashCode();        hashVal %= theLists.length;        if (hashVal < 0) {            hashVal += theLists.length;        }        return hashVal;    }    /**     * 再散列函数,插入空间不够时执行     */    private void rehash() {        List<AnyType>[] oldLists = theLists;        // 分配一个两倍大小的空表        theLists = new List[nextPrime(2 * theLists.length)];        for(int j=0;j<theLists.length;j++){            theLists[j]=new LinkedList<AnyType>();        }        currentSize = 0;        for (int i = 0; i < oldLists.length; i++) {            for (AnyType item : oldLists[i]) {                insert(item);            }        }    }    /**     * 检查某整数是否为素数     * @param num 检查整数     * @return 检查结果     */    private static boolean isPrime(int num) {        if (num == 2 || num == 3) {            return true;        }        if (num == 1 || num % 2 == 0) {            return false;        }        for (int i = 3; i * i <= num; i += 2) {            if (num % i == 0) {                return false;            }        }        return true;    }    /**     * 返回不小于某个整数的素数     * @param num 整数     * @return 下一个素数(可以相等)     */    private static int nextPrime(int num) {        if (num == 0 || num == 1 || num == 2) {            return 2;        }        if (num % 2 == 0) {            num++;        }        while (!isPrime(num)) {            num += 2;        }        return num;    }    /**     * 输出散列表     */    public void printTable() {        for(int i=0;i<theLists.length;i++){            System.out.println("-----");            Iterator iterator=theLists[i].iterator();            while(iterator.hasNext()){                System.out.print(iterator.next()+" ");            }            System.out.println();        }    }    public static void main(String[] args) {        Random random = new Random();        HashTable<Integer> hashTable = new HashTable<Integer>();        for (int i = 0; i < 30; i++) {            hashTable.insert(random.nextInt(30));        }        hashTable.printTable();    }}

结果展示:

    -----    0 23     -----    1 24     -----    2     -----    26     -----    4     -----    -----    6 29     -----    -----    -----    9     -----    10     -----    11     -----    -----    13     -----    14     -----    -----    16     -----    17     -----    -----    19     -----    20     -----    -----    22 

优先队列(堆)

二叉堆

满足结构性堆序性
- 结构性

这里插入一个完全二叉树的概念:

完全二叉树是指这样的二叉树:除最后一层外,每一层上的结点数均达到最大值;在最后一层上只缺少右边的若干结点。

堆是一种完全二叉树或者近似完全二叉树,所以效率极高,像十分常用的排序算法、Dijkstra算法、Prim算法等都要用堆才能优化。

一个堆结构将由一个(comparable对象的)数组和一个代表当前堆的大小的整数组成。由于是完全二叉树所以这里用数组实现堆的结构。


  • 堆序性质

优先队列的应用

  • 选择问题

堆排序

  • 事件模拟

d-堆

左式堆

斜堆

二项队列

JDK中的应用


排序(最应该掌握的实用技能)

插入排序

插入排序由N-1趟排序组成。对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。

对于已基本排序的输入项,插入排序是不错的算法。该算法的的效率为O(N^2)

示例如下:

程序实例:

希尔排序(缩减增量排序)



希尔排序的效率主要取决于增量序列的设置。


堆排序

实例程序:


归并排序

归并排序以O(N log N)最坏情形时间运行,而所使用的比较次数几乎是最优的。它是一个递归算法一个好的实例。


归并排序程序实例:

快速排序

桶式排序

外部排序

小结

本证主要讲述了几种常见的排序,非常重要,需要反复的研读。这里附上大牛的作品http://blog.csdn.net/happy_wu/article/details/51841244