java数据结构和算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Little_Red_Rose/article/details/78581436

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是双向链表中节点的个数。

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

猜你喜欢

转载自blog.csdn.net/Little_Red_Rose/article/details/78581436