基础数据结构,动手写一遍

2018-10-25  本文已影响0人  啦啦哇哈哈

动态Array

从最基本的动态Array开始实现(实际上可以理解成自己动手实现了个ArrayList)总共实现下面这些方法:



底层还是使用数组,不过为了实现动态缩容扩容,我们包装了一些方法在里面:

    public class Array<E>{
        //底层还是用数组实现
        private E[] data;
        //size记录data中真实存在的元素个数
        private int size;
         ...
    }
    public Array() {
        //默认实现给10个空间
        this(10);
    }
    
    public Array(E[] data) {//这块是拷贝,不是让底层的data直接指向传入的参数
        //这是Java不允许泛型数组,所以要Object数组强转
        this.data = (E[])new Object[data.length];
        for(int i = 0; i < data.length; i++) {
            this.data[i] = data[i];
        }
        size = data.length;
    }
    
    public Array(int Capacity) {
        //这是Java不允许泛型数组,所以要Object数组强转
        data = (E[]) new Object[Capacity];
        size = 0;
    }
    private void checkIndexRange(int index) {
        if (index < 0 || index >= data.length) {
            throw new IllegalArgumentException();
        }
    }
    private void resize(int newCapacity) {
        E[] newArray = (E[]) new Object[newCapacity];
        
        for(int i = 0; i < size; i++) {
            newArray[i] = data[i];
        }
        
        data = newArray;
    }
    public int getSize() {
        return size;
    }
    
    public int getCapacity() {
        return data.length;
    }
    
    public boolean isEmpty() {
        return size == 0;
    }
    
    public E get(int index) {
        checkIndexRange(index);
        return data[index];
    }
    
    public E getFirst() {
        return get(0);
    }
    
    public E getLast() {
        return get(size - 1);
    }
    
    public boolean contains(E element) {
        for(int i = 0; i < size; i++) {
            if (Objects.equals(data[i], element)) {
                return true;
            }
        }
        
        return false;
    }

    public int find(E element) {
        for(int i = 0; i < size; i++) {
            if (Objects.equals(data[i], element)) {
                return i;
            }
        }
        
        return -1;
    }

    public void set(int index, E element) {
        checkIndexRange(index);
        
        data[index] = element;
    }
    public void add(int index, E element) {
        checkIndexRange(index);
        
        if (size == data.length) {
            resize(2 * size);
        }
        
        for(int i = size; i > index; i--) {
            data[i] = data[i-1];
        }
        
        data[index] = element;
        
        size ++;
    }

    public void addFirst(E element) {
        add(0, element);
    }
    
    public void addLast(E element) {
        add(size, element);
    }
    public E remove(int index) {
        checkIndexRange(index);
        //先保存下来要删除(返回)的值
        E ret = data[index];
        
        //然后从index开始把后一个元素赋值给前一个
        for(int i = index; i < size - 1; i++) {
            data[i] = data[i+1];
        }
        
        //最后一个元素赋为Null
        data[size - 1] = null;
        //别忘了维护size
        size --;
        
        //size减小之后如果到了四分之一长度,那就缩容为原来容量的一半,但是同时要注意 data.length/2不是0
        if (size == data.length/4 && data.length / 2 != 0) {
            resize(data.length / 2);
        }
        
        return ret;
    }
    
    public E removeFirst() {
        return remove(0);
    }
    
    public E removeLast() {
        return remove(size);
    }
    
    public E removeElement(E element) {
        int index = find(element);
        
        return remove(index);
    }

这样就基本完成了一个伪的ArrayList,集合框架的源码肯定写的更强一些,也考虑的更周到一些,后面在总结集合源码时候会进行分析。

PriorityQueue

首先基于上面实现的Array为底层,实现一个heap。
需要有下面的方法:


    private Array<E> data;
    public MaxHeap() {
        this.data = new Array<>();
    }
    
    public MaxHeap(int Capacity) {
        this.data = new Array<>(Capacity);
    }
    
    public boolean isEmpty() {
        return data.isEmpty();
    }
    public int size() {
        return data.getSize();
    }
    private int parent(int index) {
        return (index-1)/2;
    }

    private int leftChild(int index) {
        return 2 * index + 1;
    }
    private int rightChild(int index) {
        return 2 * index + 2;
    }
    
    private void shiftDown(int index) {
        while(leftChild(index)< size()) {
            int j = leftChild(index);
            //第一步找到左右孩子中更大的那一个
            if(j + 1 < size() && data.get(j+1).compareTo(data.get(j))>0) {
                j = rightChild(index);
            }
            //j现在代表的不管是左还是右,反正都是最大的
            //如果当前节点比左右孩子更大的那个还要大,那就说明不再需要shiftDown了,不用再交换
            if (data.get(index).compareTo(data.get(j))>0) {
                break;
            }
            
            //如果上面的循环没有跳出去,那就说明肯定是孩子比他大的,不管左还是右,只管jjjjjj,交换一下即可
            data.swap(index, j);
            
            //再把指针挪到j上面
            index = j;
        }
    }
    
    private void shiftUp(int index) {
        while(parent(index) >= 0 && data.get(index).compareTo(data.get(parent(index)))>0) {
            data.swap(index, parent(index));
            index = parent(index);
        }
    }

