目錄
背景
在正常的工作中,我們經常遇到從訊息佇列中接收一天的資料量,並對其進行排序的場景。那麼,我們通常會想到最經典的十大經典排序演算法
。確實,這些演算法都可以滿足我們的場景需要,,但如果我們要求接收訊息過程中,實時進行排序呢?這些演算法顯然都不能滿足需求,它們都是在接收到所有資料後,再統一排序。所以,今天,我們動手自己遍寫一個實時插入排序演算法!
理論
參考插入排序
演算法,我們將接收到的資料存放在一個連結串列結構當中,每接收一個新資料,就讓它與已存在的資料逐個比較,找到需要插入的位置下一個節點,執行新節點插入操作!
程式碼實現
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)
,排序執行時間,隨著資料量遞增。
這篇文章資料結構與我之前的一篇博文(基於佇列模型編寫一個入崗檢查站
)是相通的,學習後,希望對你有幫助!