數據結構(一)——鏈表的基本操作

關於數據結構,這次總算下定決心進行一個總結了,之前各種零星散散的總結,並不成體系,這裏對一些基本的數據結構進行一個總結。大學學過數據結構的理論,但是一直沒用代碼完全實現過。

鏈表和結點的定義

節點的定義

/**
 * autor:liman
 * comment: 鏈表對應的節點
 */
public class ListNode {

    public int value;
    public ListNode next;

    public ListNode(int value) {
        this.value = value;
    }
}

這個很簡單的定義。沒啥可說的,學過數據結構這個都很簡單

鏈表的定義

鏈表的定義就是在節點的基礎上,封裝一些基礎操作,一些插入刪除的操作百度就好,這裏直接貼出代碼

package com.learn.LinkList;

/**
 * autor:liman
 * comment:
 */
public class SelfLinkedList {

    /**
     * 頭結點的插入
     * @param head
     * @param newHead
     */
    public static void headInsert(ListNode head,ListNode newHead){
        ListNode old = head;
        head = newHead;
        head.next = old;
    }

    /**
     * 未節點的插入
     * @param tail
     * @param newTail
     */
    public static void tailInsert(ListNode tail,ListNode newTail){
        ListNode old = tail;
        tail = newTail;
        newTail.next = null;
        old.next = tail;
    }

    /**
     * 遍歷鏈表
     * @param head
     */
    public static void traverse(ListNode head){
        while(head!=null){
            System.out.print(head.value+" ");
            head = head.next;
        }
        System.out.println();
    }

    /**
     * 查找節點,返回節點索引
     * @param head
     * @return
     */
    public static int findNode(ListNode head,int value){
        int index = -1;
        int count = 0;
        while(head!=null){
            if(head.value == value){
                index = count;
                return index;
            }
            count++;
            head = head.next;
        }
        return index;
    }

    /**
     * 在pre節點的後面插入s節點
     * @param pre
     * @param s
     */
    public static void insertAfterNode(ListNode pre,ListNode s){
        ListNode pAfter = pre.next;
        pre.next = s;
        s.next = pAfter;
    }

    /**
     * 刪除節點s,將s的next複製到s,然後刪除s的後繼節點
     * @param head
     * @param s
     */
    public static void deleteNode(ListNode head,ListNode s){
        if(s!=null && s.next!=null){//這裏不包含刪除尾節點的情況
            ListNode sNext = s.next;
            s.value = sNext.value;
            //刪除s的下一個節點
            s.next = sNext.next;
            sNext=null;
        }

        //如果是刪除尾節點
        if(s.next==null){
            //遍歷找打前驅
            while(head!=null){
                if(head.next!=null && head.next == s){
                    head.next=null;
                    break;
                }
                head=head.next;
            }
        }
    }

}

其中刪除節點的操作,找到前驅節點比較麻煩,這裏就偷了個懶,直接將後繼節點複製到當前節點,然後刪除當前節點。

一些實戰操作

鏈表翻轉

思路:利用三個指針依次走鏈,每走一步將隊伍後面兩個指針翻轉即可。

    /**
     * 翻轉鏈表,時間複雜度O(n),空間複雜度O(1)
     *
     * @param head
     */
    public static ListNode reverseList(ListNode head) {
        ListNode preNode = null;
        ListNode afterNode = null;
        while (head != null){
            afterNode = head.next;
            head.next=preNode;
            preNode = head;
            head = afterNode;
        }
        return preNode;
    }

獲取中間節點

思路:取中間節點,如果鏈表長度是奇數,則直接取中間的。如果是偶數則取中間的前一個。

定義兩個指針,快指針每次走兩步,慢指針每次走一步,當快指針走到尾的時候,慢指針則爲中間節點。

    /*
     * @param head
     * @return
     */
    public static ListNode getMiddleNode(ListNode head){
        if(head==null){
            return head;
        }
        ListNode fastNode = head;
        ListNode slowNode = head;
        while(fastNode.next!=null && fastNode.next.next!=null){
            slowNode = slowNode.next;
            fastNode = fastNode.next.next;
        }
        return slowNode;
    }

合併兩個有序鏈表

這個有幾種實現方式,一種是遞歸,一種是非遞歸。

遞歸實現

    /**
     * 遞歸的方式合併有序鏈表
     *
     * @param head01
     * @param head02
     * @return
     */
    public static ListNode mergeTwoListRecursive(ListNode head01, ListNode head02) {
        //遞歸出口
        if (head01 == null && head02 == null) {
            return null;
        }
        if (head01 == null) {
            return head02;
        }
        if (head02 == null) {
            return head01;
        }

        ListNode head = null;//合併後的頭節點
        if (head01.value > head02.value) {
            head = head02;
            head.next = mergeTwoListRecursive(head01, head02.next);
        } else {
            head = head01;
            head.next = mergeTwoListRecursive(head01.next, head02);
        }
        return head;
    }

