1. 程式人生 > >java資料結構和演算法

java資料結構和演算法

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