學(xué)習(xí)總結(jié)(數(shù)據(jù)結(jié)構(gòu):順序表、單雙鏈表)

轉(zhuǎn)載請標(biāo)明出處,謝謝!
http://www.itdecent.cn/p/3aeb5998e79e

關(guān)聯(lián)文章
冒泡、選擇排序 http://www.itdecent.cn/p/176b0b892591
棧和隊(duì)列 http://www.itdecent.cn/p/8cb602ef4e21
順序表、單雙鏈表 http://www.itdecent.cn/p/3aeb5998e79e
二叉樹 http://www.itdecent.cn/p/de829eab944c
圖論 http://www.itdecent.cn/p/cf03e51a3ca2

順序表

在順序表中,最常見的操作當(dāng)然是查找(搜索),插入和刪除了,現(xiàn)在對這三種操作的復(fù)雜度進(jìn)行簡要的分析。

第一,搜索:

順序表的順序搜索算法,它的主要內(nèi)容就是從順序表的第一項(xiàng)開始,根據(jù)要查找的給定值x,然后在順序表中逐次進(jìn)行比較。若相等,則搜索成功,返回它的所在位置。若遍歷整個(gè)表,沒有值與其相等,則返回搜索失敗。

一般來說,搜索算法的復(fù)雜度是根據(jù)比較次數(shù)來決定的。簡要分析,如果我們要找的值在第一個(gè)表項(xiàng),那么它的比較次數(shù)就是1,當(dāng)然要是在第二個(gè)表項(xiàng),次數(shù)就為2,依次類推,在第n個(gè)表項(xiàng),比較次數(shù)就為n。好了啦,現(xiàn)在我們可以計(jì)算它的平均比較次數(shù)了。假定每個(gè)表項(xiàng)的可能性都相等,那么我們有:

Acn=1/nΣi=1/n(1+2+.....+n)=1/nn(n+1)/2=(n+1)/2;

所以平均比較次數(shù)為(n+1)/2;

第二,插入:

順序表的插入和刪除算法復(fù)雜度與其表項(xiàng)循環(huán)內(nèi)數(shù)據(jù)移動(dòng)次數(shù)直接有關(guān),先分析插入。我們知道,順序表中如果要在某個(gè)位置插入一個(gè)元素,就必須把那個(gè)空出來,怎么空出來呢,其實(shí)就是把它以及它后面的元素向后移動(dòng)一位。那么就是這樣的,如果將新表項(xiàng)插入至第i個(gè)表項(xiàng)后面,可以從后向前循環(huán),逐個(gè)向后移動(dòng)n-i個(gè)表項(xiàng)。最好的情形就是在表尾追加新元素。那么它的移動(dòng)次數(shù)為0,相反,最壞情形是在第一個(gè)表項(xiàng)位置插入。那么移動(dòng)次數(shù)為n。來看平均移動(dòng)次數(shù)。

Amn=1/(n+1)Σ(n-i)=1/(n+1)((n-1)+...+1+0)=(n)/2;

第三,刪除:

在刪除第i個(gè)表項(xiàng)時(shí),是逐個(gè)移動(dòng)n-i個(gè)表項(xiàng)。最好的情況是在刪去最后的n個(gè)表項(xiàng)。次數(shù)為0,最壞情況是刪除第一個(gè)表項(xiàng)。移動(dòng)個(gè)數(shù)為n-1。那么平均移動(dòng)個(gè)數(shù):

Amn=1/nΣ(n-i)=1/n((n-1)+...+1+0)=(n-1)/2;

原文:https://blog.csdn.net/lifestylegoingon/article/details/47903743

總結(jié):

通過上述的分析,我們對順序表的實(shí)現(xiàn)已有了比較清晰的認(rèn)識,接下來看一下順序表的執(zhí)行效率問題,主要針對獲取、插入、修改、刪除等主要操作。前面分析過,由于順序表內(nèi)部采用了數(shù)組作為存儲容器,而數(shù)組又是隨機(jī)存取結(jié)構(gòu)的容器,也就是說在創(chuàng)建數(shù)組時(shí)操作系統(tǒng)給數(shù)組分配的是一塊連續(xù)的內(nèi)存空間,數(shù)組中每個(gè)存儲單元的地址都是連續(xù)的,所以在知道數(shù)組基地址后可以通過一個(gè)簡單的乘法和加法運(yùn)算即可計(jì)算出其他存儲單元的內(nèi)存地址(實(shí)際上計(jì)算機(jī)內(nèi)部也就是這么做的),這兩個(gè)運(yùn)算的執(zhí)行時(shí)間是常數(shù)時(shí)間,因此可以認(rèn)為數(shù)組的訪問操作能在常數(shù)時(shí)間內(nèi)完成,即順序表的訪問操作(獲取和修改元素值)的時(shí)間復(fù)雜為O(1)。