写的代码上来看,shiftUp会更加简单一些,毕竟只需要比较一个节点,shiftDown写的时候,就是找到儿子中最大的那一个,并记录下标,然后把当前节点和最大的那个比一下,这是比较优秀的写法。

    /* 
     * 添加过程是先在数组末尾添加新元素,
     * 然后为了保持堆的特性,要对最后添加的新元素进 
     * 行shiftUp操作
     */ 
    public void add(E e) {
        data.addLast(e);
        shiftUp(size() - 1);
    }
   /*
    * 拿出最大元素之后依然要保持堆的特性,
    *所以这里的方法是,先把最后一个节点值交换到第0位 
    *最大的位置,然后把最后一个位置删除掉,然后再对 
    *最大节点进行下沉操作维护堆的特性。
    */  
    public E extractMax() {
        E max = findMax();
        data.swap(0, size()-1);
        data.removeLast();
        
        shiftDown(0);
        return max;
    }
    
    public E findMax() {
        return data.getFirst();
    }
    //这个构造函数对应的过程叫Heapify
    public MaxHeap(E[] array) {
        //先把数组弄进来
        data = new Array<>(array);
        //从第一个非叶子节点开始 shiftDown
        for(int i = parent(size() - 1); i >= 0; i--) {
            shiftDown(i);
        }
    }

这里基本上这个堆的结构是实现了,我们在它之上包装上PriorityQueue的接口就好了。

public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
    private MaxHeap<E> maxHeap;
    public PriorityQueue() {
        maxHeap = new MaxHeap<>();
    }
    @Override
    public int getSize() {
        return maxHeap.size();
    }

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

    @Override
    public void enqueue(E e) {
        maxHeap.add(e);
    }

    @Override
    public E dequeue() {
        return maxHeap.extractMax();
    }

    @Override
    public E getFront() {
        return maxHeap.findMax();
    }

    
}

Java的util包下面的PriorityQueue是个最小堆,不过可以通过构造器传入自定义的Comparator来决定这个PriorityQueue的判定标准。

BST

二分搜索树是在是再常见不过了。
首先需要定义一个节点类,我们这里放成内部类。

public class BinarySearchTree<E> {
    class Node{
        public Node left,right;
        public E e;
        
        public Node(E e){
            this.e = e;
            left = null;
            right = null;
        }
    }
          .....
    
}
    private Node treeRoot;
    private int size;
    public BinarySearchTree() {
        treeRoot = null;
        size = 0;
    }
    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }
    public void add(E e) {
        treeRoot = add(treeRoot, e);
    }
    
    private Node add(Node root, E e) {
        if (root == null) {
            size++;
            return new Node(e);
        }
        
        if (root.e.compareTo(e) == 0) {
            return root;
        }
        
        if (e.compareTo(root.e)>0) {
            root.right = add(root.right, e);
        }else {
            root.left = add(root.left, e);
        }
        
        return root;
    }
    public boolean contains(E e) {
        return contains(treeRoot,e);
    }
    
    private boolean contains(Node root, E e) {
        if (root == null) {
            return false;
        }
        
        if (root.e.compareTo(e)==0) {
            return true;
        }
        
        if (e.compareTo(root.e) > 0) {
            return contains(root.right,e);
        } else {
            return contains(root.left, e);
        }
    }   
    public void preOrder() {
        System.out.println("preOrder:");
        preOrder(treeRoot);
    }
    
    private void preOrder(Node root) {
        if (root == null) {
            return ;
        }
        
        System.out.println(root.e);
        preOrder(root.left);
        preOrder(root.right);
    }

    public void inOrder() {
        System.out.println("inOrder:");
        inOrder(treeRoot);
    }
    
    private void inOrder(Node root) {
        if (root == null) {
            return ;
        }
        
        inOrder(root.left);
        System.out.println(root.e);
        inOrder(root.right);    
    }
    
    public void postOrder() {
        System.out.println("postOrder:");
        postOrder(treeRoot);
    }
    
    private void postOrder(Node root) {
        if (root == null) {
            return ;
        }
        
        postOrder(root.left);
        postOrder(root.right);
        System.out.println(root.e);
    }

