實時插入排序演算法

王心森發表於2020-08-03

背景

在正常的工作中,我們經常遇到從訊息佇列中接收一天的資料量,並對其進行排序的場景。那麼,我們通常會想到最經典的十大經典排序演算法。確實,這些演算法都可以滿足我們的場景需要,,但如果我們要求接收訊息過程中,實時進行排序呢?這些演算法顯然都不能滿足需求,它們都是在接收到所有資料後,再統一排序。所以,今天,我們動手自己遍寫一個實時插入排序演算法!

理論

參考插入排序演算法,我們將接收到的資料存放在一個連結串列結構當中,每接收一個新資料,就讓它與已存在的資料逐個比較,找到需要插入的位置下一個節點,執行新節點插入操作!

圖例

程式碼實現

1. 比較器

因為要進行排序,所以我們需要先建立一個比較器介面,來保證使用者自定義比較方法。

package cn.wxson.sort.comparator;

import cn.wxson.sort.node.AbstractNode;
import com.sun.istack.internal.NotNull;

/**
 * Title 比較器
 *
 * @author Ason(18078490)
 * @date 2020-08-03
 */
@FunctionalInterface
public interface AbstractComparator<T> {

    /**
     * 比較方法
     *
     * @param one 資料節點一
     * @param two 資料節點二
     * @return 比較結果
     */
    boolean compare(@NotNull AbstractNode<T> one, @NotNull AbstractNode<T> two);
}

2. 節點

2.1 節點抽象類

建立節點抽象類,存放資料,並利用比較器實現比較邏輯。

package cn.wxson.sort.node;

import cn.wxson.sort.comparator.AbstractComparator;
import lombok.Getter;
import lombok.Setter;

/**
 * Title 節點
 *
 * @author Ason(18078490)
 * @date 2020-08-03
 */
@Setter
@Getter
public abstract class AbstractNode<T> {

    /**
     * 前一個節點
     */
    private AbstractNode<T> pre;
    /**
     * 後一個節點
     */
    private AbstractNode<T> next;
    /**
     * 節點記憶體儲的實際資料
     */
    protected T t;

    /**
     * 無參構造
     */
    public AbstractNode() {
        super();
    }

    /**
     * 帶參構造
     *
     * @param t 資料物件
     */
    public AbstractNode(T t) {
        this.t = t;
    }

    /**
     * 獲取資料比較器
     *
     * @return 比較器物件
     */
    protected abstract AbstractComparator<T> comparator();

    /**
     * 資料比較
     *
     * @param data 資料節點
     */
    public AbstractNode<T> compareTo(AbstractNode<T> data) {
        return this.comparator().compare(this, data) ? this : this.next.compareTo(data);
    }
}

2.2 虛擬頭節點

頭節點是一個虛擬節點,只做傳遞給下個節點操作,自身實現比較器,資料比較時永遠排在第一位。

package cn.wxson.sort.node;

import cn.wxson.sort.comparator.AbstractComparator;

/**
 * Title   虛擬頭節點
 * 只做傳遞給下個節點操作,資料比較永遠排在第一位
 *
 * @author Ason(18078490)
 * @date 2020-08-03
 */
public final class DummyHeadNode<T> extends AbstractNode<T> implements AbstractComparator<T> {

    /**
     * 獲取資料比較器
     *
     * @return 比較器物件
     */
    @Override
    protected AbstractComparator<T> comparator() {
        return this;
    }

    /**
     * 比較方法
     * 頭節點永遠排在第一位
     *
     * @param one 節點一
     * @param two 節點二
     * @return 比較結果
     */
    @Override
    public boolean compare(AbstractNode<T> one, AbstractNode<T> two) {
        return false;
    }
}

2.3 虛擬尾節點

尾節點也是一個虛擬節點,不需要做任務傳遞操作,自身實現比較器,排序時永遠排在連結串列最後一位。

package cn.wxson.sort.node;