非遞歸實現

    /**
     * 非遞歸合併兩個鏈表。
     *
     * @param head01
     * @param head02
     * @return
     */
    public static ListNode mergeTwoList(ListNode head01, ListNode head02) {
        if (head01 == null || head02 == null) {
            return head01 != null ? head01 : head02;
        }

        ListNode head = head01.value <= head02.value ? head01 : head02;
        ListNode cur1 = head == head01 ? head01 : head02;
        ListNode cur2 = head == head01 ? head02 : head01;

        ListNode pre = null;
        ListNode next = null;
        while (cur1 != null && cur2 != null) {
            if (cur1.value <= cur2.value) {//將cur1合併到目標鏈表
                pre = cur1;
                cur1 = cur1.next;
            } else {//將cur2合併到目標鏈表
                next = cur2.next;
                pre.next = cur2;
                cur2.next = cur1;
                pre = cur2;
                cur2 = next;
            }
        }
        pre.next = cur1 == null ? cur2 : cur1;
        return head;
    }

非遞歸的另一種實現,我個人覺得這種思路要稍微簡單點

    /**
     * 合併鏈表,自己的思想,和當時的數據結構書籍上的處理一樣
     *
     * @param head01
     * @param head02
     * @return
     */
    public static ListNode mergeTwoListSelf(ListNode head01, ListNode head02) {
        if (head01 == null || head02 == null) {
            return head01 != null ? head01 : head02;
        }
        ListNode pNode = new ListNode(-65535);//建立一個節點用於頭節點
        ListNode head = head01.value <= head02.value ? head01 : head02;//構建head,後面的構建操作,交給了pNode
        ListNode cur01 = head01;
        ListNode cur02 = head02;
        while (cur01 != null && cur02 != null) {
            if (cur01.value <= cur02.value) {
                pNode.next = cur01;
                cur01 = cur01.next;
            } else {
                pNode.next = cur02;
                cur02 = cur02.next;
            }
            pNode = pNode.next;
        }

        pNode.next = cur01 == null ? cur02 : cur01;
        return head;
    }

一些面試題

奇數升序,偶數降序

一個鏈表,奇數位按升序,偶數位按降序排列,對該鏈表進行排序。例如:1->8->3->6->5->4->7->2->9,需要對其排序

思路:1、按照奇數位和偶數位進行拆分,拆分成兩個鏈表。2、對偶數位出來的鏈表進行翻轉。3、合併排序

這裏直接貼出完整的實現

/**
 * autor:liman
 * createtime:2020/2/4
 * 一個鏈表,奇數位升序,偶數位降序,對該鏈表進行排序
 */
public class InterviewTitleOne {

    /**
     * 分成三步:
     * 1、按照奇數位和偶數位進行拆分
     * 2、對偶數位進行翻轉
     * 3、合併排序
     */
    public static void main(String[] args) {
        ListNode node01 = new ListNode(1);
        ListNode node02 = new ListNode(8);
        ListNode node03 = new ListNode(3);
        ListNode node04 = new ListNode(6);
        ListNode node05 = new ListNode(5);
        ListNode node06 = new ListNode(4);
        ListNode node07 = new ListNode(7);
        ListNode node08 = new ListNode(2);
        ListNode node09 = new ListNode(9);

        node01.next = node02;
        node02.next = node03;
        node03.next = node04;
        node04.next = node05;
        node05.next = node06;
        node06.next = node07;
        node07.next = node08;
        node08.next = node09;

        ListNode[] listNodes = getList(node01);
        ListNode head01 = listNodes[0];
        ListNode head02 = listNodes[1];
        //翻轉偶數位的鏈表
        head02=reverseList(head02);

        ListNode result = mergetList(head01,head02);
        SelfLinkedList.traverse(result);

    }

    /**
     * 拆分鏈表,按奇偶進行拆分
     *
     * @param head
     * @return
     */
    public static ListNode[] getList(ListNode head) {
        ListNode head01 = null;
        ListNode head02 = null;

        ListNode cur01 = null;
        ListNode cur02 = null;
        int count = 1;
        while (head != null) {
            if (count % 2 == 1) {//奇數節點
                if(cur01!=null){
                    cur01.next = head;
                    cur01 = cur01.next;
                }else{
                    cur01 = head;
                    head01 = cur01;
                }
            }else{
                if(cur02!=null){
                    cur02.next = head;
                    cur02 = cur02.next;
                }else{
                    cur02 = head;
                    head02 = cur02;
                }
            }
            head = head.next;
            count++;
        }
        cur01.next = null;
        cur02.next = null;
        ListNode[] nodes = new ListNode[]{head01,head02};
        return nodes;
    }

