底层实现数据结构:动态数组、链表、栈、队列

这篇博客深入探讨了数据结构的基础知识,包括动态数组的增删改查操作,链表(单链表、双向链表、双向循环链表)的实现与操作,以及栈(动态数组和链表实现)和队列(动态数组、链表和循环队列)的详细解析。博主提供了完整的源码,便于读者理解和实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录



一、动态数组


整体结构思维导图


在这里插入图片描述
-------------------------------------------------------------------------------- 回到目录

完整源码


函数含义:

  • add(),在某个下标位置添加元素;
  • remove(),移除某个下标对应的值;
  • resize(),动态扩容,当数组容量满或者空闲整个数组的3/4时,重新定义容量;
  • set(),设置某个下标的元素值;
  • get(),获取某个位置的元素值;
  • rangeCheck(),检查数组下标是否越界;
  • contains(),查找是否包含某个元素;
  • find(),找到某个元素的下标;

public class Array<E> {      
    private E[] data;
    private int size;	//元素个数
	
	//构造函数
    public Array(int capacity){
        data = (E[])new Object[capacity]; //注意如何new泛型数组
        size = 0;
    }

    public Array(){
        this(10);
    }

	//增
    public void add(int index, E e){
        if(size == data.length)
            resize(2 * data.length);

        if(index < 0 || index > size)
            throw new IllegalArgumentException("add full");

        for(int i = size - 1; i >= index; i --){
            data[i + 1] = data[i];
        }

        data[index] = e;
        size ++;
    }

    public void addFirst(E e){
        add(0, e);
    }

    public void addLast(E e){
        add(size, e);
    }

    //删
    public E remove(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("illegal");
        E e;
        e = data[index];
        for(int i = index ; i < size ; i ++){
            data[i] = data[i + 1];
        }
        size --;
        data[size] = null; // 垃圾回收。可省略。loitering objects != memory leak

        if(size == data.length / 4 && data.length / 2 != 0) // 到了1/4时才进行缩容
            resize(data.length / 2);
        return e;
    }

    public E removeFirst(){
        return remove(0);
    }

    public E removeLast(){
        return remove(size - 1);
    }

    public void removeElement(E e){
        int index = find(e);
        if(index != -1)
        	remove(index);
    }

    //扩容/缩容函数,在增和删里使用
    private void resize(int newCapacity){
        E[] newData = (E[])new Object[newCapacity];
        for(int i = 0 ;i < size ; i++){
            newData[i] = data[i];
        }
        data = newData;
	}

    //改
    public void set(int index, E e){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("illegal");
        data[index] = e;
    }

	//查
    public E get(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("illegal");
        return data[index];
    }
    
    public int getSize(){
        return size;
    }

    public int getCapacity(){
        return data.length;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    public void rangeCheck(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Index is Illegal!");
    }

    public boolean contains(E e){
        for(int i = 0 ; i < size ; i ++){
            if(data[i].equals(e))
                return true;
        }
        return false;
    }

    public int find(E e){
        for(int i = 0 ; i < size ; i ++){
            if(data[i] == e)
                return i;
        }
        return -1;
    }

    //打印函数
    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("size = %d , capacity = %d\n", size, data.length ));
        res.append("[");
        for(int i = 0 ; i < size ; i ++){
            res.append(data[i]);
            if(i < size - 1)
                res.append(",");
        }
        res.append("]");
        return res.toString();
    }

}

-------------------------------------------------------------------------------- 回到目录

二、链表


关于虚拟头结点


在向链表添加元素的过程中我们遇到了个问题,就是在链表头添加元素和在链表的其他地方添加元素逻辑上会有差别,为什么会有差别?

答:因为在添加元素的过程中,我们总要找到待添加元素节点的前一个节点。但是添加第一个节点(头结点)的时候没有前一个节点,所以在添加第一个节点时逻辑就会特殊一些。

因此为了统一操作,方便作业,给头节点造一个虚拟头节点,里面不包含任何元素。

虚拟头结点习惯性命名为dummyHead。这是约定俗成问题,大家把这个空节点叫做真正的头节点。

整体结构思维导图


在这里插入图片描述
-------------------------------------------------------------------------------- 回到目录

1、单链表


增加节点的操作顺序图示:
在这里插入图片描述

删除节点的操作顺序图示:
在这里插入图片描述

完整源码


public class SingleList<E> {
	//内部类
    private class Node{
        public E val;
        public Node next;

        public Node(E val, Node next) {
            this.val = val;
            this.next = next;
        }
        