對于在順序表中插入或者刪除元素,從效率上則顯得不太理想了,由于插入或者刪除操作是基于位置的,需要移動(dòng)數(shù)組中的其他元素,所以順序表的插入或刪除操作,算法所花費(fèi)的時(shí)間主要是用于移動(dòng)元素,如在順序表頭部插入或刪除時(shí),效率就顯得相當(dāng)糟糕了。

優(yōu)點(diǎn)

使用數(shù)組作為內(nèi)部容器簡單且易用

在訪問元素方面效率高

數(shù)組具有內(nèi)存空間局部性的特點(diǎn),由于本身定義為連續(xù)的內(nèi)存塊,所以任何元素與其相鄰的元素在物理地址上也是相鄰的。

缺點(diǎn)

內(nèi)部數(shù)組大小是靜態(tài)的,在使用前必須指定大小,如果遇到容量不足時(shí),需動(dòng)態(tài)拓展內(nèi)部數(shù)組的大小,會造成額外的時(shí)間和空間開銷

在內(nèi)部創(chuàng)建數(shù)組時(shí)提供的是一塊連續(xù)的空間塊,當(dāng)規(guī)模較大時(shí)可能會無法分配數(shù)組所需要的內(nèi)存空間

順序表的插入和刪除是基于位置的操作,如果需要在數(shù)組中的指定位置插入或者刪除元素,可能需要移動(dòng)內(nèi)部數(shù)組中的其他元素,這樣會造成較大的時(shí)間開銷,時(shí)間復(fù)雜度為O(n)

鏈表

通過前面對線性順序表的分析,我們知道當(dāng)創(chuàng)建順序表時(shí)必須分配一塊連續(xù)的內(nèi)存存儲空間,而當(dāng)順序表內(nèi)部數(shù)組的容量不足時(shí),則必須創(chuàng)建一個(gè)新的數(shù)組,然后把原數(shù)組的的元素復(fù)制到新的數(shù)組中,這將浪費(fèi)大量的時(shí)間。而在插入或刪除元素時(shí),可能需要移動(dòng)數(shù)組中的元素,這也將消耗一定的時(shí)間。鑒于這種種原因,于是鏈表就出場了,鏈表在初始化時(shí)僅需要分配一個(gè)元素的存儲空間,并且插入和刪除新的元素也相當(dāng)便捷,同時(shí)鏈表在內(nèi)存分配上可以是不連續(xù)的內(nèi)存,也不需要做任何內(nèi)存復(fù)制和重新分配的操作。

 public class Node<T> {
        //鏈表數(shù)據(jù)域
        T t;
        //鏈表指針域
        Node<T> next;

        public Node(T t) {
            this.t = t;
        }
    }

    public Node<T> head;
    public int size;
}

單鏈表添加:鏈表添加分為頭部添加和其他

/**
     * 單鏈表添加數(shù)據(jù)
     *
     * @param index
     * @param data
     */
    public void add(int index, T data) {
        if (data == null) {
            return;
        }
        if (index < 0 || index > size) {
            System.out.println("插入位置不對");
            return;
        }
        //在頭部插入
        if (head == null ) {
            head = new Node(data);
        } else {
            Node front = head;
            //找到要插入結(jié)點(diǎn)位置的前一個(gè)結(jié)點(diǎn)
            for (int i = 0; i < index - 1; i++) {
                if (front.next != null) {
                    front = front.next;
                }
            }
            Node newNode = new Node(data);
            newNode.next = front.next;
            front.next = newNode;
        }
        size++;

    }

添加頭部位置:頭部結(jié)點(diǎn)為空所以直接將新結(jié)點(diǎn)賦值給頭部位置

添加中間(尾部)位置:先找到要添加結(jié)點(diǎn)的前一個(gè)位置,新結(jié)點(diǎn)的next=front.next, front.next指向新結(jié)點(diǎn)的位置。記得判空,因?yàn)槲膊康膎ext是空

