数据结构与算法(三)--- 线性表之链式存储结构

一、链表定义

线性表的链式存储结构的特点是用一组任意的存储单位存储线性表的数据元素,这组存储单位可以是连续的,也可以使不连续的。

种类 单链表 数据结构与算法(三)--- 线性表之链式存储结构_第1张图片
【e.g.】Message
单循环链表 数据结构与算法(三)--- 线性表之链式存储结构_第2张图片
【e.g.】丢手绢
双链表
【e.g.】LinkedList
双向循环链表 数据结构与算法(三)--- 线性表之链式存储结构_第3张图片
【e.g.】

二、单链表的定义

节点 数据结构与算法(三)--- 线性表之链式存储结构_第4张图片
数据域:可以有很多内容组成
【e.g.】Message的数据域有:what,arg1,arg2,obj等等

指针域:可以看做是内存地址的引用
【e.g.】Message next; // 下一个节点的引用
数据链 数据结构与算法(三)--- 线性表之链式存储结构_第5张图片
删除一个节点 数据结构与算法(三)--- 线性表之链式存储结构_第6张图片
增加一个节点 数据结构与算法(三)--- 线性表之链式存储结构_第7张图片

三、单链表的应用:MessageQueue

public final class MessageQueue {

    // 添加message, 到消息队列中
    boolean enqueueMessage(Message msg, long when) {
        ...
        Message p = mMessages; // mMessages头指针
        if (p == null || when == 0 || when < p.when) { // 链表为空的时候
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            Message prev;
            for (; ; ) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) { // message 是按照时间when排序的
                    break;
                }
                ...
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
    }
    
    // 从消息队列中删除一个节点
    Message next() {
        for (;;) {
            Message prevMsg = null;
            Message msg = mMessages; // mMessages头指针
            ...
            if (prevMsg != null) {
                prevMsg.next = msg.next;
            } else {
                mMessages = msg.next;
            }
            msg.next = null; // 一但GC有空,就会把msg回收
        }
    }
}

(一)插入

  • 【图示】定位:查找要插入的位置
 for (; ; ) {
     prev = p;
     p = p.next;
     if (p == null || when < p.when) 
        break;
     ...
  }

数据结构与算法(三)--- 线性表之链式存储结构_第8张图片

  • 【图示】将msg插入到p之前
msg.next = p; // invariant: p == prev.next
prev.next = msg;

数据结构与算法(三)--- 线性表之链式存储结构_第9张图片

(二)删除

  • 【图示】删除节点,当prevMsg != null时
if (prevMsg != null) {
    prevMsg.next = msg.next;
}
msg.next = null;

数据结构与算法(三)--- 线性表之链式存储结构_第10张图片

  • 【图示】删除节点,当prevMsg == null时
if (prevMsg == null) {
    mMessages = msg.next;
}
msg.next = null;

数据结构与算法(三)--- 线性表之链式存储结构_第11张图片

四、单链表的实操:麻将排序 – 链式基数排序

(一)麻将排序解析

实操:将一副麻将排序。如果用冒泡排序,最坏的情况下时间复杂度:15*15,在服务端进行排序,如果用户量大,那就会特别恶心。可以采用空间换时间的方式解决。

实现逻辑:1、一副牌的点数有9种,9组数据存放这9种点数,第一次循环,将牌的点数归位。
                  2、一副牌的花色有3种,3组数据存放着3种花色,第二次循环,将牌的花色归位。

采用LinkedList,或者使用自定义的单链表,不能使用ArrayList(内部会产生大量的数组拷贝等操作)。运行空间的大小取决于你定义的麻将的大小,和链表没有关系。这个排序算法是最优的。

适用于麻将,斗地主等排序。

  • 初始化
    在这里插入图片描述

  • 第一次循环,将牌的点数归位(总共9个链表,将对应的点数的牌放到对应链表中,然后将9个链表连接在一起)
    数据结构与算法(三)--- 线性表之链式存储结构_第12张图片

  • 第二次循环,将牌的花色归位(总共3个链表,将对应的点数的牌放到对应链表中,然后将3个链表连接在一起)
    数据结构与算法(三)--- 线性表之链式存储结构_第13张图片

  • 排序后
    在这里插入图片描述

