Android中的資料結構
資料結構在Android中也有著大量的運用,這裡採用資料結構與原始碼分析相結合,來認識Android的資料結構
線性表
線性表可分為順序儲存結構和鏈式儲存結構
順序儲存結構-ArrayList
通過對原始碼的產看得知,ArrayList繼承自AbstractList,實現了多個介面,其中List裡面就實現了常用的一些操作,包括增刪改查清除大小等等
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ··· }
ArrayList的實現其實就是基於陣列,而且可以得知ArrayList的初始長度為10,資料進行了反序列化:transient
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
可以知道,ArrayList的資料初始化是在構造方法中完成的
public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA; } public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
首先看一看add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
這裡的ensureCapacityInternal()
方法比較重要,來看看這個方法都做了什麼
private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
這裡得到了最小需要的ArrayList大小,然後呼叫了ensureExplicitCapacity()
,這裡有一個modCount
變數,用來記錄元素的情況
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
這裡做了判斷,如果當前大小小於所需大小,那麼就呼叫grow()
方法,ArrayList之所以能到增長,其實現位置就在這裡
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
這裡獲取了元素的個數,然後計算新的個數,其增量是原個數的一半,然後得到其符合的值,如果需要的個數大於規定的最大值(Integer.MAX_VALUE - 8
),那麼就將其大小設定為Integer.MAX_VALUE
或者Integer.MAX_VALUE - 8
,
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
到這裡就已經將其長度增大了,再將原資料複製到新的陣列,然後回到add方法,得知這時候將新增的元素放到之前最後面的位置elementData[size++] = e;
,這樣就實現了ArrayList資料的新增
其餘方法和add方法類似,比如remove就是將元素置空,然後讓GC去回收
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
ArrayList的迭代器,用來遍歷元素,迭代器裡面的增加刪除操作也和ArrayList的增加刪除一樣,需要對size進行操作
至此,ArrayList的簡單解讀就完成了
鏈式儲存結構-LinkedList
在Android中的鏈式儲存結構,就是使用雙向連結串列實現的,有一個內部類Node,用來定義節點,初始化的時候是頭節點指向尾節點,尾節點指向頭節點
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
其繼承了AbstractSequentialList,並實現了一系列介面,可以看到不僅實現了List還實現了Deque雙端佇列
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
···
}
定義的變數主要有三個,首節點,尾節點和大小
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
首先依舊是檢視add方法的操作
public boolean add(E e) {
linkLast(e);
return true;
}
這裡呼叫了linkLast()
方法,而這個方法是從尾部新增元素
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
還可以在指定位置新增元素,首先會檢查新增位置是否合法,合法的意思就是index >= 0 && index <= size
,如果插入的位置是末尾,那麼就是尾插法,如果不是末尾,就呼叫linkBefore()
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
會先呼叫node方法,得到指定位置的node,這裡從中間開始查詢,使得效率得到提高
這個思想在remove,set等裡面都有使用到,這也是使用雙向連結串列的原因,LinkedHashMap也是採用的雙向連結串列
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
然後在指定node位置之前插入元素
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
其他的方法與add類似,主要就是後繼和前驅的指向,以及插入位置的考量
ArrayList與LinkedList比較
ArrayList屬於順序儲存,LinkedList屬於鏈式儲存
ArrayList的刪除和插入效率低,查詢效率高,而LinkedList則剛好相反
在實際使用中要根據具體使用情況選擇
棧和佇列
棧和佇列是兩種不同讀取方式的資料結構,棧屬於先進後出,而佇列屬於先進先出,要比喻的話,棧好比一個瓶子,先放進去的要最後才能取出來,佇列還比就是一根管子,先進去的先出來,在Android中有著這兩種資料結構思想的實現類
棧
這裡就是Stack這個類,由於程式碼不多,直接全部貼出來
public
class Stack<E> extends Vector<E> {
public Stack() {
}
public E push(E item) {
addElement(item);
return item;
}
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
public boolean empty() {
return size() == 0;
}
public synchronized int search(Object o) {
int i = lastIndexOf(o);
if (i >= 0) {
return size() - i;
}
return -1;
}
private static final long serialVersionUID = 1224463164541339165L;
}
可以看到有push()
,pop()
,peek()
,empty()
,search()
方法,其中pop和peek的區別在於前者會刪除元素,而後者不會,後者只是檢視元素,那麼其具體實現是怎麼樣的呢,這就在其父類Vextor
中有所體現,檢視原始碼,其實和ArrayList基本上是一致的,無論是思想還是實現,在細微處有小區別,Vextor的擴容方式允許單個擴容,所以說Android中的棧實現是基於順序連結串列的,push是新增元素,search是查詢元素,從棧頂向棧底查詢,一旦找到返回位置,pop和peek都是檢視元素
另外,LinkedList也實現了棧結構
public void push(E e) {
addFirst(e);
}
而addFirst()
則是呼叫了linkFirst()
方法,也就是採用了頭插法
public void addFirst(E e) {
linkFirst(e);
}
那麼pop也應該是使用頭部刪除,果不其然
public E pop() {
return removeFirst();
}
佇列
佇列也分為順序結構實現和鏈式結構實現,但前者由於出隊複雜度高0(n),容易假溢位,雖然可以通過首尾相連解決假溢位,這也就是迴圈佇列,但實際中,基本是使用鏈式儲存實現的,有一個介面就是佇列的模型
public interface Queue<E> extends Collection<E> {
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
}
而在LinkedList實現了Deque介面,而Deque又是Queue的子類,故而之前的分析已經包含了佇列
那麼這次聚焦在Queue上,看看其都多是怎麼做的
前面的分析我們已經知道了add方法呼叫的是linkLast()
,也就是使用尾插法,那麼offer方法呢
public boolean offer(E e) {
return add(e);
}
可以看到offer()
呼叫了add()
,再看看剩下的方法,主要是remove方法
public E remove() {
return removeFirst();
}
移除的是首位置,而新增的是尾(與佇列隊尾插入一致)
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
poll返回的也是首位置,peek也是(與佇列隊頭取出一致)
public E element() {
return getFirst();
}
element()
返回首節點
HashMap與LinkedHashMap
這兩個嚴格來說不算資料結構,這裡將其提取出來,是因為這兩個在Android中有著廣泛運用
HashMap
首先看一下繼承類和實現介面,AbstractMap實現了Map裡面的絕大部分方法,只有eq沒有實現
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
···
}
大概看一下其結構,依舊是掃一眼內部類,其主要包括以下四類
HashMapEntry:一個個的鍵值對,其在Android中為hide,提供了包括Key的獲取,Value的設定獲取,比較等方法,注意這是一個節點,也就是說這也是通過連結串列組織起來的,不過這個連結串列屬於雜湊連結串列
XxxIterator:HashIterator,ValueIterator,KeyIterator,EntryIterator,今三個迭代器繼承自第一個,用來獲取相應的值
XxxSpliterator:HashMapSpliterator,KeySpliterator,ValueSpliterator,EntrySpliterator
XxxSet:KeySet,EntrySet,另外Value類也與其類似,不過沒有使用Set,這也就是為何value可以重複而key不能重複的原因,這是用來記錄值的集合
大致知道內部類的功能及其作用以後,就該看一看其成員變量了
static final int DEFAULT_INITIAL_CAPACITY = 4;
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final HashMapEntry<?,?>[] EMPTY_TABLE = {};
transient HashMapEntry<K,V>[] table = (HashMapEntry<K,V>[]) EMPTY_TABLE;
transient int size;
int threshold;
final float loadFactor = DEFAULT_LOAD_FACTOR;
transient int modCount;
首先是初始化大小為4,也就是說HashMap自建立開始就有4的容量,其最大容量為230,預設增長係數為0.75,也就是說其儲存容量達到總容量的75%時候,會自動擴容另外還定義了鍵值對,大小等
那麼接下來輪到構造方法了
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
} else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) {
initialCapacity = DEFAULT_INITIAL_CAPACITY;
}
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
threshold = initialCapacity;
init();
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}
簡單來說就是將設定的成員變數初始化,這裡的init()
為一個空方法
與上面分析線性表的思路一樣,我們先看新增元素的方法put()
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
我們來詳細分析一下在這裡面到底做了啥,首先要保證table不為空,然後如果key為空,那麼就儲存NullKey的value,那麼這是怎麼操作的呢,在putForNullKey()
我們可以看到,這裡使用了addEntry(0, null, value, 0);
,也就是說在HashMap裡面是可以存null鍵的,不過最多隻能存一個,後面的會覆蓋掉前面的,就下來計算了hash值,在indexFor()
裡面就一句話return h & (length-1);
,這裡是獲取到其索引值,這個索引值用來建立散列表的索引,關於散列表,使用一張百度百科的圖來說明
for迴圈裡面,會遍歷整個table,如果hash值和key都相同,那麼會覆蓋之前的key,並返回那個key所對應的值,也就是說此時是沒有新增成功的,那麼在hash值不等或者key不等的情況下,會呼叫addEntry()
方法,向散列表中新增,然後返回null
那麼在addEntry()
裡面也就是新增元素的方法了
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
在這裡,計算了大小,如果容量不足,那麼容量變為原來的兩倍,也就是說HashMap的大小為2的整次冪,同時重新計算hash和index,那麼接下來就是真正新增元素的地方了
那麼我們繼續看元素是怎麼被新增的吧
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMapEntry<K,V> e = table[bucketIndex];
table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
size++;
}
這裡傳入新值,並且完成了連結串列的指向,增加了size的大小,整個新增的流程就完成了
這是插在陣列元素位置的,後面連線起來
接下來看一看get()
方法
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
這裡可以看出,可以取key為null的value,然後呼叫getEntry()
查詢Entry
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
for (HashMapEntry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
這裡根據key計算出hash,然後再計算出index,去響應的table查詢匹配的HashMapEntry,找到則返回,沒找到返回空
然後判斷entry是否為null,為null返回null,不為空則返回value值
LinkedHashMap
LruCache類使用到了LinkedHashMap,那麼LinkedHashMap是怎麼實現知道新舊新增的元素的呢
LinkedHashMap本身繼承了HashMap,但是在資料結構上稍有不同,HashMap使用的是雜湊單向連結串列,而LinkedHashMap使用的是雜湊雙向迴圈連結串列
private static class LinkedHashMapEntry<K,V> extends HashMapEntry<K,V> {
LinkedHashMapEntry<K,V> before, after;
LinkedHashMapEntry(int hash, K key, V value, HashMapEntry<K,V> next) {
super(hash, key, value, next);
}
private void remove() {
before.after = after;
after.before = before;
}
private void addBefore(LinkedHashMapEntry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
void recordRemoval(HashMap<K,V> m) {
remove();
}
}
這裡主要看get()
方法
public V get(Object key) {
LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
注意到這裡呼叫了recordAccess()
,而這個方法的實現就比較有意思了
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
這裡的if判斷條件,我們回到LruCache,發現在給map初始化的時候,傳遞的引數為new LinkedHashMap<K, V>(0, 0.75f, true)
,也就是說這裡的accessOrder為真,真的意思就是要按照新舊排序,這裡呼叫了remove,那麼在remove裡面做了啥呢
private void remove() {
before.after = after;
after.before = before;
}
可以看到,在這個方法裡面就是將當前元素斷鏈了
然後還呼叫了addBefore()
方法,這又是為何
private void addBefore(LinkedHashMapEntry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
這裡將斷鏈的節點放到最末尾,然後和頭節點連起來了,那麼這樣每次get()
的元素都會到最末尾,header的after就是最老的和最不常用的節點了,在LruCache自動釋放記憶體時就是從這開始釋放的,保證常用常駐
那麼接下來再看看put方法,這裡可以看到只是繼承了HashMap的get方法,那麼在哪裡修改新增的呢
void addEntry(int hash, K key, V value, int bucketIndex) {
LinkedHashMapEntry<K,V> eldest = header.after;
if (eldest != header) {
boolean removeEldest;
size++;
try {
removeEldest = removeEldestEntry(eldest);
} finally {
size--;
}
if (removeEldest) {
removeEntryForKey(eldest.key);
}
}
super.addEntry(hash, key, value, bucketIndex);
}
可以看見這裡重寫了addEntry()
方法,但裡面並沒有具體的建立,在這裡的removeEldestEntry()
也是直接返回false了
所以又重寫了createEntry()
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMapEntry<K,V> old = table[bucketIndex];
LinkedHashMapEntry<K,V> e = new LinkedHashMapEntry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
可以看到這裡也呼叫了addBefore()
,也是加在了最後面,也就與header.before連線起來了
綜合分析得出結論:LinkedHashMap不斷調整元素位置,使得header.after為最不常用或者最先加入的元素,方便刪除的時候直接移除
樹
樹當中,研究最多的就是二叉樹
二叉樹
二叉樹的性質:
性質1:在二叉樹的第i層上至多有2i-1個結點(i>=1)
性質2:深度為k的二叉樹至多有2k-1個結點(k>=1)
性質3:對任何一顆二叉樹T,如果其終端結點數為n0,度為2的結點數為n2,則n0 = n2+1
性質4:具有n個結點的完全二叉樹深度為[log2n]+1 ([x]表示不大於x的最大整數)
性質5:如果對一顆有n個結點的完全二叉樹(其深度為[log2(n+1)])的結點按層序編號(從第1層到第[log2(n+1)]層,每層從左到右),對任意一個結點i(1<=i<=n)有:
1).如果i=1,則結點i是二叉樹的根,無雙親;如果i>1,則其雙親是結點[i/2]
2).如果2i>n,則結點i無左孩子(結點i為葉子結點);否則其左孩子是結點2i
3).如果2i+1>n,則結點i無右孩子;否則其右孩子是結點2i+1
二叉樹高度和節點數
- 二叉樹的高度
public int getHeight(){
return getHeight(root);
}
private int getHeight(TreeNode node) {
if(node == null){
return 0;
}else{
int i = getHeight(node.leftChild);
int j = getHeight(node.rightChild);
return (i < j) ? j + 1 : i + 1;
}
}
- 二叉樹的結點數
public int getSize(){
return getSize(root);
}
private int getSize(TreeNode node) {
if(node == null){
return 0;
}else{
return 1 + getSize(node.leftChild) + getSize(node.rightChild);
}
}
二叉樹的遍歷
- 前序遍歷
規則是若二叉樹為空,則空操作返回,否則先訪問跟結點,然後前序遍歷左子樹,再前序遍歷右子樹
public void preOrder(TreeNode node){
if(node == null){
return;
}else{
System.out.println("preOrder data:" + node.getData());
preOrder(node.leftChild);
preOrder(node.rightChild);
}
}
- 中序遍歷
規則是若樹為空,則空操作返回,否則從根結點開始(注意並不是先訪問根結點),中序遍歷根結點的左子樹,然後是訪問根結點,最後中序遍歷右子樹
public void midOrder(TreeNode node){
if(node == null){
return;
}else{
midOrder(node.leftChild);
System.out.println("midOrder data:" + node.getData());
midOrder(node.rightChild);
}
}
- 後序遍歷
規則是若樹為空,則空操作返回,否則從左到右先葉子後結點的方式遍歷訪問左右子樹,最後是訪問根結點
public void postOrder(TreeNode node){
if(node == null){
return;
}else{
postOrder(node.leftChild);
postOrder(node.rightChild);
System.out.println("postOrder data:" + node.getData());
}
}
- 層序遍歷
規則是若樹為空,則空操作返回,否則從樹的第一層,也就是根結點開始訪問,從上而下逐層遍歷,在同一層,按從左到右的順序對結點逐個訪問
生成二叉樹
public TreeNode createBinaryTree(int size, ArrayList<String> datas) {
if(datas.size() == 0) {
return null;
}
String data = datas.get(0);
TreeNode node;
int index = size - datas.size();
if(data.equals("#")) {
node = null;
datas.remove(0);
return node;
}
node = new TreeNode(index, data);
if(index == 0) {
root = node;
}
datas.remove(0);
node.leftChild = createBinaryTree(size, datas);
node.rightChild = createBinaryTree(size, datas);
return node;
}
查詢二叉樹(搜尋二叉樹)
在二叉樹中,左節點小於根節點,有節點大於根節點的的二叉樹成為查詢二叉樹,也叫做搜尋二叉樹
public class SearchBinaryTree {
public static void main(String[] args) {
SearchBinaryTree tree = new SearchBinaryTree();
int[] intArray = new int[] {50, 27, 30, 60, 20, 40};
for (int i = 0; i < intArray.length; i++) {
tree.putTreeNode(intArray[i]);
}
tree.midOrder(root);
}
private static TreeNode root;
public SearchBinaryTree() {}
//建立查詢二叉樹,新增節點
public TreeNode putTreeNode(int data) {
TreeNode node = null;
TreeNode parent = null;
if (root == null) {
node = new TreeNode(0, data);
root = node;
return root;
}
node = root;
while(node != null) {
parent = node;
if(data > node.data) {
node = node.rightChild;
}else if(data < node.data) {
node = node.leftChild;
}else {
return node;
}
}
//將節點新增到相應位置
node = new TreeNode(0, data);
if(data < parent.data) {
parent.leftChild = node;
}else {
parent.rightChild = node;
}
node.parent = parent;
return root;
}
//驗證是否正確
public void midOrder(TreeNode node){
if(node == null){
return;
} else {
midOrder(node.leftChild);
System.out.println("midOrder data:" + node.getData());
midOrder(node.rightChild);
}
}
class TreeNode{
private int key;
private int data;
private TreeNode leftChild;
private TreeNode rightChild;
private TreeNode parent;
public TreeNode(int key, int data) {
super();
this.key = key;
this.data = data;
leftChild = null;
rightChild = null;
parent = null;
}
public int getKey() {
return key;
}
public void setKey(int key) {
this.key = key;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public TreeNode getLeftChild() {
return leftChild;
}
public void setLeftChild(TreeNode leftChild) {
this.leftChild = leftChild;
}
public TreeNode getRightChild() {
return rightChild;
}
public void setRightChild(TreeNode rightChild) {
this.rightChild = rightChild;
}
public TreeNode getParent() {
return parent;
}
public void setParent(TreeNode parent) {
this.parent = parent;
}
}
}
刪除節點
//刪除節點
public void deleteNode(int key) throws Exception {
TreeNode node = searchNode(key);
if(node == null) {
throw new Exception("can not find node");
}else {
delete(node);
}
}
private void delete(TreeNode node) throws Exception {
if(node == null) {
throw new Exception("node is null");
}
TreeNode parent = node.parent;
//刪除的節點無左右節點
if(node.leftChild == null && node.rightChild == null) {
if(parent.leftChild == node) {
parent.leftChild = null;
}else {
parent.rightChild = null;
}
return;
}
//被刪除的節點有左節點無右節點
if(node.leftChild != null && node.rightChild == null) {
if(parent.leftChild == node) {
parent.leftChild = node.leftChild;
}else {
parent.rightChild = node.leftChild;
}
return;
}
//被刪除的節點無左節點有右節點
if(node.leftChild == null && node.rightChild != null) {
if(parent.leftChild == node) {
parent.leftChild = node.rightChild;
}else {
parent.rightChild = node.rightChild;
}
return;
}
//被刪除節點既有左結點又有右節點
TreeNode next = getNextNode(node);
delete(next);
node.data = next.data;
}
private TreeNode getNextNode(TreeNode node) {
if(node == null) {
return null;
}
if(node.rightChild != null) {
return getMinTreeNode(node.rightChild);
}else {
TreeNode parent = node.parent;
while(parent != null && node == parent.rightChild) {
node = parent;
parent = parent.parent;
}
return parent;
}
}
//找某節點的最小關鍵節點
private TreeNode getMinTreeNode(TreeNode node) {
if(node == null) {
return null;
}
while(node.leftChild != null) {
node = node.leftChild;
}
return node;
}
private TreeNode searchNode(int key) {
TreeNode node = root;
if(node == null) {
return null;
}
while(node != null && key != node.data) {
if(key < node.data) {
node = node.leftChild;
}else {
node = node.rightChild;
}
}
return node;
}
圖
圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示為:G(V,E),其中G表示一個圖,V是圖G中定點的集合,E是圖G中邊的集合
圖中的資料元素稱之為頂點,任意兩個頂點之間都可能有關係,頂點之間的邏輯關係用邊來表示,邊集可以為空
無向圖和有向圖
- 無向圖
無向邊:若頂點vi到vj之間的邊沒有方向,則稱這條邊為無向邊(Edge),用無序偶對(vi,vj)來表示,如果圖中任意兩個頂點之間的邊都是無向邊,則稱該圖為無向圖(Undirected Graphs)
在無向圖中,如果任意兩個頂點之間的邊都存在,那麼該圖稱為無向完全圖
- 有向圖
有向邊:若頂點vi到vj的邊有方向,則稱這條邊為有向邊,也稱之為弧(Arc),用有序偶對<vi,vj>來表示,如果圖中任意兩個頂點之間的邊都是有向邊,則稱該圖為有向圖(Directed Graphs)
在有向圖中,如果任意兩個頂點之間都存在方向互為相反的兩條弧,那麼該圖稱為有向完全圖
圖的權
有些圖的邊或者弧具有與他相關的數字,這種與圖的邊或弧相關的數叫做權
連通圖
在無向圖G中,如果從頂點v到頂點v'有路徑,則稱v和v'是連通的,如果對於圖中任意兩個頂點vi,vj∈E,vi和vj都是連通的,則稱G是連通圖(Connected Graph)
度
無向圖頂點的邊數叫度,有向圖頂點的邊數叫出度和入度
圖的儲存結構
鄰接矩陣
圖的鄰接矩陣(Adjacency Matrix)儲存方式是用兩個陣列來表示圖,一個一維陣列儲存圖中的頂點資訊,一個二維陣列(稱為鄰接矩陣)儲存圖中的-邊或弧資訊
- 鄰接矩陣
- 帶權鄰接矩陣
- 浪費的鄰接矩陣
鄰接表
講到了一種孩子表示法,將結點存入陣列,並對結點的孩子進行鏈式儲存,不管有多少孩子,也不會存在空間浪費問題,這個思路同樣適用於圖的儲存。我們把這種陣列與連結串列相結合的儲存方法稱為鄰接表
- 無向圖的鄰接表:
- 有向圖的鄰接表:
- 有向圖的逆鄰接表
- 帶權值鄰接表
鄰接矩陣的程式碼實現(Java)
public class Graph {
private int vertexSize; //頂點數量
private int[] vertexs; //頂點陣列
private int[][] matrix; //邊陣列
private static final int MAX_WEIGHT = 1000; //非連線頂點權值
public Graph(int vertexSize) {
this.vertexSize = vertexSize;
this.vertexs = new int[vertexSize];
this.matrix = new int[vertexSize][vertexSize];
//頂點初始化
for (int i = 0; i < vertexSize; i++) {
vertexs[i]= i;
}
}
//計算某頂點出度
public int getOutDegree(int index) {
int degree = 0;
for (int i = 0; i < matrix[index].length; i++) {
int weight = matrix[index][i];
if(weight != 0 && weight != MAX_WEIGHT) {
degree++;
}
}
return degree;
}
//獲取兩頂點之間的權值
public int getWeight(int v1, int v2) {
int weight = matrix[v1][v2];
return weight == 0 ? 0 : (weight == MAX_WEIGHT ? -1 : weight);
}
public static void main(String[] args) {
Graph graph = new Graph(5);
int[] a1 = new int[] {0,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,6};
int[] a2 = new int[] {9,0,3,MAX_WEIGHT,MAX_WEIGHT};
int[] a3 = new int[] {2,MAX_WEIGHT,0,5,MAX_WEIGHT};
int[] a4 = new int[] {MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,0,1};
int[] a5 = new int[] {MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,0};
graph.matrix[0] = a1;
graph.matrix[1] = a2;
graph.matrix[2] = a3;
graph.matrix[3] = a4;
graph.matrix[4] = a5;
int degree = graph.getOutDegree(0);
int weight = graph.getWeight(2, 0);
System.out.println("degree:" + degree);
System.out.println("weight:" + weight);
}
}
圖的遍歷
圖的遍歷和樹的遍歷類似,從某一頂點出發遍歷圖中其餘頂點,且使得每個頂點有且只有一次訪問,這一過程叫做圖的遍歷
深度優先遍歷
深度優先遍歷(Depth_First_Search),也稱為深度優先搜素,簡稱DFS
他從圖中某個頂點v出發,訪問此頂點,然後從v的未被訪問的鄰接點出發深度優先遍歷圖,直到圖中所有和v有路徑相通的頂點都被訪問到
下面是深度優先遍歷的虛擬碼(C)
typedef int Boolean;
Boolean visited[Max];
void DFS(MGraph G,int i){
int j;
visited[i] = TRUE;
printf("%c", G.vexs[i]);
for(j = 0; j < G.numVertexes; j++){
if(G.arc[i][j] == 1&&!visited[j]){
DFS(G,j);
}
}
}
void DFSTraverse(MGraph G){
int i;
for(i = 0 ;i<G.numVertexes;i++){
visited[i] = FALSE;
}
for(i = 0; i < G.numVertexes; i++){
if(!visited[i]){
DFS(G,i);
}
}
}
有了思路就可以寫出Java程式碼了
private boolean[] isVisited; //是否遍歷過
//獲取某個頂點的連線點:其實就是遍歷一行,獲取不為零且不為MAX_WEIGHT的第一個位置
public int getFirstNeighbor(int v) {
for (int j = 0; j < vertexSize; j++) {
if(matrix[v][j] > 0 && matrix[v][j] < MAX_WEIGHT) {
return j;
}
}
return -1;
}
//根據前一個鄰接點的下標獲得下一個鄰接點:就是找到一行中第二個有意義的位置
//v代表要找的頂點,也就是要找的那一行,index代表從這個位置往後開始找
private int getNextNeighbor(int v, int index) {
for (int j = index + 1; j < vertexSize; j++) {
if(matrix[v][j] > 0 && matrix[v][j] < MAX_WEIGHT) {
return j;
}
}
return -1;
}
//圖的深度優先遍歷
private void depthFirstSearch(int v) {
System.out.println("訪問 " + v + " 頂點");
isVisited[v] = true;
int w = getFirstNeighbor(v);
while(w != -1) {
if(!isVisited[w]) {
//遍歷該節點
depthFirstSearch(w);
}
w = getNextNeighbor(v, w);
}
}
//深度優先遍歷呼叫:直接使用depthFirstSearch(i)會造成有些頂點可能無法被訪問
public void depthFirstSearch() {
isVisited = new boolean[vertexSize];
for (int i = 0; i < vertexSize; i++) {
if(!isVisited[i]) {
depthFirstSearch(i);
}
}
isVisited = new boolean[vertexSize];
}
廣度優先遍歷
廣度優先遍歷類似於樹的層序遍歷,一級一級直到遍歷結束
廣度優先遍歷一般採用佇列儲存頂點
下面是廣度優先遍歷的虛擬碼
//鄰接矩陣的廣度遍歷演算法
void SFSTraverse(MGraph G)
{
int i, j;
Queue Q;
for (int i = 0; i < G.numVertexes; i ++)
{
visited[i] = FALSE;
}
InitQueue(&Q); //初始化輔助佇列
for (i = 0; i < G.numVertexes; i++) //對每個頂點做迴圈
{
if (!visited[i]) //若是為訪問過就處理
{
visited[i] = TRUE; //設定當前頂點已訪問過
printf("%c", G.vexs[i]); //列印頂點
EnQueue(&Q, i); //頂點入佇列
while (!QueueEmpty(Q)) //佇列不為空
{
DeQueue(&Q, &i); //佇列元素出列,賦值給i
for (int j = 0; j < G.numVertexes; j++)
{
//判斷其他頂點若與當前頂點存在邊且未訪問過
if (G.arc[i][j] == 1 && !visited[j])
{
visited[j] = TRUE; //設定當前頂點已訪問過
printf("%c", G.vexs[j]); //列印頂點
EnQueue(&Q, j); //頂點入佇列
}
}
}
}
}
}
有了思想就很容易寫出java程式碼了
//圖的廣度優先遍歷
public void broadFirstSearch(int v) {
int u,w;
LinkedList<Integer> queue = new LinkedList<>();
System.out.println("訪問 " + v + " 頂點");
isVisited[v] = true;
queue.add(v);
while(!queue.isEmpty()) {
u = (Integer)(queue.removeFirst()).intValue();
w = getFirstNeighbor(u);
while(w != -1) {
if(!isVisited[w]) {
System.out.println("訪問 " + w + " 頂點");
isVisited[w] = true;
queue.add(w);
}
w = getNextNeighbor(u, w);
}
}
}
//廣度優先遍歷,和深度遍歷一樣,可能存在訪問不到的位置
public void broadFirstSearch() {
isVisited = new boolean[vertexSize];
for (int i = 0; i < vertexSize; i++) {
if(!isVisited[i]) {
broadFirstSearch(i);
}
}
}
最小生成樹
問題引出
解決方案
一個連通圖的生成樹是一個極小的連通子圖,它含有圖中全部的頂點,但只有足以構成一棵樹的n-1條邊。我們把構造連通網的最小代價生成樹。稱為最小生成樹
找連通網的最小生成樹,經典的有兩種演算法,普里姆演算法和克魯斯卡爾演算法
普里姆演算法
先構造鄰接矩陣
普里姆演算法的C語言實現
void MiniSpanTree_Prim(MGraph G){
int min, i, j, k;
int adjvex[MAXVEX]; //儲存相關頂點下標
int lowcost[MAXVEX]; //儲存相關頂點間邊的權值
lowcost[0] = 0; //初始化第一個權值為0,既v0加入生成樹
adjvex[0] = 0; //初始化第一個頂點下標為0
for(i = 1; i < G.numVertexes; i++){ //迴圈除下標為0外的全部頂點
lowcost[i] = G.arc[0][i]; //將v0頂點與之有邊的權值存入陣列
adjvex[i] = 0; //初始化都為v0的下標
}
for(i = 1; i < G.numVertexes; i++){
min = INFINITY; //初始化最小權值為無窮數,通常設定為不可能的大數字如65535等
j = 1;
k = 0;
while(j < G.numVertexes){ //迴圈全部頂點
if(lowcost[j]!=0&&lowcost[j]<min){ //如果權值不為0,且權值小於min
min = lowcost[j];//則讓當前權值成為最小值
k = j; //將當前最小值的下標存入k
}
j++;
}
printf("(%d,%d)", adjvex[k],k); //列印當前頂點邊中權值最小邊
lowcost[k] = 0; //將當前頂點的權值設定為0,表示此頂點已經完成任務
for(j = 1; j < G.numVertexes; j++){//迴圈所有頂點
if(lowcost[j] != 0 && G.arc[k][j] < lowcost[j]){ //若下標為k頂點各邊權值小於此前這些頂點
//未被加入生成樹權值
lowcost[j] = G.arc[k][j]; //將較小權值存入lowcost
adjvex[j] = k;//將下標為k的頂點存入adjvex
}
}
}
}
改為Java演算法實現
//普里姆最小生成樹
public void prim() {
int[] lowcost = new int[vertexSize]; //最小代價頂點權值的陣列,為0表示已經獲取最小權值
int[] adjvex = new int[vertexSize]; //頂點權值下標
int min, minId, sum = 0;
//假定第一行距離為到任意頂點最短距離
for (int i = 0; i < vertexSize; i++) {
lowcost[i] = matrix[0][i];
}
for (int i = 1; i < vertexSize; i++) {
min = MAX_WEIGHT;
minId = 0;
//迴圈查詢到一行中最小的有效權值
for (int j = 1; j < vertexSize; j++) {
//有效權值
if(lowcost[j] < min && lowcost[j] > 0) {
min = lowcost[j];
minId = j;
}
}
System.out.println("頂點:" + adjvex[minId] + ",權值:" + min);
sum += min;
lowcost[minId] = 0;
for (int j = 0; j < vertexSize; j++) {
if(lowcost[j] != 0 && matrix[minId][j] < lowcost[j]) {
lowcost[j] = matrix[minId][j];
adjvex[j] = minId;
}
}
}
System.out.println("sum = " + sum);
}
測試用例
public static void main(String[] args) {
Graph graph = new Graph(9);
int NA = MAX_WEIGHT;
int[] a0 = new int[] {0,10,NA,NA,NA,11,NA,NA,NA};
int[] a1 = new int[] {10,0,18,NA,NA,NA,16,NA,12};
int[] a2 = new int[] {NA,NA,0,22,NA,NA,NA,NA,8};
int[] a3 = new int[] {NA,NA,22,0,20,NA,NA,16,21};
int[] a4 = new int[] {NA,NA,NA,20,0,26,NA,7,NA};
int[] a5 = new int[] {11,NA,NA,NA,26,0,17,NA,NA};
int[] a6 = new int[] {NA,16,NA,NA,NA,17,0,19,NA};
int[] a7 = new int[] {NA,NA,NA,16,7,NA,19,0,NA};
int[] a8 = new int[] {NA,12,8,21,NA,NA,NA,NA,0};
graph.matrix[0] = a0;
graph.matrix[1] = a1;
graph.matrix[2] = a2;
graph.matrix[3] = a3;
graph.matrix[4] = a4;
graph.matrix[5] = a5;
graph.matrix[6] = a6;
graph.matrix[7] = a7;
graph.matrix[8] = a8;
graph.prim();
}
輸出結果
頂點:0,權值:10
頂點:0,權值:11
頂點:1,權值:12
頂點:8,權值:8
頂點:1,權值:16
頂點:6,權值:19
頂點:7,權值:7
頂點:7,權值:16
sum = 99
克魯斯卡爾演算法
克魯斯卡爾演算法與普里姆演算法的區別在於,後者強調的是頂點,而前者強調的是邊
C語言實現
typedef struct{
int begin;
int end;
int weight;
}Edge;
void MiniSpanTree_Kruskal(MGraph G){
int i, n, m;
Edge edges[MAXEDGE];
int parent[MAXEDGE];
for(i = 0; i < G.numEdges; i++){
n = Find(parent,edges[i].begin);
m = Find(parent,edges[i].end);
if(n != m){
parent[n] = m;
printf("(%d,%d) %d",edges[i].begin,edges[i].end,edges[i].weight);
}
}
}
int Find(int *parent, int f){
while(parent[f] > 0){
f = parent[f];
}
return f;
}
改為Java實現
首先要構造邊的圖實現
public class GraphKruskal {
private Edge[] edges; //構建邊結構陣列
private int edgeSize; //邊數量
public GraphKruskal(int edgeSize) {
this.edgeSize = edgeSize;
edges = new Edge[edgeSize];
}
//從小到大排列
public void createEdgeArray() {
Edge edge0 = new Edge(4, 7, 7);
Edge edge1 = new Edge(2, 8, 8);
Edge edge2 = new Edge(0, 1, 10);
Edge edge3 = new Edge(0, 5, 11);
Edge edge4 = new Edge(1, 8, 12);
Edge edge5 = new Edge(3, 7, 16);
Edge edge6 = new Edge(1, 6, 16);
Edge edge7 = new Edge(5, 6, 17);
Edge edge8 = new Edge(1, 2, 18);
Edge edge9 = new Edge(6, 7, 19);
Edge edge10 = new Edge(3, 4, 20);
Edge edge11 = new Edge(3, 8, 21);
Edge edge12 = new Edge(2, 3, 22);
Edge edge13 = new Edge(3, 6, 24);
Edge edge14 = new Edge(4, 5, 26);
edges[0] = edge0;
edges[1] = edge1;
edges[2] = edge2;
edges[3] = edge3;
edges[4] = edge4;
edges[5] = edge5;
edges[6] = edge6;
edges[7] = edge7;
edges[8] = edge8;
edges[9] = edge9;
edges[10] = edge10;
edges[11] = edge11;
edges[12] = edge12;
edges[13] = edge13;
edges[14] = edge14;
}
class Edge{
private int begin;
private int end;
private int weight;
public Edge(int begin, int end, int weight) {
this.begin = begin;
this.end = end;
this.weight = weight;
}
public int getBegin() {
return begin;
}
public void setBegin(int begin) {
this.begin = begin;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
}
public void miniSpanTreeKruskal() {
int m, n, sum = 0;
int[] parent = new int[edgeSize];//以起點為下標,值為終點的陣列
for (int i = 0; i < edgeSize; i++) {
parent[i] = 0;
}
for (int i = 0; i < edgeSize; i++) {
n = find(parent,edges[i].begin);
m = find(parent,edges[i].end);
//保證不出現迴環
if(n != m) {
parent[n] = m;
System.out.println("起點:" + edges[i].begin + ",終點:"
+ edges[i].end + ",權值:" + edges[i].weight);
sum += edges[i].weight;
}
}
System.out.println("sum = " + sum);
}
//查詢陣列,獲取非迴環的值,也就是說這裡找到的是值為0的位置
private int find(int[] parent, int value) {
while(parent[value] > 0) {
value = parent[value];
}
return value;
}
測試
public static void main(String[] args) {
GraphKruskal gKruskal = new GraphKruskal(15);
gKruskal.createEdgeArray();
gKruskal.miniSpanTreeKruskal();
}
輸出結果
起點:4,終點:7,權值:7
起點:2,終點:8,權值:8
起點:0,終點:1,權值:10
起點:0,終點:5,權值:11
起點:1,終點:8,權值:12
起點:3,終點:7,權值:16
起點:1,終點:6,權值:16
起點:6,終點:7,權值:19
sum = 99
最短路徑
最短路徑在路徑規劃時候是經常使用到的
網轉鄰接矩陣
計算最短路徑,採用迪傑斯特拉演算法
#define MAXVEX 9
#define INFINITY 65535
typedef int Pathmatirx[MAXVEX]; //用於儲存最短路徑下標的陣列
typedef int ShortPathTable[MAXVEX]; //用於儲存到各點最短路徑的權值和
//Dijkstra演算法,求有向網G的v0頂點到其餘頂點v最短路徑P[v]及帶權長度D[v]
//P[v]的值為前驅頂點下標,D[v]表示v0到v的最短路徑長度和
void ShortestPath_Dijkstra(MGraph G,int v0,Pathmatrix *P,ShortPathTable *D){
int v,w,k,min;
int final[MAXVEX]; //final[w] = 1表示求得頂點v0至vw的最短路徑
for(v = 0; v < G.numVertexes; v++){ //初始化資料
final[v] = 0; //全部頂點初始化為未知最短路徑狀態
(*D)[v] = G.matirx[v0][v]; //將與v0點有連線的頂點加上權值
(*P)[v] = 0; //初始化路徑陣列為0
}
(*D)[v0] = 0; //v0至v0的路徑為0
final[v0] = 1; //v0至v0不需要求路徑
//開始主迴圈,每次求得v0到某個v頂點的最短路徑
for(v = 1; v < G.numVertexes; v++){
min = INFINITY; //當前所知離v0頂點的最近距離
for(w = 0; w < G.numVertexes; w++){ //尋找離v0最近的頂點
if(!final[w] && (*D)[w] < min){
k = w;
min = (*D)[w]; //w頂點離v0頂點更近
}
}
final[k] = 1; //將目前找到的最近的頂點置為1
for(w = 0; w < G.numVertexes; w++){ //修正當前最短路徑與距離
//如果經過v頂點的路徑比現在這條路徑的長度短的話
if(!final[w] && (min + G.matirx[k][w] < (*D)[w])){
//說明找到了更短的路徑,修改D[w]和P[w]
(*D)[w] = min + G.matirx[k][w]; //修改當前路徑長度
(*P)[w] = k;
}
}
}
}
轉化為Java
public class Dijkstra {
private int MAXVEX;
private int MAX_WEIGHT;
private boolean isGetPath[];
private int shortTablePath[];
public void shortesPathDijkstra(Graph graph) {
int min, k = 0;
MAXVEX = graph.getVertexSize();
MAX_WEIGHT = Graph.MAX_WEIGHT;
shortTablePath = new int[MAXVEX];
isGetPath = new boolean[MAXVEX];
//初始化,拿到第一行位置
for (int v = 0; v < graph.getVertexSize(); v++) {
shortTablePath[v] = graph.getMatrix()[0][v];
}
//從V0開始,自身到自身的距離為0
shortTablePath[0] = 0;
isGetPath[0] = true;
for (int v = 1; v < graph.getVertexSize(); v++) {
min = MAX_WEIGHT;
for (int w = 0; w < graph.getVertexSize(); w++) {
//說明v和w有焦點
if(!isGetPath[w] && shortTablePath[w] < min) {
k = w;
min = shortTablePath[w];
}
}
isGetPath[k] = true;
for (int u = 0; u < graph.getVertexSize(); u++) {
if(!isGetPath[u] && (min + graph.getMatrix()[k][u]) < shortTablePath[u]) {
shortTablePath[u] = min + graph.getMatrix()[k][u];
}
}
}
for (int i = 0; i < shortTablePath.length; i++) {
System.out.println("V0到V" + i + "的最短路徑為:" + shortTablePath[i]);
}
}
public static void main(String[] args) {
Graph graph = new Graph(9);
graph.createGraph();
Dijkstra dijkstra = new Dijkstra();
dijkstra.shortesPathDijkstra(graph);
}
}
測試資料
public class Graph {
private int vertexSize; //頂點數量
private int[] vertexs; //頂點陣列
private int[][] matrix; //邊陣列
public static final int MAX_WEIGHT = 1000; //非連線頂點權值
public Graph(int vertexSize) {
this.vertexSize = vertexSize;
this.vertexs = new int[vertexSize];
this.matrix = new int[vertexSize][vertexSize];
//頂點初始化
for (int i = 0; i < vertexSize; i++) {
vertexs[i]= i;
}
}
public int getVertexSize() {
return vertexSize;
}
public int[][] getMatrix() {
return matrix;
}
public void createGraph(){
int [] a1 = new int[]{0,1,5,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT};
int [] a2 = new int[]{1,0,3,7,5,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT};
int [] a3 = new int[]{5,3,0,MAX_WEIGHT,1,7,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT};
int [] a4 = new int[]{MAX_WEIGHT,7,MAX_WEIGHT,0,2,MAX_WEIGHT,3,MAX_WEIGHT,MAX_WEIGHT};
int [] a5 = new int[]{MAX_WEIGHT,5,1,2,0,3,6,9,MAX_WEIGHT};
int [] a6 = new int[]{MAX_WEIGHT,MAX_WEIGHT,7,MAX_WEIGHT,3,0,MAX_WEIGHT,5,MAX_WEIGHT};
int [] a7 = new int[]{MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,3,6,MAX_WEIGHT,0,2,7};
int [] a8 = new int[]{MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,9,5,2,0,4};
int [] a9 = new int[]{MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,MAX_WEIGHT,7,4,0};
matrix[0] = a1;
matrix[1] = a2;
matrix[2] = a3;
matrix[3] = a4;
matrix[4] = a5;
matrix[5] = a6;
matrix[6] = a7;
matrix[7] = a8;
matrix[8] = a9;
}
}
拓撲排序
在一個表示工程的有向圖中,用頂點表示活動,用弧表示活動之間的優先關係,這樣的有向圖為頂點表示活動的網,稱之為AOV網(Activity On Vertex Network)
設G=(V,E)是一個具有n個頂點的有向圖,V中的頂點序列v1,v2,···,vn滿足若從頂點vi到vj有一條路徑,則在頂點序列中頂點vi必在頂點vj之前,則我們稱這樣的頂點序列為一個拓撲序列
實現拓撲排序
public class GraphTopologic {
private int numVertexes;
private VertexNode[] adjList;//鄰接頂點的一維陣列
public GraphTopologic(int numVertexes){
this.numVertexes = numVertexes;
}
//邊表頂點
class EdgeNode{
private int adjVert; //下標
private EdgeNode next;
private int weight;
public EdgeNode(int adjVert) {
this.adjVert = adjVert;
}
}
//鄰接頂點
class VertexNode{
private int in; //入度
private String data;
private EdgeNode firstEdge;
public VertexNode(int in, String data) {
this.in = in;
this.data = data;
}
}
private void createGraph(){
VertexNode node0 = new VertexNode(0,"v0");
VertexNode node1 = new VertexNode(0,"v1");
VertexNode node2 = new VertexNode(2,"v2");
VertexNode node3 = new VertexNode(0,"v3");
VertexNode node4 = new VertexNode(2,"v4");
VertexNode node5 = new VertexNode(3,"v5");
VertexNode node6 = new VertexNode(1,"v6");
VertexNode node7 = new VertexNode(2,"v7");
VertexNode node8 = new VertexNode(2,"v8");
VertexNode node9 = new VertexNode(1,"v9");
VertexNode node10 = new VertexNode(1,"v10");
VertexNode node11 = new VertexNode(2,"v11");
VertexNode node12 = new VertexNode(1,"v12");
VertexNode node13 = new VertexNode(2,"v13");
adjList = new VertexNode[numVertexes];
adjList[0] =node0;
adjList[1] =node1;
adjList[2] =node2;
adjList[3] =node3;
adjList[4] =node4;
adjList[5] =node5;
adjList[6] =node6;
adjList[7] =node7;
adjList[8] =node8;
adjList[9] =node9;
adjList[10] =node10;
adjList[11] =node11;
adjList[12] =node12;
adjList[13] =node13;
node0.firstEdge = new EdgeNode(11);node0.firstEdge.next = new Edge