    /**
     * 翻轉鏈表
     * @param head
     * @return
     */
    public static ListNode reverseList(ListNode head){
        ListNode pre = null;
        ListNode next = null;
        while(head!=null){
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }

    /**
     * 合併兩個鏈表(遞歸實現)
     * @param head01
     * @param head02
     * @return
     */
    public static ListNode mergetList(ListNode head01,ListNode head02){
        if(head01 == null && head02 ==null){
            return null;
        }
        if(head01==null){
            return head02;
        }
        if(head02 == null){
            return head01;
        }
        ListNode head = null;
        if(head01.value>head02.value){
            head=head02;
            head.next = mergetList(head01,head02.next);
        }else{
            head = head01;
            head.next = mergetList(head01.next,head02);
        }
        return head;
    }

}

其中的翻轉和合並操作之前已經介紹過,這裏不再贅述,這裏值得關注的是拆分操作。

實現鏈表的歸併排序

歸併排序應該算是鏈表排序最佳的選擇了,保證了最好和最好的時間複雜度都是nlogn,而且歸併排序在數組中空間複雜度爲O(n),在鏈表中也變成了O(1)。

思路:1、將待排序的數組(鏈表)一分爲二。2、遞歸的將左邊部分進行歸併排序。3、遞歸的將右邊部分進行歸併排序。4、將兩個部分進行合併排序,得到結果。

package com.learn.LinkList;

/**
 * autor:liman
 * createtime:2020/2/4
 */
public class InterviewTitleTwo {

    public static void main(String[] args) {
        ListNode node01 = new ListNode(1);
        ListNode node02 = new ListNode(8);
        ListNode node03 = new ListNode(3);
        ListNode node04 = new ListNode(6);
        ListNode node05 = new ListNode(5);
        ListNode node06 = new ListNode(4);
        ListNode node07 = new ListNode(7);
        ListNode node08 = new ListNode(2);
        ListNode node09 = new ListNode(9);

        node01.next = node02;
        node02.next = node03;
        node03.next = node04;
        node04.next = node05;
        node05.next = node06;
        node06.next = node07;
        node07.next = node08;
        node08.next = node09;

        ListNode sortedList = sortList(node01);
        SelfLinkedList.traverse(sortedList);
    }

    /**
     * 鏈表的歸併排序算法
     *
     * @param head
     * @return
     */
    public static ListNode sortList(ListNode head) {
        if (head == null || head.next==null) {//0個或者1個元素,不用排序,直接返回
            return head;
        }
        ListNode middleNode = getMiddleNode(head);
        ListNode rightFirst = middleNode.next;
        middleNode.next=null;//拆成兩個鏈表;
        ListNode node=merge(sortList(head),sortList(rightFirst));
        return node;
    }

    /**
     * 獲取中間節點
     *
     * @param head
     * @return
     */
    public static ListNode getMiddleNode(ListNode head) {
        if (head == null) {
            return head;
        }
        ListNode fastNode = head;
        ListNode slowNode = head;
        while (fastNode.next != null && fastNode.next.next != null) {
            slowNode = slowNode.next;
            fastNode = fastNode.next.next;
        }
        return slowNode;
    }

    /**
     * 非遞歸有序合併兩個鏈表
     *
     * @param head01
     * @param head02
     * @return
     */
    public static ListNode merge(ListNode head01, ListNode head02) {
        if (head01 == null || head02 == null) {
            return head01 != null ? head01 : head02;
        }

        ListNode head = head01.value <= head02.value ? head01 : head02;
        ListNode cur1 = head == head01 ? head01 : head02;
        ListNode cur2 = head == head01 ? head02 : head01;

        ListNode pre = null;
        ListNode next = null;
        while (cur1 != null && cur2 != null) {
            if (cur1.value <= cur2.value) {
                pre = cur1;
                cur1 = cur1.next;
            } else {
                next = cur2.next;
                pre.next = cur2;
                cur2.next = cur1;
                pre = cur2;
                cur2 = next;
            }
        }
        pre.next = cur1 == null ? cur2 : cur1;
        return head;
    }
}

總結:

一些基礎操作的總結。

發佈了134 篇原創文章 · 獲贊 37 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章