        public Node(E val) {
            this(val,null);
        }
        
        public Node(){
            this(null,null);
        }

        @Override
        public String toString() {
            return val.toString();
        }
    }

    private Node dummyHead; //虚拟的头结点
    private int size; //表示链表当前的元素

	//构造函数
    public SingleList() {
        dummyHead = new Node();
        size = 0;
    }

    //增
    public void add(int index,E e){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("Index is Illegal ! ");
        }
        
        Node prev = dummyHead;
        for(int i = 0; i < index; i++) 
        	prev = prev.next;
        	
        Node node = new Node(e);
        node.next = prev.next;
        prev.next = node;
        size++;
    }
    
    public void addFirst(E e){
        add(0,e);
    }
    
    public void addLast(E e){
        add(size,e);
    }

    //删
    public E remove(int index){
        rangeCheck(index);
        Node prev = dummyHead;
        for(int i = 0; i < index; i++) 
        	prev = prev.next;
        	
        Node delNode = prev.next;
        prev.next = delNode.next;
        delNode.next = null;
        size--;
        return delNode.val;
    }
    
    public E removeFirst(){
        return remove(0);
    }
    
    public E removeLast(){
        return remove(size - 1);
    }

    public void removeElement(E e){
        Node prev = dummyHead;
        while(prev.next != null){	//找到对应e的prev
            if(prev.next.val.equals(e)){
                break;
            }
            prev = prev.next;
        }

        if(prev.next != null){
            Node delNode = prev.next;
            prev.next = delNode.next;
            delNode.next = null;
            size--;
        }
    }

	//改
    public void set(int index,E e){
        rangeCheck(index);
        Node cur = dummyHead.next;
        for(int i = 0; i < index; i++)
        	cur = cur.next;
        cur.val = e;
    }

    //查
    public E get(int index) {
        rangeCheck(index);
        Node cur = dummyHead.next;
        for (int i = 0; i < index; i++) 
        	cur = cur.next;
        return cur.val;
    }
    
    public E getFirst(){
        return get(0);
    }
    
    public E getLast(){
        return get(size - 1);
    }

    public int getSize(){
        return size;
    }
    
    public boolean isEmpty(){
        return size == 0;
    }

    public void rangeCheck(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Index is Illegal!");
    }

    public boolean contains(E e){
        Node cur = dummyHead.next;
        while(cur != null){
            if(cur.val.equals(e)){
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

	//打印函数
    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        Node cur = dummyHead.next;
        while(cur != null){
            res.append(cur + "->");
            cur = cur.next;
        }
        res.append("NULL");

        return res.toString();
    }

}

-------------------------------------------------------------------------------- 回到目录

2、双向链表


增加节点的操作顺序图示:
在这里插入图片描述

删除节点的操作顺序图示:
在这里插入图片描述

完整源码


public class DoubleList<E> {
	//内部类
    private class Node{
        public E val;
        public Node prev;
        public Node next;

        public Node(E val, Node prev, Node next) {
            this.val = val;
            this.prev = prev;
            this.next = next;
        }
        public Node(E val){
            this(val,null,null);
        }
        public Node(){
            this(null,null,null);
        }

        @Override
        public String toString() {
            return val.toString();
        }
    }

    private Node dummyHead;
    private int size;

	//构造函数
    public DoubleList() {
        dummyHead = new Node();
        size = 0;
    }

	//增
    public void add(int index,E e){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("Index is Illegal ! ");
        }
        
        Node pre = dummyHead;
        for(int i = 0; i < index; i++) 
        	pre = pre.next;
        Node node = new Node(e);
        Node nxt = pre.next;

		//操作顺序如图示
        node.prev = pre;
        node.next = (nxt == null) ? null : nxt;
        pre.next = node;
        if(nxt != null)
        	nxt.prev = node;
        	
        size++;
    }
    
    public void addFirst(E e){
        add(0,e);
    }
    
    public void addLast(E e){
        add(size,e);
    }

	//删
    public E remove(int index){
        rangeCheck(index);
        if(isEmpty())
        	return null;
        Node pre = dummyHead;
        for(int i = 0; i < index; i++) 
        	pre = pre.next;
        	
        Node delNode = pre.next;
        pre.next = delNode.next;
        if(delNode.next != null)
        	delNode.next.prev = pre;	//!!!
        	
        size--;
        return delNode.val;
    }
    
    public E removeFirst(){
        return remove(0);
    }
    
    public E removeLast(){
        return remove(size-1);
    }

	//查
    public int getSize(){
        return size;
    }
    public boolean isEmpty(){
        return size == 0;
    }

    public void rangeCheck(int index){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Index is Illegal!");
    }

	//打印函数
    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();

        Node cur = dummyHead.next;
        while(cur != null){
            res.append(cur + "->");
            cur = cur.next;
        }
        res.append("NULL");

        return res.toString();
    }

}

