Java数据结构和算法(一)——链表、栈、队列

一 前言

由于做的是应用层开发,因此对数据结构理解不够透彻,有时候优化代码,或者定位问题时,往往陷入挣扎。最近在看数据结构方面的内容。现在重新过一下常用的数据结构和算法。Java集合类基本实现了所有的数据结构和算法,我这边会简单实现一些数据结构。

二 算法

2.1 算法的特性

算法和数据结构是互为一体的,互利共生的。一个算法要具备以下特性:

  • 输入,输出;
  • 有穷性(如果一个算法陷入死循环,或者要计算几年时间,那就没什么意义了);
  • 确定性(在计算机世界,是要解决确定问题,不能出现人类社会模棱两可的情况);
  • 可行性(执行有限的次数后,要有结果)。

2.2 衡量算法效率的两个标准

一个算法要考虑时间成本(时间复杂度)和空间存储成本(空间复杂度)。这两个概念我相信很多软件行业的从业者不是很清楚。

2.2.1 时间复杂度

先看这个时间成本如何估算,用一个时间复杂度的概念计算,数学上记为T(n)。
举个例子1-100之间求和,来理解一下事件复杂度这个概念。

  • 实现一(一般大家都是这样实现的)
public int sumTotal1(int total){
   int sum=0;               //执行1次
   int n=total;             //执行1次
   for (int i=1;i<=n;i++){   //执行n+1次
       sum=sum+i;            //执行n次
   }
   return sum;               //执行1次
}

  • 实现二(二百年前的高斯小朋友实现方法,不愧是神童)
public int sumTotal2(int total){
    int sum=0;            //执行1次
    int n=total;          //执行1次
    sum=(1+n)*n/2;        //执行1次
    return sum;           //执行1次
}

实现一的算法执行次数是2n+4次,实现二的算法执行次数是4次。
前面提到的算法时间复杂度与两个因素有关,一是问题输入规模(也就是n),二是执行次数。
这里可以看出实现一的是随着n的变化,线性增加的。
实现二,n无论如何变化,都是常数。
现在简化一下实现一的表达式,去掉常数项,只保留最高阶,并去掉最高阶的系数。
实现一的算法时间复杂度可记为O(n);
实现二的算法时间复杂度可记为O(1);
常用时间复杂度耗费时间从小到大排序依次是:

O(1)

想想如果n取比较小的值20,0(n的n次幂)是20的20次幂,这个执行次数需要耗费多长时间,想想都是噩梦。

2.2.2 空间复杂度

如果一个算法执行所需的空间不算输入数据规模的变化而变化,就记为O(1)。

2.2.3 小结

一个算法效率取决于时间复杂度和空间复杂度,我们要根据实际情况抉择,平衡两者之间的度。

三 数据结构

常见的数据结构有线性顺序表、链表、栈(注意和内存中的栈不是一个意思)、队列、树、图。
下面会简单讲解常用数据结构的实现,以及四中基本操作算法的时间复杂度。

3.1 线性顺序表(类似于Java集合中的ArrayList)

3.1.1 用数组自定义实现线性顺序表

/**
 * 用数组自定义实现线性顺序表
 * Java的ArrayList默认就是用动态数据实现的
 */
public class MyArrayList{

    private int maxLength=10; //数组默认大小;
    private int size;        //数组当前下标;
    private Object[] elementDatas;
    
    public MyArrayList(int length){
        this.maxLength=length;
        this.elementDatas=new Object[length];
    }
    
    public MyArrayList(){
        this(10);
    }
    
    public boolean addItem(Object e){
        if(size==maxLength){
            //超出设定的长度,返回false
            return false;
        }
        elementDatas[size++]=e;
        return true;
    }
    
    public Object getItem(int index){
        if(index<0||index>size) return null
        return elementDatas[index];
    }
    
