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是雙向連結串列中節點的個數。
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