-------------------------------------------------------------------------------- 回到目录

3、双向循环链表


双向循环链表图示:
在这里插入图片描述

增加节点的操作顺序图示:
在这里插入图片描述

删除节点的操作顺序图示:
在这里插入图片描述

注意:

  • 插入的时候要注意如果是在最后插入的话,可以直接通过dummyHead(虚拟头结点找到最后的),然后直接插入;
  • 在get的时候,可以判断一下从哪边开始查找,加快查找速度;

完整源码


public class MyLinkedList<E>{
	//内部类
    private class Node{
        public E e;
        public Node prev;
        public Node next;

        public Node(E e, Node prev, Node next) {
            this.e = e;
            this.prev = prev;
            this.next = next;
        }

        public Node(E e){
            this(e,null,null);
        }
        
        public Node (){
            this(null,null,null);
        }

        @Override
        public String toString() {
            return e.toString();
        }
    }

    private Node dummyHead;
    private Node tail;
    private int size;

	//构造函数
    public MyLinkedList() {
        dummyHead = new Node();
        tail = dummyHead;
        
        //dummyHead自成一环
        dummyHead.next = tail;
        dummyHead.prev = tail;
        tail.next = dummyHead;
        tail.prev = dummyHead;
        size = 0;
    }

    //增 
    //add Last
    public void add(E e){
    	//操作顺序如图示
        Node node = new Node(e);
        node.prev = tail;
        node.next = dummyHead;
        tail.next = node;
        dummyHead.prev = node;
        tail = node;
        size++;
    }

    public void add(int index, E e){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("Index is Illegal ! ");
        }
        
        if(index == size){ //add to Last
            add(e);
            return;
        }
        
        Node pre = dummyHead;
        for(int i = 0; i < index; i++)
        	pre = pre.next;
        Node nxt = pre.next;
        Node node = new Node(e);

		//操作顺序如图示
        node.prev = pre;
        node.next = nxt;
        pre.next = node;
        nxt.prev = node;
        size++;
    }

	//删
    public E remove(int index){
        rangeCheck(index);
        if(index == size - 1){
            return removeLast();
        }
        
        Node pre = dummyHead;
        for(int i = 0; i < index; i++)
        	pre = pre.next;
        Node delNode = pre.next;
        pre.next = delNode.next;
        delNode.next.prev = delNode.prev;
        size--;
        return delNode.e;
    }

    public E removeLast(){
        E ret = tail.e;
        tail.prev.next = tail.next;
        tail.next.prev = tail.prev;
        tail = tail.prev;	//改变tail
        size--;
        return ret;
    }

	//查
    public E get(int index){
        rangeCheck(index);
        Node cur = dummyHead;
        if(index < (size << 1)){
            for(int i = 0; i < index + 1; i++)
            	cur = cur.next;
            return cur.e;
        }
        else {
            for(int i = 0; i < index + 1; i++)
            	cur = cur.prev;
            return cur.e;
        }
    }

    public int getSize(){
        return size;
    }
    public boolean isEmpty(){
        return size == 0;
    }

    public void rangeCheck(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("Index is Illegal ! ");
        }
    }

	//打印函数
    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        Node cur = dummyHead.next;
        while(cur != dummyHead){
            res.append(cur.e + "->");
            cur = cur.next;
        }
        res.append("NULL");
        return res.toString();
    }

}

-------------------------------------------------------------------------------- 回到目录

三、栈


接口:

public interface Stack<E> {
	void push(E e);	  //将元素压入栈顶
    E pop();	//移除栈顶元素
    int getSize();	//查看栈中元素的长度   
    boolean isEmpty();	//判断栈顶是否为空
    E peek();	//查看栈顶元素
}

1、基于动态数组实现的栈


除 Array 中的方法外,额外在 Stack 工程中的 Array 类里添加两个方法:

    public E getLast(){
        return get(size - 1);
    }

    public E getFirst(){
        return get(0);
    }

完整源码


public class ArrayStack<E> implements Stack<E> {
    Array<E> array;

