数据结构与算法分析(第二篇-常见ADT-抽象数据类型学习-手写List)

仅作为学习数据结构与算法分析边通过微博记录,蓝色为知识点。

上一篇我们已经了解了基本的算法,并能做出合理的耗时分析,本片主要讲解的点是集中抽象数据类型(ADT).

1.数组(这个太简单了直接跳过了)能手写一个ArrayList即可。

2.链表(手写一个LinkedList

3.栈(手写一个后缀运算)

4.队列(这个应用太多了讲下概念,之后着重讲图)

一。数组和链表(贴手写LinkedList代码,这个总体还是有点意思)

        数组这块我只讲一下ArrayList的实现理念,实际代码不贴了太长了。

        JDK的List是继承Collection<E>

public interface List<E> extends Collection<E> {
public interface Collection<E> extends Iterable<E> {

迭代器方法在Collection里面被定义

Iterator<E> iterator();

所以手写ArrayListDemo直接实现Interable即可

新增修改删除,还有iterator都需要手动实现。

这里我主要讲一下LikedList

LikedList是链表,且是双向链表,双向是因为链性结构头节点(header node)和尾节点(tail node)都是特殊的存在,因为删除时候需要找到指出最后节点的项把它变成null,删除第一个的时候需要删除第二个指出的上一个节点。

为了解决这个问题,LinkedList特殊声明了header node和tail node

这里画个图给大家示意一下:


上图为LinkedList的数据结构,按位置查找链表的速度低于数组即数组为O(N)链表为O(N^2),但是使用迭代器一次next的话速度同为O(N).按位置新增和删除链表的速度为O(N),而数组为O(N^2)。

这里我主要给大家讲解一个LinkedList新增和修改的逻辑,了解了新增,删除的逻辑自然也通了。

这里我直接就贴上我自己写的代码,内容都在备注里边,注:迭代器部分我给省略了

代码下载地址:

https://github.com/shenyang312/shenyang/blob/dev/shen/src/main/java/com/shen/my_j_u/MyLinkedList.java

package com.shen.my_j_u;

import java.util.Iterator;

public class MyLinkedList <AnyType> implements Iterable<AnyType>{
    //链表长度
    private int theSize;
    //链表操作计数器,当清空,新增,删除时自增
    private int modCount = 0 ;
    //特殊节点:头节点
    private Node<AnyType> beginMarker;
    //特殊节点:尾节点
    private Node<AnyType> endMarker;

    public MyLinkedList(){
        doClear();
    }

    @Override
    public Iterator<AnyType> iterator() {
        return null;
    }

    /**
     * 节点对象
     * @param <AnyType>
     */
    private static class Node<AnyType>{
        /**
         * 节点构造器
         * @param d 节点数据对象
         * @param p 当前节点前节点
         * @param n 当前节点后节点
         */
        public Node(AnyType d,Node<AnyType> p,Node<AnyType> n){
            data = d;
            prev = p;
            next = n;
        }
        public AnyType data;
        public Node<AnyType> prev;
        public Node<AnyType> next;
    }

    /**
     * 链表初始化方法
     */
    private void doClear(){
        beginMarker = new Node<>(null,null,null);
        endMarker = new Node<>(null,beginMarker,null);
        beginMarker.next = endMarker;

        theSize = 0;
        modCount++;
    }

    /**
     * 获取当前链表长度
     * @return
     */
    public int size(){
        return theSize;
    }

    //插入对象方法,默认位置插入链表尾部
    public boolean add(AnyType x){
        add(size(),x);
        return true;
    }

    //按位置插入对象方法
    public boolean add(int idx,AnyType x){
        addBefore(getNode(idx,0,size()),x);
        return true;
    }

    //按位置修改方法
    public AnyType set(int idx,AnyType newVal){
        Node<AnyType> p = getNode(idx);
        AnyType oldVal = p.data;
        p.data = newVal;
        return oldVal;
    }

    /**
     * 按位置取出对象
     * @param idx 取出对象位置
     * @return
     */
    private Node<AnyType> getNode(int idx){
        return getNode(idx,0,size()-1);
    }

    /**
     * 获取插入位置之前的对象
     * @param idx 插入位置
     * @param lower 0
     * @param upper 现有长度
     * @return 插入位置现有元素
     */
    private Node<AnyType> getNode(int idx,int lower,int upper){
        Node<AnyType> p;
        //如果插入位置小于0,或者大于链表现有长度,抛出异常
        if(idx<lower || idx >upper)
            throw new IndexOutOfBoundsException();
        //如果插入位置在链表的前半部分
        if(idx<size()/2){
            //先赋值头节点,因为当插入位置为第一位时直接弹出
            p = beginMarker.next;
            //从头节点开始寻找插入位置
            for (int i = 0;i<idx;i++)
                //取出循环位置的下一个,即为需要插入的位置
                p = p.next;
        }else {
            //当插入位置在链表后半部
            //先赋值尾节点,因为当插入位置为最后一位位时直接弹出
            p =endMarker;
            //从尾位置查询
            for (int i= size();i>idx;i--)
                //取出循环位置的上一个,即为需要插入的位置
                p = p.prev;
        }
        return p;
    }

    /**
     * 新增节点方法
     * @param p 插入位置现有对象数据
     * @param x 新增对象数据
     */
    private void addBefore(Node<AnyType> p,AnyType x){
        //创建新增对象节点,p.prev为之前位置节点的母节点,子节点为插入前前当前位置节点
        Node<AnyType> newNode = new Node<>(x,p.prev,p);
        //修改父节点子节点,新创建对象节点复制给,之前父节点的自阶段
        newNode.prev.next = newNode;
        //修改位置节点的父节点
        p.prev = newNode;
        //长度加1
        theSize++;
        //操作计数加1
        modCount++;
    }

}



第二-栈

        说到栈大家需要记忆的点:1.push进栈

                                                 2.pop出栈

                                                 3.top顶部

            栈也叫LIFO表,就是先进先出,这里记忆的时候注意他和队列相反即可。

        懒了这里就不贴图了。

        讲一下栈的应用:栈的应用就比较多了,比如说平衡符号(类似编译器符号检错),后缀计算法(这块树的部分还会讲)

着重讲一下--方法调用

        当调用一个新的方法时,主调例程的所有局部变量需要由系统储存起来,否则呗调用的新方法将会重新由主调例程的变量所使用的内存。主调例程的当前位子也需要储存。这时候由一个新的栈来完成,而这正式在实现递归的每一种设计语句中实际发生的事实。这些所储存的信息或曾为活动记录,或叫栈帧

猜你喜欢

转载自blog.csdn.net/weixin_37352094/article/details/80626282