下面是先序的非递归实现,我们是用一个Stack去模拟系统的栈,从树根入栈开始,取栈顶先加右再加左入栈,直到栈取空为止。

    private void preOrderNotRecursively(Node root) {
        if (root == null) {
            return ;
        }
        
        Stack<Node> stack = new Stack<>();
        
        stack.push(root);
        
        while(!stack.isEmpty()) {
            Node node = stack.pop();
            
            System.out.println(node.e);
            
            if (node.right != null) {
                stack.push(node.right);
            }
            
            if (node.left != null) {
                stack.push(node.left);
            }
        }
    }

然后再来层序遍历,这种依赖于队列来实现。从根节点入队开始,取出队首节点访问,先入左再入右。直到队空为止。

    public void levelOrder() {
        System.out.println("leverOrder:");
        levelOrder(treeRoot);
    }
    
    private void levelOrder(Node root) {
        if (root == null) {
            return ;
        }
        
        Queue<Node> queue = new LinkedList<>();
        
        queue.offer(root);
        
        while(!queue.isEmpty()) {
            Node node = queue.poll();
            
            System.out.println(node.e);
            
            if (node.left != null) {
                queue.offer(node.left);
            }
            
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
    }

接下来的一系列方法,都是为remove服务的,我们一步步来,bst中的remove方法确实要复杂于那些add操作。

    public E getMax() {
        if (isEmpty()) {
            throw new NullPointerException();
        }
        return getMax(treeRoot).e;
    }
    private Node getMax(Node root) {
        if (root.right == null) {
            return root;
        }
        
        return getMax(root.right);
    }
    
    private Node getMaxNotRecursively(Node root) {
        if (root == null) {
            return null;
        }
        
        while(root.right != null) {
            root = root.right;
        }
        
        return root;
    }
    
    public E getMin() {
        if (isEmpty()) {
            throw new NullPointerException();
        }
        return getMin(treeRoot).e;
    }
    
    private Node getMin(Node root) {
        if(root.left == null) {
            return root;
        }
        
        return getMin(root.left);
    }
    
    private Node getMinNotRecursively(Node root) {
        if (root == null) {
            return null;
        }
        
        while(root.left != null) {
            root = root.left;
        }
        
        return root;
    }
    
    public E removeMax() {
        E ret = getMax();
        treeRoot = removeMax(treeRoot);
        return ret;
    }
    
    /*
     * 删除以root为根的子树的最大节点
     * 返回删除最大节点后的子树的根
     * 删除的过程是用该节点的左孩子直接代替它
     */
    private Node removeMax(Node root) {
        if (root.right == null) {
            Node leftNode = root.left;
            root.left = null;
            return leftNode;
        }
        
        root.right = removeMax(root.right);
        return root;
    }
    public E removeMin() {
        E ret = getMin();
        treeRoot = removeMin(treeRoot);
        return ret;
    }
    
    /*
     * 删除以root为根的子树中的最小节点
     * 返回删除最小节点后的子树的根
     * 删除的过程是一直往左走,找到最左节点,用该节点的右孩子代替它
     */
    private Node removeMin(Node root) {
        if (root.left == null) {
            Node rightNode = root.right;
            root.right = null;
            return rightNode;
        }
        
        root.left = removeMin(root.left);
        return root;
    }

    /*
     * 删除以root为根节点的子树中值为e的节点
     * 返回删除目的节点后的子树的根
     * 删除的过程就是根据e的值和当前节点的值的比较结果
     * 向左或者向右去走,直到找到目的节点
     * 这时候有三种情况。
     * 1.左子树为空,那就直接用它的右孩子去代替这个节点
     * 2.右子树为空,那就直接用它的左孩子去代替这个节点
     * 3.左右子树都不为空,我们找它的后继节点successor,
     * 也就是右子树中的最小节点,用这个节点来代替它(或者找
     * 前驱节点,左子树中的最大节点,用这个节点来代替它),还要
     * 记的把后继(前驱)节点删除掉。
     */
    private Node removeNode(Node root, E e) {
        
        if (root == null) {
            return null;
        }
        
        if (e.compareTo(root.e)>0) {
            root.right = removeNode(root.right,e);
            return root;
        }
        
        if (e.compareTo(root.e)<0) {
            root.left = removeNode(root.left, e);
            return root;
        }
        
        if (root.left == null) {
            Node rightNode = root.right;
            root.right = null;
            size--;
            return rightNode;
        }
        
        if (root.right == null) {
            Node leftNode = root.left;
            root.left = null;
            size--;
            return leftNode;
        }
        
        //左右子树都不为空,找到后继节点——右子树中的最小值
        Node successor = getMin(root.right);
        
        successor.right = removeMin(root.right);
        successor.left = root.left;
        
        root.left = null;
        root.right = null;
        
        
        return successor;
    }
上一篇下一篇

猜你喜欢

热点阅读