單鏈表刪除:鏈表刪除分為頭部刪除和其他

刪除頭部位置:刪除頭部位置,及它的next變成新的head.

刪除中間位置:先找到要?jiǎng)h除結(jié)點(diǎn)的前一個(gè)位置,該位置的next直接指向該位置next的next.

/**
     * 刪除結(jié)點(diǎn)
     *
     * @param index
     */
    public void delNode(int index) {
        if (index < 0 || index > size) {
            System.out.println("刪除位置不對");
            return;
        }

        /*刪除頭結(jié)點(diǎn)*/
        if (index == 0 && head != null) {
            head = head.next;
        } else {
            Node front = head;
            //找到要?jiǎng)h除結(jié)點(diǎn)位置的前一個(gè)結(jié)點(diǎn)
            for (int i = 0; i < index - 1; i++) {
                if (front.next != null) {
                    front = front.next;
                }
            }
            if (front.next != null) {
                front.next = front.next.next;
            }
        }
        size--;

    }

其他方法:

 /**
     * 判斷是否是空
     * @return
     */
    public boolean isEmpty() {
        return head == null;
    }

    /**
     * 清空結(jié)點(diǎn)
     */
    public void clearLinked() {
        if (!isEmpty()) {
            head = null;
        }
    }

    /**
     * 顯示出所有的節(jié)點(diǎn)信息
     */
    public void displayAllNode() {
        Node current = head;
        while (current != null) {
            System.out.print(current.t + " ");
            current = current.next;
        }

    }

測試:

 @Test
    public void test() {
        SignLinkedList signLinkedList = new SignLinkedList();
        signLinkedList.add(0, "a");
        signLinkedList.add(1, "b");
        signLinkedList.add(2, "c");
        signLinkedList.add(3, "d");
        signLinkedList.add(4, "e");
        signLinkedList.displayAllNode();

        System.out.println("\n----刪除頭結(jié)點(diǎn)----");
        signLinkedList.delNode(0);
        signLinkedList.displayAllNode();

        System.out.println("\n----刪除中間結(jié)點(diǎn)----");
        signLinkedList.delNode(2);
        signLinkedList.displayAllNode();

        System.out.println("\n----刪除尾結(jié)點(diǎn)----");
        signLinkedList.delNode(2);
        signLinkedList.displayAllNode();

        System.out.println("\n----清空數(shù)據(jù)----");
        signLinkedList.clearLinked();
        signLinkedList.displayAllNode();


    }
image.png

單鏈表性能分析:

由于單鏈表并不是隨機(jī)存取結(jié)構(gòu),即使單鏈表在訪問第一個(gè)結(jié)點(diǎn)時(shí)花費(fèi)的時(shí)間為常數(shù)時(shí)間,但是如果需要訪問第i(0<i<n)個(gè)結(jié)點(diǎn),需要從頭結(jié)點(diǎn)head開始遍歷部分鏈表,進(jìn)行i次的p=p.next操作,這點(diǎn)從上述的圖文分析我們也可以看出,這種情況類似于前面計(jì)算順序表需要平均移動(dòng)元素的總數(shù),因此鏈表也需要平均進(jìn)行n2n2次的p=p.next操作,也就是說get(i)和set(i,x)的時(shí)間復(fù)雜度都為O(n)。

由于鏈表在插入和刪除結(jié)點(diǎn)方面十分高效的,因此鏈表比較適合那些插入刪除頻繁的場景使用,單純從插入操作來看,我們假設(shè)front指向的是單鏈表中的一個(gè)結(jié)點(diǎn),此時(shí)插入front的后繼結(jié)點(diǎn)所消耗的時(shí)間為常數(shù)時(shí)間O(1),但如果此時(shí)需要在front的前面插入一個(gè)結(jié)點(diǎn)或者刪除結(jié)點(diǎn)自己時(shí),由于front并沒有前驅(qū)指針,單憑front根本無法知道前驅(qū)結(jié)點(diǎn),所以必須從鏈表的表頭遍歷至front的前一個(gè)結(jié)點(diǎn)再執(zhí)行插入或者刪除操作,而這個(gè)查詢操作所消耗的時(shí)間為O(n),因此在已知front結(jié)點(diǎn)需要插入前驅(qū)結(jié)點(diǎn)或者刪除結(jié)點(diǎn)自己時(shí),消耗的時(shí)間為O(n)。當(dāng)然這種情況并不是無法解決的,后面我們要分析到的雙鏈表就可以很好解決這個(gè)問題,雙鏈表是每個(gè)結(jié)點(diǎn)都同時(shí)擁有前后繼結(jié)點(diǎn)的鏈表,這樣的話上面的問題就迎刃而解了。上述是從已知單鏈表中front結(jié)點(diǎn)的情況下討論的單鏈表的插入刪除效率。