import cn.wxson.sort.comparator.AbstractComparator;

/**
 * Title 虛擬尾節點
 * 因為是最後一個節點,所以,不需要做任務傳遞操作,永遠排在連結串列最後一位
 *
 * @author Ason(18078490)
 * @date 2020-08-03
 */
public final class DummyTailNode<T> extends AbstractNode<T> implements AbstractComparator<T> {

    /**
     * 獲取資料比較器
     *
     * @return 比較器物件
     */
    @Override
    protected AbstractComparator<T> comparator() {
        return this;
    }

    /**
     * 比較方法
     * 尾節點永遠排在最後一位
     *
     * @param one 節點一
     * @param two 節點二
     * @return 比較結果
     */
    @Override
    public boolean compare(AbstractNode<T> one, AbstractNode<T> two) {
        return true;
    }
}

2.4 普通節點

普通節點將接收使用者自定義比較器進行資料比較。

package cn.wxson.sort.node;

import cn.wxson.sort.comparator.AbstractComparator;

/**
 * Title 普通節點
 *
 * @author Ason(18078490)
 * @date 2020-08-03
 */
public class CommonNode<T> extends AbstractNode<T> {

    /**
     * 比較器
     * 是普通節點必須的比較工具
     */
    private AbstractComparator<T> comparator;

    /**
     * 帶參構造
     *
     * @param data 資料
     */
    public CommonNode(T data) {
        super(data);
    }

    /**
     * 註冊比較器
     *
     * @param comparator 比較器
     */
    public void register(AbstractComparator<T> comparator) {
        this.comparator = comparator;
    }

    /**
     * 獲取資料比較器
     *
     * @return 比較器物件
     */
    @Override
    protected AbstractComparator<T> comparator() {
        return this.comparator;
    }
}

3. 連結串列

3.1 連結串列結構抽象類

連結串列結構抽象類中存在虛擬頭節點和虛擬尾節點,並實現連結串列所有遍歷功能。

package cn.wxson.sort.biz;

import cn.wxson.sort.node.AbstractNode;
import cn.wxson.sort.node.DummyHeadNode;
import cn.wxson.sort.node.DummyTailNode;
import com.google.common.collect.Lists;

import java.util.List;

/**
 * Title 連結串列結構抽象類
 * 做初始化連結串列、表元素遍歷等操作
 *
 * @author Ason(18078490)
 * @date 2020-08-03
 */
public class AbstractLinkedList<T> {

    /**
     * 虛擬頭節點
     */
    protected final AbstractNode<T> dummyHeadNode;
    /**
     * 虛擬尾節點
     */
    protected final AbstractNode<T> dummyTailNode;

    /**
     * 無參構造
     * 初始化連結串列的頭尾虛擬節點
     */
    public AbstractLinkedList() {
        // 建立虛擬頭節點、虛擬尾節點,並將它們關聯起來
        this.dummyHeadNode = new DummyHeadNode<>();
        this.dummyTailNode = new DummyTailNode<>();
        this.dummyHeadNode.setNext(this.dummyTailNode);
        this.dummyTailNode.setPre(this.dummyHeadNode);
    }

    /**
     * 除虛擬頭節點、虛擬尾節點外,是否包含其他節點
     *
     * @return 判定結果
     */
    public boolean hasNext() {
        return this.dummyHeadNode.getNext() != this.dummyTailNode;
    }

    /**
     * 獲取連結串列元素
     *
     * @return 元素集合
     */
    public List<T> list() {
        List<T> result = Lists.newLinkedList();
        AbstractNode<T> pos = this.dummyHeadNode.getNext();
        while (pos != this.dummyTailNode) {
            result.add(pos.getT());
            pos = pos.getNext();
        }
        return result;
    }