(二)【代码实现01】Mahjong麻将类

/**
 * 功能:麻将类
 * 

* Created by danke on 2018/11/28. */ public class Mahjong { /** * 花色:万,条,筒 */ public int suit; /** * 点数:一,二,三 */ public int rank; public static int SUIT_WANG = 1; // 花色:万 public static int SUIT_TIAO = 2; // 花色:条 public static int SUIT_TONG = 3; // 花色:筒 public Mahjong(int suit, int rank) { this.rank = rank; this.suit = suit; } @Override public String toString() { StringBuffer sb = new StringBuffer(); if (suit == SUIT_WANG) { sb.append("万"); } else if (suit == SUIT_TIAO) { sb.append("条"); } else if (suit == SUIT_TONG) { sb.append("筒"); } sb.append(rank); return sb.toString(); } }

(三)【代码实现02】逻辑代码

    private int ORDER_BY_SUIT = 0;  // 按照花色排序
    private int ORDER_BY_RANK = 1;  // 按照点数排序
    
    public void radixSort(LinkedList list) {
        // 1、按照点数分组
        sortGroup(list, ORDER_BY_RANK, 9);
        // 2、按照花色分组
        sortGroup(list, ORDER_BY_SUIT, 3);
    }

    /**
     * 根据对应的orderBy进行分类合并
     *
     * @param list
     * @param orderBy
     * @param groupNumber
     */
    private void sortGroup(LinkedList list, int orderBy, int groupNumber) {
        LinkedList[] numList = new LinkedList[groupNumber];
        for (int i = 0; i < numList.length; i++) {
            numList[i] = new LinkedList<>();
        }
        while (!list.isEmpty()) {
            Mahjong remove = list.remove(); // 将麻将取出
            int index = remove.rank - 1; // 下标为点数-1
            if (orderBy == ORDER_BY_SUIT) { // 下标为花色-1
                index = remove.suit - 1;
            }
            numList[index].add(remove); // 放到对应链表中
        }
        // 将链表组拼在一起
        for (int i = 0; i < numList.length; i++) {
            list.addAll(numList[i]);
        }
    }

自己写一个单链表代替JDK提供的LinkedList,性能会最优。

(具体代码见GitHub)

五、双向链表 – 自定义LinedList

对比单向链表和双向链表:

  • 单向链表查询只能从前往后
  • 双向链表查询可以从前往后,也可以从后往前,增加了查询的效率

针对专项问题处理,用自己定义的链式结构要比JDK的性能更好,内存开销小10倍

/**
 * 功能:自己写的LinkedList
 * 

* Created by danke on 2018/12/1. */ public class LinkedList { transient int size = 0; // 大小 /** * 头结点 */ transient Node first; // 前驱 /** * 尾结点 */ transient Node last; // 后继 /** * Constructs an empty list. */ public LinkedList() { } /** * 添加数据 * @param e * @return */ public boolean add(E e) { linkLast(e); return true; } /** * 添加到index的位置 * @param index * @param element * @return */ public void add(int index, E element) { checkPositionIndex(index); if (index == size) { // 加到最后一位 linkLast(element); } else { // 加到指定位置 linkBefore(element, node(index)); } } /** * 加到指定位置 * @param element 需要添加的元素 * @param succ 指定的节点 */ private void linkBefore(E element, Node succ) { Node prev = succ.prev; Node newNode = new Node<>(prev, element, succ); succ.prev = newNode; if (prev == null) { first = newNode; } else { prev.next = newNode; } size++; } /** * 从尾部添加数据 * @param e */ private void linkLast(E e) { Node l = last; Node newNode = new Node<>(l, e, null); last = newNode; if (l == null) { first = newNode; } else { l.next = newNode; } size++; } public boolean isEmpty() { return size == 0; } public Object[] toArray() { Object[] result = new Object[size]; int i = 0; for (Node x = first; x != null ; x = x.next) { result[i++] = x.item; } return result; } public E remove() { return removeFirst(); } private E removeFirst() { final Node f = first; if (f == null) { throw new NoSuchElementException(); } return unlinkFirst(f); } private E unlinkFirst(Node f) { E element = f.item; Node next = f.next; f.item = null; f.next = null; // help GC first = next; if (next == null) { last = null; } else { next.prev = null; } size--; return element; } public boolean addAll(LinkedList c) { return addAll(size, c); } private boolean addAll(int index, LinkedList c) { checkPositionIndex(index); Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) { return false; } Node pred, succ; if (index == size) { pred = last; succ = null; } else { succ = node(index); pred = succ.prev; } for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; Node newNode = new Node(pred, e, null); if (pred == null) { first = newNode; } else { pred.next = newNode; } pred = newNode; // 更新最后的节点 } if (succ == null) { last = pred; } else { pred.next = succ; succ.prev = pred; } size += numNew; return false; } /** * 获取结点:链式结构查找数据比较困难,需要循环 * 单向链表和双向链表的区别就在此处:双向链表的查找会比单向链表快 * @param index * @return */ private Node node(int index) { if (index < (size >> 1)) { // 如果index在整个链表的前半部分,从前往后找 (size >> 1 = size / 2) Node x = first; for (int i = 0; i < index; i++) { x = x.next; } return x; } else { // 如果index在整个链表的后半部分,从后往前找 Node x = last; for (int i = size - 1; i > index; i--) { x = x.prev; } return x; } } private void checkElementIndex(int index) { if (!(isElementIndex(index))) { throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } } /** * 判断是否index是否越界 * @param index */ private void checkPositionIndex(int index) { if (!(isPositionIndex(index))) { throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } } private boolean isElementIndex(int index) { return index >= 0 && index < size; } private boolean isPositionIndex(int index) { return index >= 0 && index <= size; } private String outOfBoundsMsg(int index) { return "Index: " + index + ", Size: " + size; } public int size() { return size; } public E get(int index) { checkElementIndex(index); return node(index).item; } public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } private E unlink(Node x) { E item = x.item; Node prev = x.prev; Node next = x.next; if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; return item; } /** * 结点 * @param */ protected static class Node { E item; // 数据域 Node next; // 后继 Node prev; // 前驱 Node(Node prev, E element, Node next) { this.item = element; this.next = next; this.prev = prev; } } }

(一)代码解析:尾部添加数据【add】

/**
 * 从尾部添加数据
 * @param e
 */
private void linkLast(E e) {
    Node l = last;
    Node newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null) {
        first = newNode;
    } else {
        l.next = newNode;
    }
    size++;
}

数据结构与算法(三)--- 线性表之链式存储结构_第14张图片

(二)代码解析:指定位置上添加数据【add】

/**
 * 加到指定位置
 * @param element 需要添加的元素
 * @param succ 指定的节点
 */
private void linkBefore(E element, Node succ) {
    Node prev = succ.prev;
    Node newNode = new Node<>(prev, element, succ);
    succ.prev = newNode;
    if (prev  == null) {
        first = newNode;
    } else {
        prev.next = newNode;
    }
    size++;
}

数据结构与算法(三)--- 线性表之链式存储结构_第15张图片

(三)代码解析:删除指定位置的数据【remove】

/**
 * 删除指定位置的元素
 * @param x
 * @return
 */
private E unlink(Node x) {
    E item = x.item;
    Node prev = x.prev;
    Node next = x.next;
    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }
    x.item = null;
    size--;
    return item;
}

数据结构与算法(三)--- 线性表之链式存储结构_第16张图片

你可能感兴趣的:(数据结构与算法,链式存储,单链表,双链表,链式基数排序,stack)