    public ArrayStack(int capacity){
        array = new Array<>(capacity);
    }

    public ArrayStack(){
        array = new Array<>();
    }

    @Override
    public int getSize(){
        return array.getSize();
    }

    @Override
    public boolean isEmpty(){
        return array.isEmpty();
    }

    public int getCapacity(){
        return array.getCapacity();
    }

    @Override
    public void push(E e){
        array.addLast(e);
    }

    @Override
    public E pop(){
        return (E) array.removeLast();
    }

	@Override
    public E peek(){
        return (E) array.getLast();
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Stack: [");
        for(int i = 0 ; i < array.getSize() ; i++){
            res.append(array.get(i));
            if(i != array.getSize() - 1 )
                res.append(",");
        }
        res.append("] top");
        return res.toString();
    }
}

-------------------------------------------------------------------------------- 回到目录

2、基于链表实现的栈


完整源码


public class LinkedListStack<E> implements Stack<E>{
    private SingleList<E> list;

    public LinkedListStack(){
        list = new LinkedList<>();
    }

    @Override
    public int getSize(){
        return list.getSize();
    }

    @Override
    public boolean isEmpty(){
        return list.isEmpty();
    }

    @Override
    public void push(E e){
        list.addFirst(e);
    }

    @Override
    public E pop(){
        return list.removeFirst();
    }

    @Override
    public E peek(){
        return list.getFirst();
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Stack :");
        res.append(list);
        res.append("love you baby");
        return res.toString();
    }
}

-------------------------------------------------------------------------------- 回到目录

测试二者的时间复杂度:

import java.util.Random;

public class Main {

    public static double testStack(Stack<Integer> stack , int opCount){
        long startTime = System.nanoTime();
        Random random = new Random();
        for(int i = 0 ; i < opCount ; i ++)
            stack.push(random.nextInt(Integer.MAX_VALUE));
        for(int i = 0 ; i < opCount ; i ++)
            stack.pop();
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }

    public static void main(String[] args){
        ArrayStack<Integer> arr = new ArrayStack<>();
        LinkedListStack<Integer> lin = new LinkedListStack<>();
        int time = 10000000;
        double tt = testStack(arr , time);
        System.out.println("arr = " + tt);

        double tw = testStack(lin , time);
        System.out.println("lin = " + tw);
    }
}

-------------------------------------------------------------------------------- 回到目录

四、队列


接口:

public interface Queue<E> {
    void enqueue(E e);	//元素进入队列
    E dequeue();	//元素退出队列
    E getFront();	//查看队头的第一个值
    int getSize();	//获取队列长度
    boolean isEmpty();	//判断队列是否为空
}

1、基于动态数组实现的队列


除 Array 中的方法外,额外在 Queue 工程中的 Array 类里添加一个方法:

    public E getFirst(){
        return get(0);
    }

完整源码


public class ArrayQueue<E> implements Queue<E>{
    //我们要用自己写的逻辑套进Queue的函数里面,所以我们要建一个变量array来实现queue应该做的事。
    //除了本身带有的函数外,别忘了写两个构造函数、一个查看队列中最多可装多少元素的函数getCapacity和一个打印toString函数
    //重写接口里的的函数时记得写 @Override

    private Array<E> array;

    public ArrayQueue(int capacity){
        array = new Array<>(capacity);
    }

    public ArrayQueue(){
        array = new Array<>();
    }

    public int getCapacity(){
        return array.getCapacity();
    }

    @Override
    public void enqueue(E e){
        array.addLast(e);
    }

    @Override
    public int getSize(){
        return array.getSize();
    }

    @Override
    public boolean isEmpty(){
        return array.isEmpty();
    }

    @Override
    public E dequeue(){
        return array.removeFirst();
    }

    @Override
    public E getFront(){
        return array.getFirst( );
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Queue:"));
        res.append("f [");
        for(int i = 0 ; i < array.getSize() ; i ++){
            res.append(array.get(i));
            if(i < array.getSize() - 1)
                res.append(",");
        }
        res.append("] t");
        return res.toString();
    }

}

-------------------------------------------------------------------------------- 回到目录

2、基于链表实现的队列


注意:

  • 为了dequeue的方便,增加了一个tail指针指向尾节点;
  • 注意队列为空的情况要特判
    在这里插入图片描述

完整源码


public class LinkedListQueue<E> implements Queue<E> {
    private class Node {         
        public E e;
        public Node next;

        public Node(E e, Node next) {
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e, null);
        }