    /**
     * toString方法
     *
     * @return 字串
     */
    @Override
    public String toString() {
        // 不存在元素時,返回空集合
        if (!hasNext()) {
            return "[]";
        }
        // 存在元素時,逐個列印
        StringBuilder sb = new StringBuilder("[");
        AbstractNode<T> pos = this.dummyHeadNode.getNext();
        while (pos != this.dummyTailNode) {
            sb.append(pos.getT().toString()).append(",");
            pos = pos.getNext();
        }
        String result = sb.substring(0, sb.lastIndexOf(","));
        return result + "]";
    }
}

3.2 連結串列結構類

連結串列結構類中,在新增元素時,註冊比較器,進行比較後,實現實時插入操作。

package cn.wxson.sort.biz;

import cn.wxson.sort.comparator.AbstractComparator;
import cn.wxson.sort.node.AbstractNode;
import cn.wxson.sort.node.CommonNode;

/**
 * Title 連結串列結構類
 * 功能:從頭結點向後進行比較
 *
 * @author Ason(18078490)
 * @date 2020-08-03
 */
public class LinkedList<T> extends AbstractLinkedList<T> {

    /**
     * 比較器
     */
    private final AbstractComparator<T> comparator;

    /**
     * 帶參構造
     *
     * @param comparator 比較器
     */
    public LinkedList(AbstractComparator<T> comparator) {
        // 初始化連結串列
        super();
        // 注入比較器
        this.comparator = comparator;
    }

    /**
     * 新增元素
     *
     * @param data 資料
     */
    public void add(T data) {
        // 建立新節點,並註冊比較器
        CommonNode<T> newNode = new CommonNode<>(data);
        newNode.register(this.comparator);
        // 從頭節點開始,找到新節點應該插入的位置的下一個節點
        AbstractNode<T> next = dummyHeadNode.compareTo(newNode);
        // 將新節點插入連結串列
        AbstractNode<T> pre = next.getPre();
        newNode.setPre(pre);
        newNode.setNext(next);
        pre.setNext(newNode);
        next.setPre(newNode);
    }
}

4. 測試

我們新建幾個使用者類,來根據使用者年齡進行實時排序測試。

4.1 使用者類

package cn.wxson.sort.test;

import com.alibaba.fastjson.JSON;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

/**
 * Title 使用者類
 *
 * @author Ason(18078490)
 * @date 2020-08-03
 */
@Getter
@Setter
@Builder
public class User {

    private String name;
    private int age;

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
}

4.2 測試類

package cn.wxson.sort.test;

import cn.wxson.sort.biz.LinkedList;
import lombok.extern.slf4j.Slf4j;

/**
 * Title 測試類
 *
 * @author Ason(18078490)
 * @date 2020-08-03
 */
@Slf4j
public class Domain {

    public static void main(String[] arg) {
        // 建立三個使用者
        User tom = User.builder().name("Tom").age(20).build();
        User kate = User.builder().name("kate").age(18).build();
        User jerry = User.builder().name("Jerry").age(22).build();
        // 建立連結串列,放入按照使用者年齡升序排列的比較器
        LinkedList<User> linkedList = new LinkedList<>((one, two) -> one.getT().getAge() >= two.getT().getAge());
        log.info("連結串列初始化:{}", linkedList.toString());
        linkedList.add(tom);
        log.info("接收到第一個使用者:Tom,{}", linkedList.toString());
        linkedList.add(kate);
        log.info("接收到第一個使用者:Kate,{}", linkedList.toString());
        linkedList.add(jerry);
        log.info("接收到第一個使用者:Jerry,{}", linkedList.toString());
    }
}

執行以上測試樣例,我們來看下每次插入新資料後,連結串列結構:

執行結果

通過上面結果,我們可以看到,實時插入效果已經實現!

總結

本篇文章通過連結串列結構實現了實時插入排序的功能,它的時間複雜度為O(n),排序執行時間,隨著資料量遞增。

這篇文章資料結構與我之前的一篇博文(基於佇列模型編寫一個入崗檢查站)是相通的,學習後,希望對你有幫助!

相關文章