    void ensureCap(int cap){
        Object[] T=elementDatas;
        elementDatas=T;
        for(int i=0;isize){
            //index不在数组范围内
            return false;
        }
        if(index<=size){
            for(int k=size;k>=index;k--){
                  //将下标index到size的元素向后移动一位
                  elementDatas[k+1]=elementDatas[k];
                }
                
            }
        }
        elementDatas[index]=e;
        index++;
        return true;
        
    }
    //移除指定位置元素
    public boolean remove(int index){
        if(index==0) return false;
        if(index<0||index>size)return false;
        if(index<=size){
            for(int k=index;k

3.1.2 增删改查的算法时间复杂度

get set add remove
O(1) O(1) O(n) O(n)

3.1.3 小结

上面的时间复杂度表明,线性顺序表随机读写元素的效率高,插入和删除元素的效率低。

3.2 线性单链表

链表实现原理图,可参考文章2,画的原理图很形象。

3.2.1 自定义实现单链表

public class MySingleLinkList{
    //定义结点结构
    class Node{
        //结点数据
        private Object data;
        //结点指针
        private Node next;
        
        Node(Object e){
            this.data=e;
        }
    }
    
    public MySingleLinkList(){
        
    }
    
    public MySingleLinkList(Object o){
        this.data=o;
    }
    
    public Node head;//头指针
    public Object data;
    
    public boolean isEmpty(){
        return this.head==null;
    }
    
    //获取链表长度
    public int length(){
        Node p=head; //定义移动指针
        int i=0;
        //节点循环
        while(p!=null){
            i++;
            p=p.next;
        }
        return i;
    }
    
    //添加元素
    public boolean add(int index,Object o){
        if(o!=null&&index>=0){
        Node q=new Node(o); //构造新结点
            if(head==null||index==0){
                //空链表或者表头添加元素
                q.next=head;
                head=q;
            }else{
                //表中间或者结尾插入元素
                //定义移动指针
                Node p=head;
                int i=0;
                //循环找到插入节点前一个节点
                while(p.next!=null&&i=0){
            Node p=head;//定义移动指针
            int i=0;
            while(p.next!=null&&i=0&&o!=null){
           Node p=head; //定义移动指针;
           int i=0;
           while(p.next!=null&&i=0){
            if(index==0){
                //删除头节点
                head=head.next;
            }else{
                Node p=head;
                int i=0;
                while(p.next!=null&&i

3.2.2 增删改查算法时间复杂度

get set add remove
O(n) O(n) O(n) O(n)

链表的添加和删除时间复杂度是O(n),因为需要循环查找位置,找到位置以后,赋值操作是O(1),所以添加删除元素的效率要比线性顺序列表高。

3.2.3 小结

单链表添加删除元素效率较高,随机查询元素效率较低。

3.3 双链表(类似于JAVA集合中的LinkedList)

/**
 * 双链表实现 
 */
public class MyDoubleLinkedList {

    class Node{

        private Object data;
        private Node prev;  //前指针
        private Node next;  //后指针

        Node(){

        }
        //构造节点
        Node(Object data){
            this.data=data;
        }
    }



    private Node head; //头指针
    private int size;// 链表索引长度

    public MyDoubleLinkedList(){
        //初始化头指针
        this.head=new Node();
    }


    //尾部添加元素
    public boolean add(Object object){
        if (object!=null){
           Node p= head;
           while (p!=null){
               p=p.next;
           }
           Node q=new Node(object);
           p.next=q;
           q.prev=p;
           size++;
           return true;
        }
        return false;
    }

    //添加指定索引位置的数据
    public boolean add(int index,Object object){
        if(object!=null&&index>=0){
            if (index>size){
                add(object);
            }else {
                Node p=head;//定义移动指针
                int i=0;
                while (p!=null&&i=0){
            if (index==0) index=1;
            Node p=head;
            int i=0;
            while (p!=null&&i=0&&o!=null){
            Node p=head; //定义移动指针;
            int i=0;
            while (p!=null&&i=0){
            Node p=head;
            int i=0;
            while (p!=null&&i

3.4 循环链表

3.5 栈

概念已经被大家说烂了(后进先出或者先进后出的线性表结构)。

3.5.1 数组实现栈(类似与Java中的ArrayDueue)

public class MyArrayStack{
    private int size; //内部数组当前下标
    private int maxLength=10; //内部数组默认长度;
    private int top=-1;//栈顶指针;
    private Object[] elementDatas;
    
    MyArrayStack(int length){
        this.maxLength=length;
        this.elementDatas=new Object[length];
        
    }
    MyArrayStack(){
        this(10);
    }
    
    //压栈
    public void push(Object e){
        if(elementDatas.length==size){
            //动态扩容
            ensureCap(size*2);
        }
        size++;
        elementDatas[++top]=e;
        
    }
    
    //出栈-从栈顶移除元素
    public Object pop(){
        if(top==-1) new StackExceptio();
        size--;
        renturn element[top--];
    }
    
    //查询栈顶元素
    public Object peek(){
        if(top==-1) new StackException();
        return element[top];
    }

    
    //内部数组扩容
    void ensureCap(int cap){
        if(cap

3.5.2 顺序表(ArrayList)实现栈

public class MyColStack{
    private ArrayList arrayList;
    MyColStack(){
        arrayList=new ArrayList();
    }
    public void push(Object e){
        arrayList.add(e);
    }
    public Object pop(){
        if(arraylist.size==0) new EmptyStackExceptio();
        return arrayList.remove(arrayList.size()-1);
    }
    
    public Object peek(){
        if(arrayList.size==0) new EmptyStackException();
        return arrayList.get(arrayList.size()-1);
    }
}

3.5.3 链表栈

public class MyLinkedStack {
    //节点
    class Node{
        Node next;
        Object object;

        Node(Object o){
            this.object=o;
        }
        Node(){

        }
    }

    //头节点
    private Node head;
    private int size;


    public MyLinkedStack() {
        this.head = null;
        size = 0;

    }

    public boolean isEmpty(){
        return this.head==null;
    }

    public int length(){
        return size;
    }

    /**
     * 入栈-在表头插入数据
     * @param o
     */
    public void push(Object o){
        if (o==null) return ;
        Node old=head;
        head=new Node();
        head.object=o;
        head.next=old;
        size++;
    }

    /**
     * 出栈
     * @return
     */
    public Object pop(){
        if (isEmpty()) return new NoSuchElementException("stack ");
        Object item=head.object;
        head=head.next;
        size--;
        return item;
    }

    /**
     * 查看栈顶元素
     * @return
     */
    public Object peek(){
        if (isEmpty()) return new NoSuchElementException("stack ");
        Object item=head.object;
        return item;
    }
}

3.5.4 栈算法时间复杂度

push pop peek
O(1) O(1) O(1)

3.6 队列

队列实现原理图,可参考文章2,画的原理图很形象。

3.6.1 利用数组实现顺序队列

public class MySeqQueue {

    private Object[] value; //存储元素;
    private int front;
    private int rear;

    MySeqQueue(int length){
        value=new Object[Math.abs(length)];
        front=-1;
        rear=-1;
    }

    MySeqQueue(){
        this(16);
    }

    public boolean isEmpty(){
        return front==-1&&rear==-1;
    }

    //扩展容量
    void ensureCap(int size){
        Object[] T=value;
        value=new Object[size];
        for (int i=0;i

3.6.2 循环队列

/**
 * 数组实现循环队列
 */
 class MySeqCycleQueue {

     private Object[] objects;  //存储队列的数组元素
     private int front; //队头下标
     private int rear; //队尾下标

    public MySeqCycleQueue(int size){
        objects=new Object[size];
        front=0;
        rear=0;
    }

    public MySeqCycleQueue(){
        this(16);
    }

    public boolean isEmpty(){
        //循环队列在中间也有可能front和rear相同,所以不能用front==rear==0.来判断
        return front==rear;
    }

    public boolean enqueue(Object o){
        if (o!=null) return false;
        if (front==(rear+1)%objects.length){
            //队满——注意队满的情况会预留一个空位,防止front==rear,这里采用了取模运算
            ensureCap(objects.length*2);
        }
        objects[rear]=o;
        rear=(rear+1)%objects.length; //rear下标变化规律,入队移动rear下标
        return true;
    }

    void ensureCap(int size){
             Object[] old=objects;
             objects=new Object[size];
            for(int i=0;i

3.6.3 链式循环队列

/**
 * 链表实现循环队列
 */
public class MyLinkedQueue {

    public MyLinkedQueue() {
        head=null;
        tail=null;
        n=0;
    }

    class Node{
        Node next;
        Object object;

        Node(Object o){
            this.object=o;
        }
        Node(){
        }
    }

    //头节点
    Node head;
    //尾节点
    Node tail;
    int n;

    public boolean isEmpty(){
        return head==null;
    }

    public int length(){
        return n;
    }

    /**
     * 入队
     */
    public void enqueue(Object o){
        if (o==null) return;
        Node old=tail;
        tail=new Node();
        tail.object=o;
        tail.next=null;
        if (isEmpty()) head=tail;
        else old.next=tail;
        n++;
    }

    /**
     * 出队
     */
    public Object dequeue(){
        if (isEmpty()) throw new NoSuchElementException("Queue underflow");
           Object o=head.object;
           head=head.next;
           n--;
           if (isEmpty())tail=null;
           return o;

    }

    public Object peek() {
        if (isEmpty()) throw new NoSuchElementException("Queue underflow");
        return head.object;
    }

}

3.6.4 循环队列时间复杂度

enqueue dequeue
O(1) O(1)
代码地址:https://github.com/ywqyunshan/LearnCode/tree/master/src/com/iigeo/datastrut

参考文章

1.数据结构与算法——常用数据结构及其Java实现.MageekChiu
2.Java数据结构与算法.zejian的博客
3.数据结构.人生设计师的博客

你可能感兴趣的:(Java数据结构和算法(一)——链表、栈、队列)