我們可能會有個(gè)疑問,從前面單鏈表的插入刪除的代碼實(shí)現(xiàn)上來說,我們并不知道front結(jié)點(diǎn)的,每次插入和刪除結(jié)點(diǎn),都需要從表頭開始遍歷至要插入或者刪除結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn),而這個(gè)過程所花費(fèi)的時(shí)間和訪問結(jié)點(diǎn)所花費(fèi)的時(shí)間是一樣的,即O(n),

也就是說從實(shí)現(xiàn)上來說確實(shí)單鏈表的插入刪除操作花費(fèi)時(shí)間也是O(n),而順序表插入和刪除的時(shí)間也是O(n),那為什么說單鏈表的插入和刪除的效率高呢?這里我們要明白的是鏈表的插入和刪除之所以是O(N),是因?yàn)椴樵儾迦朦c(diǎn)所消耗的,找到插入點(diǎn)后插入操作消耗時(shí)間只為O(1),而順序表查找插入點(diǎn)的時(shí)間為O(1),但要把后面的元素全部后移一位,消耗時(shí)間為O(n)。問題是大部分情況下查找所需時(shí)間比移動(dòng)短多了,還有就是鏈表不需要連續(xù)空間也不需要擴(kuò)容操作,因此即使時(shí)間復(fù)雜度都是O(n),所以相對來說鏈表更適合插入刪除操作。

原文:https://blog.csdn.net/javazejian/article/details/52953190

單鏈表的反轉(zhuǎn)

https://mp.weixin.qq.com/s?src=11&timestamp=1590225130&ver=2356&signature=ir7GLbKq7oqI0usHtgvIBItkfEZScqO0XH3inr2F2in9Bb36xsWkOblF8An6XqTLG3ff0jyoHPUzLGqQZVw0RAvcHU-NYudxl3lNQX-kPLpI9fD0uxFx1nLlb4g395&new=1

雙鏈表

雙鏈表的主要優(yōu)點(diǎn)是對于任意給的結(jié)點(diǎn),都可以很輕易的獲取其前驅(qū)結(jié)點(diǎn)或者后繼結(jié)點(diǎn),而主要缺點(diǎn)是每個(gè)結(jié)點(diǎn)需要添加額外的next域,因此需要更多的空間開銷,同時(shí)結(jié)點(diǎn)的插入與刪除操作也將更加耗時(shí),因?yàn)樾枰嗟闹羔樦赶虿僮鳌?/p>

在插入雙鏈表時(shí)需分兩種情況,一種是在插入空雙鏈表和尾部插入,另一種是雙鏈表的中間插入,如下圖在空雙鏈表插入值x:

image

原文:https://blog.csdn.net/javazejian/article/details/52953190

代碼:

 public class Node<T> {
        public Node pre;

        public Node next;

        public T data;

        public Node(Node pre, Node next, T data) {
            this.pre = pre;
            this.next = next;
            this.data = data;
        }
    }

尾部的插入:如果是空表則將新節(jié)點(diǎn)設(shè)置位first結(jié)點(diǎn),反之添加在尾部位置。

/**
     * 從尾部添加
     * @param data
     */
    public void addLast(T data) {
        Node newNode = new Node(last, null, data);
        Node l = last;

        last = newNode;

        if (l == null) {
            first = newNode;
        } else {
            l.next = newNode;
        }
        size++;
    }

指定位置添加: 在指定位置添加需要將新結(jié)點(diǎn)的前驅(qū)指向front后繼指向front的next。故新結(jié)點(diǎn)為:

  Node newNode = new Node(front, front.next, data);

接著更改front的next指向newNode,front的next的pre指向newNode。這樣原本A、B兩個(gè)的連接點(diǎn)就斷開,重新指向新的位置。

image

代碼表示:

/**
     * 指定位置添加
     * @param index
     * @param data
     */
    public void add(int index, T data) {
        if (data == null) {
            return;
        }
        if (index < 0 || index > size) {
            System.out.println("插入位置不對");
            return;
        }
        /*如果指定的位置等于在表的尾部*/
        if (index == size) {
            addLast(data);
        } else {
            Node front = first;
            /*先將指針移向?qū)⒁鋈胛恢玫那耙粋€(gè)位置*/
            for (int i = 0; i < index - 1; i++) {
                if (front.next != null) {
                    front = front.next;
                }
            }
            /*創(chuàng)建將要插入的結(jié)點(diǎn) 新結(jié)點(diǎn)前驅(qū)指向front,后繼指向front的后繼*/
            Node newNode = new Node(front, front.next, data);
            if (front.next != null) {
                front.next.pre = newNode;
                //更改front的后繼指針
                front.next = newNode;
            }
            size++;
        }
    }

根據(jù)index獲取結(jié)點(diǎn)

/**
     * 根據(jù)index獲取結(jié)點(diǎn)
     * @param index
     * @return
     */
    private Node<T> getNode(int index) {

        Node node = first;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

刪除結(jié)點(diǎn):刪除結(jié)點(diǎn)包括刪除尾結(jié)點(diǎn),頭結(jié)點(diǎn)以及中間結(jié)點(diǎn)。

/**
     * 刪除結(jié)點(diǎn)
     * @param index
     */
    public void removeNode(int index){
        if(index<0||index>size){
            System.out.println("刪除位置不對");
            return;
        }
        /*根據(jù)指定位置獲取當(dāng)前要?jiǎng)h除的結(jié)點(diǎn)*/
        Node delNode = getNode(index);

        /*刪除頭結(jié)點(diǎn)*/
        if(delNode.pre == null){
            first = delNode.next ;
        }
        /*刪除尾結(jié)點(diǎn)*/
        if(delNode.next == null){
            last = delNode.pre;
        }
        /*刪除中間結(jié)點(diǎn)*/
        if(delNode.pre !=null && delNode.next!=null){
            delNode.pre.next = delNode.next ;
            delNode.next.pre = delNode.pre;
        }
        size-- ;
    }

測試:

 @Test
    public void test() {
        LinkedList linkedList = new LinkedList();
        linkedList.add(0, "1");
        linkedList.add(1, "2");
        linkedList.add(2, "3");
        linkedList.add(3, "4");

        linkedList.add(3,"5");
        linkedList.add("last");

        for (int i = 0; i < linkedList.size; i++) {
            System.out.println("位置 " + linkedList.getData(i));
        }
        linkedList.removeNode(2);
        linkedList.removeNode(linkedList.size-1);
        System.out.println("刪除數(shù)據(jù)后");
        for (int i = 0; i < linkedList.size; i++) {
            System.out.println("位置 " + linkedList.getData(i));
        }
    }
image.png

總結(jié)

順序表和鏈表存儲的優(yōu)缺點(diǎn)

1.順序表存儲

 原理:順序表存儲是將數(shù)據(jù)元素放到一塊連續(xù)的內(nèi)存存儲空間,存取效率高,速度快。但是不可以動(dòng)態(tài)增加長度

 優(yōu)點(diǎn):存取速度高效,通過下標(biāo)來直接存儲

 缺點(diǎn):1.插入和刪除比較慢,2.不可以增長長度   

            比如:插入或者刪除一個(gè)元素時(shí),整個(gè)表需要遍歷移動(dòng)元素來重新排一次順序

2.鏈表存儲

  原理:鏈表存儲是在程序運(yùn)行過程中動(dòng)態(tài)的分配空間,只要存儲器還有空間,就不會發(fā)生存儲溢出問題

 優(yōu)點(diǎn):插入和刪除速度快,保留原有的物理順序,比如:插入或者刪除一個(gè)元素時(shí),只需要改變指針指向即可

 缺點(diǎn):查找速度慢,因?yàn)椴檎視r(shí),需要循環(huán)鏈表訪問

從它們的存儲優(yōu)缺點(diǎn)來看,各自有各自的使用場景,比如:頻繁的查找卻很少的插入和刪除操作可以用順序表存儲,如果頻繁的插入和刪除操作很少的查詢就可以使用鏈表存儲

原文鏈接:https://blog.csdn.net/u013538542/article/details/47336179

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容