戀上數據結構與算法:複雜度分析(五)

文章目錄

(一)複雜度分析:ArrayList
(二)複雜度分析:LinkedList
(三)均攤複雜度
(四)ArrayList的縮容
(五)複雜度震盪

(一)複雜度分析:ArrayList

  • get(int index)方法的時間複雜度是O(1)
        @Override
        public E get(int index) {
            rangeCheck(index);
            //假設存放的是int類型,地址值=index*4+數組的首地址
            return elements[index];
        }
    
    數組的特點:隨機訪問(無論訪問哪個元素)速度非常快
  • set(int index, E element)方法的時間複雜度是O(1)
        @Override
        public E set(int index, E element) {
            rangeCheck(index);
            E old = elements[index];
            elements[index] = element;//存儲也是先計算地址,再存,也很快
            return old;
        }
    
  • add(int index, E element)方法的時間複雜度如下:
    O(n)的n是數據規模,此處的數據規模是size
    數組執行add方法需要向右挪動index之後的元素
    1. 最好情況複雜度(index=size):O(1)
    2. 最壞情況複雜度(index=0):O(n)
    3. 平均情況複雜度(0<index<size):O[(1+2+…+n)/n],相當於O(n/2),即O(n)
        @Override
        public void add(int index, E element) {
            if (element == null) return;//加上就不可以存儲null
            rangeCheckForAdd(index);
            ensureCapacity(size + 1);//capacity取值爲size+1,就是保證比size多一個,不怕不夠用
            for (int i = size; i > index; i--) {
                elements[i] = elements[i - 1];
            }
            elements[index] = element;
            size++;
        }
    
  • remove(int index)方法的時間複雜度如下:
    數組執行remove方法需要向左挪動index之後的元素
    1. 最好情況複雜度(index=size):O(1)
    2. 最壞情況複雜度(index=0):O(n)
    3. 平均情況複雜度(0<index<size): O(n)
        @Override
        public E remove(int index) {
            rangeCheck(index);
            E old = elements[index];
            for (int i = index + 1; i < size; i++) {
                elements[i - 1] = elements[i];
            }
            elements[--size] = null;
            return old;
        }
    

總結:數組增刪慢,查詢、修改快

(二)複雜度分析:LinkedList

  • get(int index)方法的時間複雜度如下:
    1. 最好情況複雜度:O(1)
    2. 最壞情況複雜度:O(n)
    3. 平均情況複雜度: O(n)
        @Override
        public E get(int index) {
            return node(index).element;
        }
    
        private Node<E> node(int index) {
            rangeCheck(index);
            Node<E> node = first;
            //從first開始,next index次 就可以找到要找的結點
            for (int i = 0; i < index; i++) {
                node = node.next;
            }
            return node;
        }
    
  • set(int index, E element)方法的時間複雜度如下:
    1. 最好情況複雜度:O(1)
    2. 最壞情況複雜度:O(n)
    3. 平均情況複雜度: O(n)
        @Override
        public E set(int index, E element) {
            Node<E> node = node(index);
            E old = node.element;
            node.element = element;
            return old;
        }
    
        private Node<E> node(int index) {
            rangeCheck(index);
            Node<E> node = first;
            //從first開始,next index次 就可以找到要找的結點
            for (int i = 0; i < index; i++) {
                node = node.next;
            }
            return node;
        }
    
  • add(int index, E element)方法的時間複雜度如下:
    網上常說的鏈表的插入時間複雜度是O(1)指的是插入那一瞬間的複雜度是O(1),整體的複雜度還是O(n),因爲同樣調用了node(int index)方法
    1. 最好情況複雜度:O(1)
    2. 最壞情況複雜度:O(n)
    3. 平均情況複雜度: O(n)
        @Override
        public void add(int index, E element) {
            rangeCheckForAdd(index);
            //要特殊處理index=0的情況
            if (index == 0) {
                first = new Node<>(element, first);
            } else {
                Node<E> prev = node(index - 1);
                prev.next = new Node<>(element, prev.next);
            }
            size++;
        }
    
  • remove(int index)方法的時間複雜度如下:
    網上常說的鏈表的刪除時間複雜度是O(1)指的是插入那一瞬間的複雜度是O(1),整體的複雜度還是O(n),因爲同樣調用了node(int index)方法
    1. 最好情況複雜度:O(1)
    2. 最壞情況複雜度:O(n)
    3. 平均情況複雜度: O(n)
        @Override
        public E remove(int index) {
            rangeCheck(index);
            Node<E> node = first;
            if (index == 0) {
                first = first.next;
            } else {
                Node<E> prev = node(index - 1);
                node = prev.next;
    //            prev.next = prev.next.next;
                prev.next = node.next;
            }
            size--;
            return node.element;
        }
    

