数据结构基础-(数组、链表、优先队列...)

[数组, 单链表, 双链表, 优先队列, ByteBuffer(可变读写)]

以下均为伪代码、提供示例。

数组

创建一个数组:

Object[] values = new Object[1024];

 

创建的数据长度为1024 ,这个是指定长度的数组,是一个不可变长的数组。

可以通过index操作数组的数据获得与修改。values[10] = 1000; 

 

可变长数组

可变长数组是在基本的数组上做的扩展,预先分配一个固定数组,和一个 writeIndex (写入位置)的变量。

封装一个方法 public void add(Object value);

当调用 add 写入数据的时候,数据写入位置为 this.values[writeIndex] = value;

改变这个index的值后,writeIndex++ (下一次写入位置将会+1)。

如果 writeIndex 的长度 与 this.values.length 相等,则无法继续写入,因为数组越界了,此时重新分配一个比现在的数组还要长一些的新数组 nvalues = new Object[新的长度要比久的长度大]

将原values数据 copy 到 nvalues 后,替换values的引用(this.values=nvalues)

 

Object[] values = new Object[16];
private int writeIndex = 0;
public void add(Object value) {
    if (writeIndex == values.length) {
        Object[] nvalues = new Object[values.length + 16];
        System.arraycopy(values, 0, nvalues, 0, values.length);
        this.values = nvalues;
    }
    
    values[writeIndex++] = value;
}

可变数组的优点继承了数组操作的优点,

对数据查询获取速度比较迅速。

但是删除数据性能较低。例如移除 index = 0 的数据,从index = 1 到 index = writeIndex-1的位置都有向前移动数据。

 

 

单链表

单链表一般可用于栈操作,先进先后出,从头入栈从头出栈。

 

入栈时 如果栈内有无数据直接入栈,如果栈内有其他节点,则将新的节点的下节点指向目前的头节点。头节点(栈顶)则替换为当前节点。(本文均为伪代码,仅提供思路)

class Node {
    Node next; 
    Object value;
}
class Stack {
    Node head;
    public void add(Object value) {
	Node node = new Node(value);
	node.next = head;
	head = node;
    } 
}

单链表对于只操作顶端数据时,移除顶端只需要从新将头节点 指向当前原头节点下一节点。

public void removeFirst() {
    head = head.next;
}

双链表

双链表对于操作头尾端点,或对某节点移除操作时更为迅速。但查询数据需要遍历节点所以查询数据并不时最优的选择。选择双链表还是可变数组的要关注业务情况,查询与节点修改的多少。

双链表的节点拥有 上节点引用与下节点引用。 两个并列节点相互引用。

插入节点的时候,可以插入头或插入尾部,例如插入尾部的时候,将新节点的上一节点指向当前节点的末尾节点,末尾节点的下一节点指向新节点,则新节点此时作为末尾节点。

删除中间或头节点的时候,不需要向数组那样向前移动下标。

只需要将当前节点的上一节点指向当前节点的下节点,当前节点的下节点指向当前节点的上节点,当前节点上下节点赋NULL即可。

// 节点
class Node {
    Node prev;  // 上一节点
    Node next;  // 下一节点
    Object value;
}
class LinkedList {

    Node head = null;
    Node foot  = null;
    // 添加节点
    public void add(Object value) {
        Node node = new Node(value);
        if (head == null) {
        foot = head = node;
        } else {
            node.prev = foot;
            foot.next = node;
            foot = node
        }
    }


    // 删除某节点时
    public void remove(Node node) {
        if (node == head && node == foot) {
            head = null;
            foot = null;
        }
        Node prev =  node.prev;
        Node next =  node.next;
        node.prev = null;
        node.next = null;
        if (prev != null ) {
            prev.next = next;

        }
        if (next != null ) {
            next.prev = prev;
        }
    }

}

 

优先队列

 

优先队列处理的问题,例如千万的数据中查询出前几的操作, 随机对数据优先级最高的先输出。

普通队列时对头尾追加或移除的操作, 先队列中元素拥有优先级,优先级最高的元素将被取出。

创建优先队列时候,定义一个数组来存储每个元素, 假设有 put(num)  pop() 两个方法做进出处理。

put 方法将一个数据插入到当前数组的可写入位置末端(用writeIndex 记录)。

 

此时将一个数组看作一个 平衡二叉树。假设数组内数值越大则优先级越高。put时插入末端的元素实际上为树的末节点,此节点将与父节点做比较,大于父则与父交换位置,并依次向上比较,直到没有比它更大的或者到节点头(index = 0)

数组 : [0][1][2][3][4][5][6], 对应的树结构如下:

           0

    1            2

 3      4    5      6

那么每个节点有一个规律,

节点的 父节点index = (当前节点 - 1) / 2

左子节点 = 当前节点 x 2 + 1

右子节点 = 当前节点 x 2 + 2

 

