死磕算法第二弹——栈、队列、链表(5)
本文整理來源 《輕松學算法——互聯網算法面試寶典》/趙燁 編著
鏈表其實也可以使用數組模擬
在C或者C++語言中有“指針”的概念。因為這個概念,鏈表在編程語言中能夠方便地得以發揮作用,但并不是所有的編程語言中都有這個指針概念,比如Java。雖然沒有“指針”這個概念,但是Java有“引用”的概念,類似于指針,可以用于完成鏈表的實現。
但若有的編程怨言沒有指針怎么辦呢?那么就可以用數組模擬。
靜態鏈表
鏈表有兩種:靜態列表和動態列表。平時所用的鏈表就是動態鏈表,空間都是需要時動態生成的;而靜態鏈表一般是使用數組來描述的鏈表,多數用于一些沒有指針的高級編程語言實現鏈表。
靜態列表的實現
一般來說,靜態鏈表的實現就是使用一段固定長度的數組,其中的每個元素需要有兩個部分組成:一個是data,用于記錄數據,一個是cur,用于記錄指向下一個節點的位置。在C語言中一般使用結構體,在Java中一般使用對象。如果不使用這種組合方式,那么也可以使用兩個數組:一個數組村data,一個數組村cur,讓同一個元素的data和cur的坐標保持一致。
其中靜態鏈表也可以模擬雙向鏈表,只需要再增加一個部分,用于記錄前面的一個節點的位置即可,但是這個靜態鏈表的維護成本更高。
靜態鏈表中的結構體:
public class Element<E>{private E data;private int cur;public E getData() {return data;}public void setData(E data) {this.data = data;}public int getCur() {return cur;}public void setCur(int cur) {this.cur = cur;}}動態鏈表主要包含創建、插入、刪除、遍歷操作;靜態鏈表是使用數組對動態鏈表進行模擬,當然也可以實現這種操作。
創建靜態鏈表
首先要創建。對于動態鏈表來說,創建操作并不復雜;但是對于靜態鏈表來說,創建操作會復雜一些。
首先需要三個標記,為了方便,我們直接采用三個變量來記錄,分別是頭指針標記、尾指針標記和未使用鏈表的頭指針標記。
未使用鏈表的頭指針標記的作用是什么?由于使用數組作為鏈表的存儲空間,所以鏈表的元素肯定會分布在數組的一些元素上去,但是對于鏈表進行插入操作時,會出現鏈表的順序和數組的下表順序不一致的情況,可能會導致數組中的一些連續空間為空,即未被使用,可能是這里的元素之前被刪除了。
所以我們需要把鏈表中未使用的空間通過一個鏈表串起來,這樣在需要分配空間時就可以把未使用鏈表的頭指針指向的元素給我們真正使用的鏈表了。
這個未使用的鏈表一般叫做備用鏈表,用于串聯那些沒有被使用的數組元素,為接下來的鏈表中的插入的操作使用,而在鏈表中刪除元素時,需要及時把要刪除的元素加入備用鏈表的頭部記錄下來。
所以在創建一個鏈表時需要把這個備用鏈表串一下。
public StaticLink(int capacity) {elements = new Element[capacity];unUsed = 0;for (int i = 0; i < capacity - 1; i++) {elements[i] = new Element<T>();elements[i].setCur(i + 1);}elements[capacity - 1] = new Element<T>();elements[capacity - 1].setCur(-1);}創建靜態鏈表時,所需要把數組的所有元素遍歷一下,用備用鏈表穿起來。我們在這里對除最后一個元素外的所有元素進行循環賦值,對最后一個元素需要賦不一樣的值,即把它的指針賦值為-1,用于說明沒有下一個元素了。
插入操縱
靜態鏈表的頭插入需要進行如下操作。
首先從備用鏈表頭中拿出一個元素,把備用鏈表的投標及指向備用鏈表的第二個元素的數組下標,然后把這個被拿出的元素的cur設為鏈表頭標記的位置,即當前鏈表中的第1個元素的數組下標,接著把頭元素的標記指向這個新數組元素下標。如此完成了對鏈表頭插入。
靜態鏈表的尾插入類似,首先從備用鏈表頭拿出一個元素,把備用鏈表的頭標記指向備用鏈表的第2個元素的數組下標,因為要作為鏈表的最后一個元素,因此把這個元素的cur設為空,接著把真是鏈表的為指針指向這個數組元素cur設為這個被拿出來的元素的下標,接著把為指針標記的值設為這個元素的數組下標,這樣就完成了鏈表的尾插入。
鏈表的中間插入需要對靜態鏈表進行遍歷,在遍歷到指定位置之后進行操作。備用鏈表同樣從頭袁旭作為鏈表的插入元素空間。而在鏈表遍歷到要插入元素的位置的前一個元素之后,把這個元素的cur設為新拿出來的備用元素的下標。而這個新拿出來的備用元素的cur同樣需要設置為前一個cur的值(也就是本該是新插入這個元素的下一個元素的數組下標),這樣就完成了鏈表的中間插入。
其實靜態鏈表的插入操作和動態鏈表的原理一樣,只是改變了一些操作步驟:一個需要處理靜態鏈表;一個是沒有指針,所以需要修改cur值為指定元素的數組下標。
刪除操作
靜態鏈表的刪除操作的原理類似于動態鏈表,需要改變前后元素的指針方向,同時把當前元素移出(在靜態鏈表中,就是在備用鏈表中進行頭插入)。
頭刪除時,需要把頭指針的值(head)設為原本鏈表的第2個元素的數組下標,同時需要把這個被刪除元素的cur設為備用鏈表的頭元素(unUseHead)數組下標,然后修改備用鏈表頭標記值為這個元素額數組下標。即刪除靜態鏈表時,除了需要把鏈表cur的關系設定好,還需要把這個被刪除的元素歸還到備用鏈表里,以備以后使用。
尾刪除時,需要把為指針(tail)前移,單由于不知道前一個元素的下標是什么(除非使用雙向鏈表),所以尾刪除和中間刪除一樣,都需要進行遍歷。在遍歷到要刪除的元素的前一個元素時,把這個元素的cur設為要刪除的元素的后一個元素的數組下標(如果沒有,則設置為空,這時刪除的這個元素肯定是鏈表最后的一個元素)。如果要刪除的元素時最后一個元素,那么需要修改微元素的標記(tail)的值。
遍歷操作
插入和刪除操作有時候需要遍歷到鏈表的制定位置。遍歷操作時不需要理會備用鏈表,只需要從頭標記(head)的值開始,找到元素數組的下標,再根據每個元素的cur去找下一個元素的數組坐標,知道cur為空為止,則說明遍歷完成。當我們需要遍歷指定的位置時,需要一個計數器來記錄我們遍歷了多少個元素。
靜態鏈表不管是插入還是刪除,其操作步驟都與動態鏈表類似,唯一需要額外處理的就是對備用鏈表的操作。進行插入操作時需要對備用鏈表進行頭刪除;而進行刪除操作時,則需要對備用鏈表進行航插入,這是需要額外維護的工作。
public class StaticLink<T> {private Element[] elements;private int unUsed;private int head;private int tail;private int size;public StaticLink(int capacity) {elements = new Element[capacity];unUsed = 0;for (int i = 0; i < capacity - 1; i++) {elements[i] = new Element<>();elements[i].setCur(i + 1);}elements[capacity - 1] = new Element<>();elements[capacity - 1].setCur(-1);}public void insert(T data, int index) {if (index == 0) {insertFirst(data);} else if (index == size) {insertLast(data);} else {checkFull();//獲取要插入的元素的前一個元素Element<T> preElement = get(index);//獲取一個未被使用的元素作為要插入的元素Element<T> unUsedElement = elements[unUsed];//記錄要插入元素的數組下標int temp = unUsed;//將要備用鏈表中拿出來的元素的數組下標設為備用鏈表頭unUsed = unUsedElement.getCur();//將要插入元素的指針設為原本前一個元素的指向的下標值unUsedElement.setCur(preElement.getCur());//將前一個元素的指針指向插入的元素下標preElement.setCur(temp);//賦值unUsedElement.setData(data);//鏈表長度+1size++;}}public void printAll() {Element<T> element = elements[head];System.out.println(element.getData());for (int i = 1; i < size; i++) {element = elements[element.getCur()];System.out.println(element.getData());}}public int size() {return size;}public Element<T> get(int index) {checkEmpty();Element<T> element = elements[head];for (int i = 0; i < index; i++) {element = elements[element.getCur()];}return element;}public void checkFull() {if (size == elements.length) {throw new IndexOutOfBoundsException("數組不夠長了");}}public void deleteFirst() {checkEmpty();Element deleteElement = elements[head];int temp = head;head = deleteElement.getCur();deleteElement.setCur(unUsed);unUsed = temp;size--;}public void deleteLast() {delete(size - 1);}public void delete(int index) {if (index == 0) {deleteFirst();} else {checkEmpty();Element pre = get(index - 1);int del = pre.getCur();Element deleteElement = elements[del];pre.setCur(deleteElement.getCur());if (index == size - 1) {tail = index - 1;}deleteElement.setCur(unUsed);unUsed = del;size--;}}public void checkEmpty() {if (size == 0) {throw new IndexOutOfBoundsException("鏈表為空");}}public void insertLast(T data) {checkFull();Element<T> unUsedElement = elements[unUsed];int temp = unUsed;unUsed = unUsedElement.getCur();elements[tail].setCur(temp);unUsedElement.setData(data);tail = temp;size++;}public void insertFirst(T data) {checkFull();Element<T> unUsedElement = elements[unUsed];int temp = unUsed;unUsed = unUsedElement.getCur();unUsedElement.setCur(head);unUsedElement.setData(data);head = temp;size++;}public class Element<V> {private V data;private int cur;public V getData() {return data;}public void setData(V data) {this.data = data;}public int getCur() {return cur;}public void setCur(int cur) {this.cur = cur;}}}測試代碼
public class StaticLinkTest {@Testpublic void main(){StaticLink<Integer> link = new StaticLink<>(10);link.insertFirst(2);link.insertFirst(1);link.insertLast(4);link.insertLast(5);link.insert(3,1);link.printAll();link.deleteFirst();link.deleteLast();link.delete(1);link.printAll();Assert.assertEquals(4,(int)link.get(1).getData());link.deleteFirst();link.deleteFirst();Assert.assertEquals(0,link.size());}}靜態列表的特點
靜態列表的大多數情況下和動態鏈表相似,但是靜態鏈表的實現方式當值鏈表失去了原有的優勢,而且操作變得更加復雜了,主要體現為與以下幾點。
由于靜態鏈表使用數組模擬的,所以空間是連續的,雖然鏈表在數組中可以不按照順序排列,但是對于整個存儲空間來說,還是需要連續的。
另外,由于用到數組模擬,所以我們在創建數組時需要初始化長度,鏈表本身的一個優點就是動態添加,但是靜態鏈表沒有辦法這樣添加。當鏈表的長度需要大于數組的長度時就無法實現數組模擬了,除非復制到一個更長的新數組,那是就需要考慮更多問題,比如串聯備用鏈表、更高性能消耗等。
這點和動態鏈表相似,在找一個元素時需要對鏈表進行遍歷。
由于執行操作需要額外維護一個備用鏈表,所以無論是插入還是刪除,都需要額外關心操作元素的動向,所以靜態鏈表操作比動態鏈表更復雜。
存在以上問題,所以靜態鏈表除了有助于我們分析問題,在很多語言中并不常用,尤其是現在不支持指針或者引用高級編程語言越來越少的情況下。但是,我們學習算法時,還是需要賬戶哦靜態鏈表的。
轉載于:https://www.cnblogs.com/mr-cc/p/8659860.html
總結
以上是生活随笔為你收集整理的死磕算法第二弹——栈、队列、链表(5)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Uva 442 - Matrix Cha
- 下一篇: Eclipse + Apache Axi