注意:鏈表的增刪的瞬間,不用像數組那樣挪動元素,但是從整體上來看鏈表的時間複雜度還是O(n)

鏈表的優點省內存(用一個就創建一個,不像數組動態創建,1.5倍擴容)

(三)均攤複雜度

下面分析AbstractList類中add(E element)方法的時間複雜度
在這裏插入圖片描述
最好情況複雜度:O(1)
最壞情況複雜度:O(n)(容量不夠需要擴容時)
平均情況複雜度:O(1)(因爲大多數情況都是O(1),(1+1+1+1…+n)/n,所以平均下來也是O(1))
均攤複雜度:O(1)(絕大多數都是O(1),只有個別少數是O(n)的情況用均攤複雜度更合適)
在這裏插入圖片描述
什麼情況下適合使用均攤複雜度
經過連續的多次複雜度比較低的情況後,出現個別複雜度比較高的情況

(四)ArrayList的縮容

如果內存使用比較緊張,動態數組有比較多的剩餘空間,可以考慮進行縮容操作
(比如剩餘空間佔總容量的一半時,就進行縮容)

縮容和擴容很類似,只是縮容是申請一塊小一點的內存空間,然後把數組拷貝過來
在這裏插入圖片描述
我們之前是在add時增加擴容操作,現在應該在remove時增加縮容操作
我們是在add之前判斷是否要擴容,現在應該在remove之後判斷是否縮容

代碼如下:
在這裏插入圖片描述
測試效果如下:
在這裏插入圖片描述
clear()方法也可以縮容,可以直接創建一個默認長度的數組,如下:
在這裏插入圖片描述

(五)複雜度震盪

如果擴容倍數縮容時機設計不得當,有可能會導致複雜度震盪

  • 我們現在的擴容倍數是1.5倍,如下:
    在這裏插入圖片描述
  • 我們現在的縮容時機是剩餘容量大於總容量一半,如下:
    在這裏插入圖片描述

接下來把擴容倍數設定爲2倍,如下:
在這裏插入圖片描述
縮容時機的判斷語句在size = newCapacity時也爲真
在這裏插入圖片描述
效果如下:
假設數組的默認長度爲4,使用普通的add(E element)方法增加元素,複雜度爲O(1)
在這裏插入圖片描述
再一次添加元素時需要擴容,複雜度爲O(n)
在這裏插入圖片描述
如果此時刪掉剛纔添加的元素
在這裏插入圖片描述
需要縮容,複雜度爲O(n)
在這裏插入圖片描述
此時又增加一個元素,又要擴容,複雜度又爲O(n)
如果一直循環這樣的操作,複雜度一直維持在O(n),這種情況叫做複雜度震盪(之前一直是O(1),後面突然維持O(n)

可見如果擴容倍數縮容時機設計不得當,有可能會導致複雜度震盪

解決方案:讓擴容和縮容的幅度相乘不等於1即可
我們上述的情況是2.0(擴容幅度)× 0.5(縮容幅度)= 1.0,剛好等於1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章