        public Node(){
            //this.e = null;
            // this.next = null;
            this(null, null);
        }

        @Override
        public String toString(){
            return e.toString();
        }
    }

    private Node head, tail;
    private int size;

    public LinkedListQueue(){
        head = null;
        tail = null;
        size = 0;
    }

    @Override
    public int getSize(){
        return size;
    }

    @Override
    public boolean isEmpty(){
        return size == 0;
    }

    @Override
    public void enqueue(E e){
        if(tail == null){
            tail = new Node(e);
            head = tail;
        }
        else{
            tail.next = new Node(e);
            tail = tail.next;
        }
        size ++;
    }

    @Override
    public E dequeue(){
        if(isEmpty())
            throw new IllegalArgumentException("fail");
        Node res = head;
        head = head.next;
        res.next = null;
        if(head == null)
            tail = null;
        size --;
        return res.e;
    }

    @Override
    public E getFront(){
        if(isEmpty())
            throw new IllegalArgumentException("fail");
        return head.e;
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Queue:");
        Node cur = head;
        while(cur != null){
            res.append(cur + "->");
            cur = cur.next;
        }
        return res.toString();
    }
}

-------------------------------------------------------------------------------- 回到目录

3、循环队列


注意:

  • 循环队列的内置数组中要浪费一个空间用来区分队列空和满;
  • 循环队列的出队操作比ArrayQueue要快很多,原因在于ArrayQueue出队要移动大量元素;
    在这里插入图片描述

完整源码


public class LoopQueue<E> implements Queue<E>{
    private E[] data;//一个数组,即一个抽象上的容器,用来装数据形成队列的
    private int front;
    private int tail;
    private int size;

    public LoopQueue(int capacity){
        data = (E[])new Object[capacity + 1];
        front = 0;
        tail = 0;
        size = 0;
    }

    public LoopQueue(){
        this(10);
    }

    @Override
    public int getSize(){	//查看现在的队列中一共有几个元素
        return size;
    }

    public int getCapacity(){
        return data.length - 1;	//整个容器长度 - 1 为真正队列的长度
    }

    @Override
    public boolean isEmpty(){
        return front == tail;
    }

	//扩容/缩容函数
    private void resize(int newCapacity){
        E[] newData = (E[])new Object[newCapacity + 1];
        for(int i = 0 ; i < size ; i ++){
            newData[i] = data[(i + front) % data.length];
        }
        data = newData;
        front = 0;
        tail = size;
    }

    @Override
    public void enqueue(E e){
        if((tail + 1) % data.length == front)	//判断队列是否是满的,满的话则扩容
            resize(getCapacity() * 2);
        data[tail] = e;
        size ++;
        tail = (tail + 1) % data.length;
    }

    @Override
    public E dequeue(){
        E ret = data[front];
        if(isEmpty())
            throw new IllegalArgumentException("fail");
        data[front] = null;
        front = (front + 1) % data.length;
        size --;
        if(size == getCapacity() / 4 && getCapacity() / 2 != 0)
            resize(getCapacity() / 2);
        return ret;
    }

    @Override
    public E getFront(){
        if(isEmpty())
            throw new IllegalArgumentException("fail");
        return data[front];
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Queue: size = %d , capacity = %d", size, getCapacity()));
        res.append("f [");
        for(int i = front; i != tail; i = (i + 1) % data.length){
            res.append(data[i]);
            if((i + 1) % data.length != tail)
                res.append(",");
        }
        res.append("] t");
        return res.toString();
    }
}

-------------------------------------------------------------------------------- 回到目录

比较数组队列和循环队列的运行时间:

import java.util.Random;

public class time { 
    //private double test = 100000;
    private  static  double testQueue(Queue<Integer> q, int opCount){
        long start = System.nanoTime();
        Random random = new Random();
        for(int i = 0; i < opCount; i++)
            q.enqueue(random.nextInt(Integer.MAX_VALUE));
        for(int i = 0; i < opCount ; i++)
            q.dequeue();

        long end = System.nanoTime();
        //double end;

        return (end - start) / 1000000000.0;
    }
    public static void main(String[] args){
        int opCount = 100000;
        ArrayQueue<Integer> arr = new ArrayQueue<>();
        double t1 = testQueue(arr, opCount);
        System.out.println(t1);
        LoopQueue<Integer> loo = new LoopQueue<>();
        double t2 = testQueue(loo, opCount);
        System.out.println(t2);
    }
}

-------------------------------------------------------------------------------- 回到目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值