int values[] = new int[10000000];
int writeIndex = 0;
void put (int value) {
    values[writeIndex ++] = value;
    while (index > 0) {
        int parentIndex = parentIndex(index);
        int parentValue =  this.values[parentIndex];
        if (value > parentValue) { 			
            this.values[index] = parentValue;
            this.values[parentIndex] = value;
            index = parentIndex;
        } else {
            break;
        }		
    }
}

 

pop取出数据操作的时候,是保证了,优先级越高的优先出队。

每次取出数据实际上是返回 index =0 的数据,要保证每次优先级最高的取出数据,取出数据后将要将空位数据下沉,下沉过程比较左子节点与右子节点的大小,优先级大的与本节点替换,依次下沉到末端,比较次数实际为二叉树的层数量。

int pop () {

    int popvalue = this.values[0];
    int value = this.values[--writeIndex];
    this.values[0] = value;
    int currentIndex = 0; 
        
    while (true) {
    int index = currentIndex * 2 + 1;  // left node
    if (index >= writeIndex) {
        break;
    }
    int rightIndex = index + 1;  // right node = currentIndex * 2 + 2
    if (rightIndex < writeIndex && this.values[rightIndex] > this.values[index]) {
        index = rightIndex;
    }
    if (value >= this.values[index]) {
        break;
    }
    this.values[currentIndex] = this.values[index];
    this.values[index] = value;
    currentIndex = index;
    }
    return popvalue;
}

ByteBuffer 读写 (简介)

 

这种数据结构的形式是一个 byte[] 的可变数组实现 (参考可变数组), 

他有几个常用方法:

write(b), read() 写入一个字节或读取一个字节。他有两个指向下标writeIndex readIndex

markWriteIndex(); 标记目前写入位置,当调用 resetWriteIndex 时写index回归当前标记。

markReadIndex(); 标记目前读取位置,当调用 resetReadIndex 时读index回归当前标记。

resetWriteIndex(); markWriteIndex 默认 -1

resetReadIndex(); markReadIndex 默认 -1

每次调用read都会读取一个新的数据,直到读取不到数据时发生异常。

写入数据会一直向后写入,当写入空间达到 数组.length的值时,则自动扩容。

发生扩容时容量和index的位置变化:

当没有标记 markReadIndex时

新的容量 大于 (writeIndex - readIndex);

当前数组从 index = readIndex 位置, copy长度 = writeIndex - readIndex 数据到 新生成的数组中(0index开始)。然后将新数组替换原引用后,重新赋值read与write index

此时 

writeIndex = writeIndex - readIndex

readIndex = 0,

当标记 markReadIndex 时

新的容量 大于 (writeIndex - markReadIndex)

当前数组从 index = markReadIndex 位置, copy长度 = writeIndex - markReadIndex 数据到 新生成的数组中(0index开始)。然后将新数组替换原引用后,重新赋值read与write index

此时:

writeIndex = writeIndex - markReadIndex

readIndex = readIndex - markReadIndex

markReadIndex = 0 

假设 markReadIndex = 10 , readIndex = 20 , writeIndex = 30

真实数据大小(从mark-windex) = writeIndex - markReadIndex = 20

可以读取长度(从rindex-windex)= writeIndex - readIndex = 10

扩容后:markReadIndex  = 0, readIndex = 10, writeIndex = 20

 

一般用于处理封装协议的连续数据,当读取的数据不完整不能被解析,需要等待后续内容连接成完整的数据才能解析,可以使用此实现将读取的数据暂时缓存。等待读取的内容足够完整在进行解析。


byte[] values = new byte[capacity];
int writeIndex;    // 写位置
int readIndex;    // 读位置
int makeReadIndex = -1; // 标记读读位置

int makeWriteIndex = -1;


public void put(int b) {
    // 整理并扩容
    if (writeIndex == values.length) {
        int leftindex = readIndex;
        if (makeReadIndex != -1) {
            leftindex  = makeReadIndex;
            this.makeReadIndex = 0;
        }
        int length = writeIndex - leftindex;
        byte[] nvalues = new byte[length + 10];
        System.arraycopy(values, leftindex, nvalues, 0, length);
        readIndex = readIndex - leftindex;
        writeIndex = writeIndex - leftindex;
        if (makeWriteIndex != -1) {
            makeWriteIndex = makeWriteIndex - leftindex;
        }
        this.values = nvalues;
    }

    values[writeIndex ++] = (byte) b;
}

public int read() {
    if (readIndex == writeIndex) {
        throw new ArrayIndexOutOfBoundsException(“越界了");
    }
    return values[readIndex++];
}
// 标记读位置
public void markReadIndex() {
    makeReadIndex = readIndex;
}

public void markWriteIndex() {
    makeWriteIndex = writeIndex;
}
// 还原读位置
public void resetReadIndex() {
    if (makeReadIndex != -1) {
        readIndex = makeReadIndex;
        makeReadIndex = -1;
    }
}
public void resetWriteIndex() {
    if (makeWriteIndex != -1) {
        writeIndex = makeWriteIndex;
        makeWriteIndex = -1;
    }
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值