漫画算法笔记(java实现)

代码git地址

算法概述

  • 算法(Algorithm)
    一系列程序指令,用于处理特定的运算和逻辑问题。
    衡量算法优劣的标准:
    • 时间复杂度
    • 空间复杂度
  • 数据结构(Data structure)
    数据的组织管理和储存格式,使用目的是为了高效的访问和修改数据。
    不同的算法会选用不同的数据结构。
    • 线性结构(数组,链表,栈,队列,哈希表)
    • 树(二叉树,二叉堆)
    • 其他数据结构
  • 时间复杂度
    T(n)=O(f(n))
    O(1)
  • 空间复杂度
    S(n)=O(f(n))
    • 四种情况
      1. 常量空间O(1)
      2. 线性空间O(n)
      3. 二维空间O(n^2)
      4. 递归空间(方法调用栈)O(n)

数据结构基础

数据结构的操作无非增删改查

  • 数组(array)
    有限个相同类型变量组成的有序集合。
    物理储存方式是顺序存储,访问是随机访问(根据下标的方式叫做)。
    下标查找O(1),中间插入和删除O(n)。
    适合读操作多,写操作少的场景
package chapter2.part1;

/**
 * Created by weimengshu on 2018/8/24.
 */
public class MyArray {

    private int[] array;
    private int size;

    public MyArray(int capacity){
        this.array = new int[capacity];
        size = 0;
    }

    /**
     * 数组插入元素
     * @param index  插入的位置
     * @param element  插入的元素
     */
    public void insert(int index, int element) throws Exception {
        //判断访问下标是否超出范围
        if(index<0 || index>size){
            throw new IndexOutOfBoundsException("超出数组实际元素范围!");
        }
        //如果实际元素达到数组容量上线,数组扩容
        if(size >= array.length){
            resize();
        }
        //从右向左循环,逐个元素向右挪一位。
        for(int i=size-1; i>=index; i--){
            array[i+1] = array[i];
        }
        //腾出的位置放入新元素
        array[index] = element;
        size++;
    }

    /**
     * 数组扩容
     */
    public void resize(){
        int[] arrayNew = new int[array.length*2];
        //从旧数组拷贝到新数组
        System.arraycopy(array, 0, arrayNew, 0, array.length);
        array = arrayNew;
    }

    /**
     * 数组删除元素
     * @param index  删除的位置
     */
    public int delete(int index) throws Exception {
        //判断访问下标是否超出范围
        if(index<0 || index>=size){
            throw new IndexOutOfBoundsException("超出数组实际元素范围!");
        }
        int deletedElement = array[index];
        //从左向右循环,逐个元素向左挪一位。
        for(int i=index; i<size-1; i++){
            array[i] = array[i+1];
        }
        size--;
        return deletedElement;
    }

    /**
     * 输出数组
     */
    public void output(){
        for(int i=0; i<size; i++){
            System.out.println(array[i]);
        }
    }

    public static void main(String[] args) throws Exception {
        MyArray myArray = new MyArray(4);
        myArray.insert(0,3);
        myArray.insert(1,7);
        myArray.insert(2,9);
        myArray.insert(3,5);
        myArray.insert(1,6);
        myArray.insert(5,8);
        myArray.delete(3);
        myArray.output();
    }
}
  • 链表(linked list)
    链式数据结构,由若干节点构成,每个节点包含下一节点的指针。
    头节点(Head)——尾部(NULL)
    物理储存方式是随机存储,访问时顺序访问。
    查找节点O(n),中间插入和删除O(1)。
    适合写操作多,读操作少的场景
    • 单向链表:节点(node)——数据(data),下一个节点的指针(next)
    • 双向链表:节点(node)——数据(data),前后节点的指针(prev,next)
  • 逻辑结构
    • 线性结构:顺序表,栈,队列
    • 非线性结构:树,图
  • 物理结构
    • 顺序存储结构:数组
    • 链式存储结构:链表
package chapter2.part2;

/**
 * Created by weimengshu on 2018/8/24.
 */
public class MyLinkedList {

    //头节点指针
    private Node head;
    //尾节点指针
    private Node last;
    //链表实际长度
    private int size;

    /**
     * 链表插入元素
     * @param index  插入位置
     * @param data  插入元素
     */
    public void insert(int index, int data) throws Exception {
        if (index<0 || index>size) {
            throw new IndexOutOfBoundsException("超出链表节点范围!");
        }
        Node insertedNode = new Node(data);
        if(size == 0){
            //空链表
            head = insertedNode;
            last = insertedNode;
        } else if(index == 0){
            //插入头部
            insertedNode.next = head;
            head = insertedNode;
        }else if(size == index){
            //插入尾部
            last.next = insertedNode;
            last = insertedNode;
        }else {
            //插入中间
            Node prevNode = get(index-1);
            insertedNode.next = prevNode.next;
            prevNode.next = insertedNode;
        }
        size++;
    }

    /**
     * 链表删除元素
     * @param index  删除的位置
     */
    public Node remove(int index) throws Exception {
        if (index<0 || index>=size) {
            throw new IndexOutOfBoundsException("超出链表节点范围!");
        }
        Node removedNode = null;
        if(index == 0){
            //删除头节点
            removedNode = head;
            head = head.next;
        }else if(index == size-1){
            //删除尾节点
            Node prevNode = get(index-1);
            removedNode = prevNode.next;
            prevNode.next = null;
            last = prevNode;
        }else {
            //删除中间节点
            Node prevNode = get(index-1);
            Node nextNode = prevNode.next.next;
            removedNode = prevNode.next;
            prevNode.next = nextNode;
        }
        size--;
        return removedNode;
    }

    /**
     * 链表查找元素
     * @param index  查找的位置
     */
    public Node get(int index) throws Exception {
        if (index<0 || index>=size) {
            throw new IndexOutOfBoundsException("超出链表节点范围!");
        }
        Node temp = head;
        for(int i=0; i<index; i++){
            temp = temp.next;
        }
        return temp;
    }

    /**
     * 输出链表
     */
    public void output(){
        Node temp = head;
        while (temp!=null) {
            System.out.println(temp.data);
            temp = temp.next;
        }
    }

    /**
     * 链表节点
     */
    private static class Node {
        int data;
        Node next;

        Node(int data) {
            this.data = data;
        }
    }

    public static void main(String[] args) throws Exception {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.insert(0,3);
        myLinkedList.insert(0,4);
        myLinkedList.insert(2,9);
        myLinkedList.insert(3,5);
        myLinkedList.insert(1,6);
        myLinkedList.remove(0);
        myLinkedList.output();
    }
}
  • 栈(stack)
    线性逻辑结构,可以用数组和链表实现。
    包含入栈和出栈操作,遵循先入后出(FILO)。
    • 栈底(bottom),栈顶(top)
    • 进栈(push),出栈(pop)
  • 队列(queue)
    线性逻辑结构,可以用数组和链表实现。
    包含入队和出队操作,遵循先入先出(FIFO)。
    • 队头(front),队尾(rear)
    • 入队(enqueue),出队(dequeue)
    • 双端队列,优先队列等。
      循环队列:队尾指针指向的位置永远空出一位

栈的应用:历史回溯(递归的逻辑的代替,面包屑导航)
队列的应用:历史重现(多线程公平锁,网络爬虫)

package chapter2.part3;

/**
 * Created by weimengshu on 2018/8/24.
 */
public class MyQueue {

    private int[] array;
    private int front;
    private int rear;

    public MyQueue(int capacity){
        this.array = new int[capacity];
    }

    /**
     * 入队
     * @param element  入队的元素
     */
    public void enQueue(int element) throws Exception {
        if((rear+1)%array.length == front){
            throw new Exception("队列已满!");
        }
        array[rear] = element;
        rear =(rear+1)%array.length;
    }

    /**
     * 出队
     */
    public int deQueue() throws Exception {
        if(rear == front){
            throw new Exception("队列已空!");
        }
        int deQueueElement = array[front];
        front =(front+1)%array.length;
        return deQueueElement;
    }

    /**
     * 输出队列
     */
    public void output(){
        for(int i=front; i!=rear; i=(i+1)%array.length){
            System.out.println(array[i]);
        }
    }

    public static void main(String[] args) throws Exception {
        MyQueue myQueue = new MyQueue(6);
        myQueue.enQueue(3);
        myQueue.enQueue(5);
        myQueue.enQueue(6);
        myQueue.enQueue(8);
        myQueue.enQueue(1);
        myQueue.deQueue();
        myQueue.deQueue();
        myQueue.deQueue();
        myQueue.enQueue(2);
        myQueue.enQueue(4);
        myQueue.enQueue(9);
        myQueue.output();
    }
}
  • 散列表(Hash table)
    也叫哈希表(hash table),是存储key-value映射的集合。
    对于某一个key,可以在接近O(1)的时间内读写。
    散列表通过哈希函数实现KEY和数组下标的转换,通过开放寻址法和链表法来解决哈希冲突。
    • 写(put)
    • 读(get)
    • 扩容(resize)

通过哈希函数来实现:
index =HashCode(Key)%Array.length

  • 树(tree)
    • 根结点(root)
    • 叶子结点(leaf)
    • 父结点(parent),孩子结点(child),兄弟结点(sibling)
  • 二叉树(binary tree)
    • 左孩子(left child),右孩子(right child)
    • 满二叉树,完全二叉树(倒数第二层是满二叉树)
  • 二叉树物理存储结构
    • 链式存储
    • 数组存储
      • left child =2*parent+1
      • right child = 2*parent+2
  • 二叉树应用
    • 二叉查找树(binary search tree)查找
      左子树的结点均小于根结点的值,右子树的结点大于根结点的值,子树也都是二叉查找树。
      • 搜索结点的时间复杂度是O(logn),与树的深度相同
      • 又称二叉排序树(binary sort tree)维持相对顺序
        • 自平衡问题
  • 二叉树的遍历
    • 深度优先遍历
      • 前序遍历,中序遍历,后序遍历
      • 除了采用递归,还可以用栈(具有回溯特性)
    • 广度优先遍历
      • 层序遍历(队列)
//BinaryTreeTraversal.java
package chapter3.part2;

import java.util.Arrays;
import java.util.LinkedList;

/**
 * Created by weimengshu on 2018/9/22.
 */
public class BinaryTreeTraversal {

    /**
     * 构建二叉树
     * @param inputList   输入序列
     */
    public static TreeNode createBinaryTree(LinkedList<Integer> inputList){
        TreeNode node = null;
        if(inputList==null || inputList.isEmpty()){
            return null;
        }
        Integer data = inputList.removeFirst();
        //这里的判空很关键。如果元素是空,说明该节点不存在,跳出这一层递归;如果元素非空,继续递归构建该节点的左右孩子。
        if(data != null){
            node = new TreeNode(data);
            node.leftChild = createBinaryTree(inputList);
            node.rightChild = createBinaryTree(inputList);
        }
        return node;
    }

    /**
     * 二叉树前序遍历
     * @param node   二叉树节点
     */
    public static void preOrderTraversal(TreeNode node){
        if(node == null){
            return;
        }
        System.out.println(node.data);
        preOrderTraversal(node.leftChild);
        preOrderTraversal(node.rightChild);
    }

    /**
     * 二叉树中序遍历
     * @param node   二叉树节点
     */
    public static void inOrderTraversal(TreeNode node){
        if(node == null){
            return;
        }
        inOrderTraversal(node.leftChild);
        System.out.println(node.data);
        inOrderTraversal(node.rightChild);
    }


    /**
     * 二叉树后序遍历
     * @param node   二叉树节点
     */
    public static void postOrderTraversal(TreeNode node){
        if(node == null){
            return;
        }
        postOrderTraversal(node.leftChild);
        postOrderTraversal(node.rightChild);
        System.out.println(node.data);
    }

    /**
     * 二叉树节点
     */
    private static class TreeNode {
        int data;
        TreeNode leftChild;
        TreeNode rightChild;

        TreeNode(int data) {
            this.data = data;
        }
    }

    public static void main(String[] args) {
        LinkedList<Integer> inputList = new LinkedList<Integer>(Arrays.asList(new Integer[]{3,2,9,null,null,10,null,null,8,null,4,}));
        TreeNode treeNode = createBinaryTree(inputList);
        System.out.println("前序遍历:");
        preOrderTraversal(treeNode);
        System.out.println("中序遍历:");
        inOrderTraversal(treeNode);
        System.out.println("后序遍历:");
        postOrderTraversal(treeNode);
    }

}
//BinaryTreeTraversalStack.java
package chapter3.part2;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Stack;

/**
 * Created by weimengshu on 2018/9/22.
 */
public class BinaryTreeTraversalStack {

    /**
     * 构建二叉树
     * @param inputList   输入序列
     */
    public static TreeNode createBinaryTree(LinkedList<Integer> inputList){
        TreeNode node = null;
        if(inputList==null || inputList.isEmpty()){
            return null;
        }
        Integer data = inputList.removeFirst();
        //这里的判空很关键。如果元素是空,说明该节点不存在,跳出这一层递归;如果元素非空,继续递归构建该节点的左右孩子。
        if(data != null){
            node = new TreeNode(data);
            node.leftChild = createBinaryTree(inputList);
            node.rightChild = createBinaryTree(inputList);
        }
        return node;
    }

    /**
     * 二叉树非递归前序遍历
     * @param root   二叉树根节点
     */
    public static void preOrderTraveralWithStack(TreeNode root){
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode treeNode = root;
        while(treeNode!=null || !stack.isEmpty()){
            //迭代访问节点的左孩子,并入栈
            while (treeNode != null){
                System.out.println(treeNode.data);
                stack.push(treeNode);
                treeNode = treeNode.leftChild;
            }
            //如果节点没有左孩子,则弹出栈顶节点,访问节点右孩子
            if(!stack.isEmpty()){
                treeNode = stack.pop();
                treeNode = treeNode.rightChild;
            }
        }
    }

    /**
     * 二叉树节点
     */
    private static class TreeNode {
        int data;
        TreeNode leftChild;
        TreeNode rightChild;

        TreeNode(int data) {
            this.data = data;
        }
    }

    public static void main(String[] args) {
        LinkedList<Integer> inputList = new LinkedList<Integer>(Arrays.asList(new Integer[]{3,2,9,null,null,10,null,null,8,null,4,}));
        TreeNode treeNode = createBinaryTree(inputList);
        preOrderTraveralWithStack(treeNode);
    }

}
//BinaryTreeTraversalLevel.java
package chapter3.part2;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

/**
 * Created by weimengshu on 2018/9/22.
 */
public class BinaryTreeTraversalLevel {

    /**
     * 构建二叉树
     * @param inputList   输入序列
     */
    public static TreeNode createBinaryTree(LinkedList<Integer> inputList){
        TreeNode node = null;
        if(inputList==null || inputList.isEmpty()){
            return null;
        }
        Integer data = inputList.removeFirst();
        //这里的判空很关键。如果元素是空,说明该节点不存在,跳出这一层递归;如果元素非空,继续递归构建该节点的左右孩子。
        if(data != null){
            node = new TreeNode(data);
            node.leftChild = createBinaryTree(inputList);
            node.rightChild = createBinaryTree(inputList);
        }
        return node;
    }

    /**
     * 二叉树层序遍历
     * @param root   二叉树根节点
     */
    public static void levelOrderTraversal(TreeNode root){
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            System.out.println(node.data);
            if(node.leftChild != null){
                queue.offer(node.leftChild);
            }
            if(node.rightChild != null){
                queue.offer(node.rightChild);
            }
        }
    }

    /**
     * 二叉树节点
     */
    private static class TreeNode {
        int data;
        TreeNode leftChild;
        TreeNode rightChild;

        TreeNode(int data) {
            this.data = data;
        }
    }

    public static void main(String[] args) {
        LinkedList<Integer> inputList = new LinkedList<Integer>(Arrays.asList(new Integer[]{3,2,9,null,null,10,null,null,8,null,4,}));
        TreeNode treeNode = createBinaryTree(inputList);
        System.out.println("层序遍历:");
        levelOrderTraversal(treeNode);
    }

}
  • 二叉堆
    本质上是完全二叉树,任何一个父结点都大于左右孩子的值。根节点叫做堆顶。
    • 最大堆(父结点大),最小堆(父结点小)
    • 操作
      • 插入(大于最小的,小于最大的),删除,构建二叉树
//HeapOperator.java
package chapter3.part3;

/**
 * Created by weimengshu on 2018/7/13.
 */
import java.util.Arrays;

public class HeapOperator {

    /**
     * 上浮调整
     * @param array     待调整的堆
     */
    public static void upAdjust(int[] array) {
        int childIndex = array.length-1;
        int parentIndex = (childIndex-1)/2;
        // temp保存插入的叶子节点值,用于最后的赋值
        int temp = array[childIndex];
        while (childIndex > 0 && temp < array[parentIndex])
        {
            //无需真正交换,单向赋值即可
            array[childIndex] = array[parentIndex];
            childIndex = parentIndex;
            parentIndex = (parentIndex-1) / 2;
        }
        array[childIndex] = temp;
    }

    /**
     * 下沉调整
     * @param array     待调整的堆
     * @param parentIndex    要下沉的父节点
     * @param length    堆的有效大小
     */
    public static void downAdjust(int[] array, int parentIndex, int length) {
        // temp保存父节点值,用于最后的赋值
        int temp = array[parentIndex];
        int childIndex = 2 * parentIndex + 1;
        while (childIndex < length) {
            // 如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
            if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {
                childIndex++;
            }
            // 如果父节点小于任何一个孩子的值,直接跳出
            if (temp <= array[childIndex])
                break;
            //无需真正交换,单向赋值即可
            array[parentIndex] = array[childIndex];
            parentIndex = childIndex;
            childIndex = 2 * childIndex + 1;
        }
        array[parentIndex] = temp;
    }

    /**
     * 构建堆
     * @param array     待调整的堆
     */
    public static void buildHeap(int[] array) {
        // 从最后一个非叶子节点开始,依次下沉调整
        for (int i = (array.length-2)/2; i >= 0; i--) {
            downAdjust(array, i, array.length);
        }
    }

    public static void main(String[] args) {
        int[] array = new int[] {1,3,2,6,5,7,8,9,10,0};
        upAdjust(array);
        System.out.println(Arrays.toString(array));

        array = new int[] {7,1,3,10,5,2,8,9,6};
        buildHeap(array);
        System.out.println(Arrays.toString(array));
    }
}
  • 优先队列
    无论入队顺序,都是当前最大(最小)的元素优先出队
    • 最大优先队列,最小优先队列
    • 通过二叉堆来实现
      每一次入队都时堆的插入,出队就是删除堆顶节点。
package chapter3.part4;

/**
 * Created by weimengshu on 2018/7/13.
 */
import java.util.Arrays;

public class PriorityQueue {

    private int[] array;
    private int size;

    public PriorityQueue(){
        //队列初始长度32
        array = new int[32];
    }

    /**
     * 入队
     * @param key  入队元素
     */
    public void enQueue(int key) {
        //队列长度超出范围,扩容
        if(size >= array.length){
            resize();
        }
        array[size++] = key;
        upAdjust();
    }

    /**
     * 出队
     */
    public int deQueue() throws Exception {
        if(size <= 0){
            throw new Exception("the queue is empty !");
        }
        //获取堆顶元素
        int head = array[0];
        //最后一个元素移动到堆顶
        array[0] = array[--size];
        downAdjust();
        return head;
    }

    /**
     * 上浮调整
     */
    private void upAdjust() {
        int childIndex = size-1;
        int parentIndex = (childIndex-1)/2;
        // temp保存插入的叶子节点值,用于最后的赋值
        int temp = array[childIndex];
        while (childIndex > 0 && temp > array[parentIndex])
        {
            //无需真正交换,单向赋值即可
            array[childIndex] = array[parentIndex];
            childIndex = parentIndex;
            parentIndex = parentIndex / 2;
        }
        array[childIndex] = temp;
    }

    /**
     * 下沉调整
     */
    private void downAdjust() {
        // temp保存父节点值,用于最后的赋值
        int parentIndex = 0;
        int temp = array[parentIndex];
        int childIndex = 1;
        while (childIndex < size) {
            // 如果有右孩子,且右孩子大于左孩子的值,则定位到右孩子
            if (childIndex + 1 < size && array[childIndex + 1] > array[childIndex]) {
                childIndex++;
            }
            // 如果父节点大于任何一个孩子的值,直接跳出
            if (temp >= array[childIndex])
                break;
            //无需真正交换,单向赋值即可
            array[parentIndex] = array[childIndex];
            parentIndex = childIndex;
            childIndex = 2 * childIndex + 1;
        }
        array[parentIndex] = temp;
    }

    /**
     * 队列扩容
     */
    private void resize() {
        //队列容量翻倍
        int newSize = this.size * 2;
        this.array = Arrays.copyOf(this.array, newSize);
    }

    public static void main(String[] args) throws Exception {
        PriorityQueue priorityQueue = new PriorityQueue();
        priorityQueue.enQueue(3);
        priorityQueue.enQueue(5);
        priorityQueue.enQueue(10);
        priorityQueue.enQueue(2);
        priorityQueue.enQueue(7);
        System.out.println("出队元素:" + priorityQueue.deQueue());
        System.out.println("出队元素:" + priorityQueue.deQueue());
    }
}

排序算法

  • 排序算法分为稳定排序和非稳定排序
  • 主流排序算法三大分类
    • 时间复杂度O(n^2)
      • 冒泡排序
      • 选择排序
      • 插入排序
      • 希尔排序
    • 时间复杂度O(nlog n)
      • 快速排序
      • 归并排序
      • 堆排序
    • 时间复杂度为线性
      • 计数排序
      • 桶排序
      • 基数排序
  • 冒泡排序(bubble sort)(稳定排序)
    时间复杂度O(log(n^2))
    • 冒泡排序的优化
      • isSorted( )
      • sortBorder
      • 鸡尾酒排序
//BubbleSort.java
package chapter4.part2;

import java.util.Arrays;

public class BubbleSort {

    public static void sort(int array[])
    {
        int tmp  = 0;
        //记录最后一次交换的位置
        int lastExchangeIndex = 0;
        //无序数列的边界,每次比较只需要比到这里为止
        int sortBorder = array.length - 1;
        for(int i = 0; i < array.length; i++)
        {
            //有序标记,每一轮的初始是true
            boolean isSorted = true;

            for(int j = 0; j < sortBorder; j++)
            {
                if(array[j] > array[j+1])
                {
                    tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                    //有元素交换,所以不是有序,标记变为false
                    isSorted = false;
                    //把无序数列的边界更新为最后一次交换元素的位置
                    lastExchangeIndex = j;
                }
            }
            sortBorder = lastExchangeIndex;
            if(isSorted){
                break;
            }
        }
    }

    public static void main(String[] args){
        int[] array = new int[]{3,4,2,1,5,6,7,8};
        sort(array);
        System.out.println(Arrays.toString(array));
    }
}
package chapter4.part2;

import java.util.Arrays;

public class CockTailSort {

    public static void sort(int array[])
    {
        int tmp  = 0;
        for(int i=0; i<array.length/2; i++)
        {
            //有序标记,每一轮的初始是true
            boolean isSorted = true;
            //奇数轮,从左向右比较和交换
            for(int j=i; j<array.length-i-1; j++)
            {
                if(array[j] > array[j+1])
                {
                    tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                    //有元素交换,所以不是有序,标记变为false
                    isSorted = false;
                }
            }
            if(isSorted){
                break;
            }

            //偶数轮之前,重新标记为true
            isSorted = true;
            //偶数轮,从右向左比较和交换
            for(int j=array.length-i-1; j>i; j--)
            {
                if(array[j] < array[j-1])
                {
                    tmp = array[j];
                    array[j] = array[j-1];
                    array[j-1] = tmp;
                    //有元素交换,所以不是有序,标记变为false
                    isSorted = false;
                }
            }
            if(isSorted){
                break;
            }
        }
    }

    public static void main(String[] args){
        int[] array = new int[]{2,3,4,5,6,7,8,1};
        sort(array);
        System.out.println(Arrays.toString(array));
    }
}
  • 快速排序(quick sort) (不稳定排序)
    时间复杂度O(nlog n)
    选择基准元素pivot,采取了分治(partition)思想
    • 双边循环法(left,right指针)
    • 单边循环法(mark指针)
    • 非递归的实现(采用栈)
    • 最坏时间复杂度为O(n^2)

package chapter4.part3;

/**
 * Created by weimengshu on 2018/7/13.
 */
import java.util.Arrays;

public class QuickSort {

    public static void quickSort(int[] arr, int startIndex, int endIndex) {
        // 递归结束条件:startIndex大等于endIndex的时候
        if (startIndex >= endIndex) {
            return;
        }
        // 得到基准元素位置
        int pivotIndex = partition(arr, startIndex, endIndex);
        // 根据基准元素,分成两部分递归排序
        quickSort(arr, startIndex, pivotIndex - 1);
        quickSort(arr, pivotIndex + 1, endIndex);
    }

    /**
     * 分治(双边循环法)
     * @param arr     待交换的数组
     * @param startIndex    起始下标
     * @param endIndex    结束下标
     */
    private static int partition(int[] arr, int startIndex, int endIndex) {
        // 取第一个位置的元素作为基准元素(也可以选择随机位置)
        int pivot = arr[startIndex];
        int left = startIndex;
        int right = endIndex;

        while( left != right) {
            //控制right指针比较并左移
            while(left<right && arr[right] > pivot){
                right--;
            }
            //控制left指针比较并右移
            while( left<right && arr[left] <= pivot) {
                left++;
            }
            //交换left和right指向的元素
            if(left<right) {
                int p = arr[left];
                arr[left] = arr[right];
                arr[right] = p;
            }
        }

        //pivot和指针重合点交换
        arr[startIndex] = arr[left];
        arr[left] = pivot;

        return left;
    }

    /**
     * 分治(单边循环法)
     * @param arr     待交换的数组
     * @param startIndex    起始下标
     * @param endIndex    结束下标
     */
    private static int partitionV2(int[] arr, int startIndex, int endIndex) {
        // 取第一个位置的元素作为基准元素(也可以选择随机位置)
        int pivot = arr[startIndex];
        int mark = startIndex;

        for(int i=startIndex+1; i<=endIndex; i++){
            if(arr[i]<pivot){
                mark ++;
                int p = arr[mark];
                arr[mark] = arr[i];
                arr[i] = p;
            }
        }

        arr[startIndex] = arr[mark];
        arr[mark] = pivot;
        return mark;
    }

    public static void main(String[] args) {
        int[] arr = new int[] {4,4,6,5,3,2,8,1};
        quickSort(arr, 0, arr.length-1);
        System.out.println(Arrays.toString(arr));
    }
}
package chapter4.part3;

/**
 * Created by weimengshu on 2018/7/13.
 */
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

public class QuickSortWithStack {

    public static void quickSort(int[] arr, int startIndex, int endIndex) {
        // 用一个集合栈来代替递归的函数栈
        Stack<Map<String, Integer>> quickSortStack = new Stack<Map<String, Integer>>();
        // 整个数列的起止下标,以哈希的形式入栈
        Map rootParam = new HashMap();
        rootParam.put("startIndex", startIndex);
        rootParam.put("endIndex", endIndex);
        quickSortStack.push(rootParam);

        // 循环结束条件:栈为空时结束
        while (!quickSortStack.isEmpty()) {
            // 栈顶元素出栈,得到起止下标
            Map<String, Integer> param = quickSortStack.pop();
            // 得到基准元素位置
            int pivotIndex = partition(arr, param.get("startIndex"), param.get("endIndex"));
            // 根据基准元素分成两部分, 把每一部分的起止下标入栈
            if(param.get("startIndex") <  pivotIndex -1){
                Map<String, Integer> leftParam = new HashMap<String, Integer>();
                leftParam.put("startIndex",  param.get("startIndex"));
                leftParam.put("endIndex", pivotIndex -1);
                quickSortStack.push(leftParam);
            }
            if(pivotIndex + 1 < param.get("endIndex")){
                Map<String, Integer> rightParam = new HashMap<String, Integer>();
                rightParam.put("startIndex", pivotIndex + 1);
                rightParam.put("endIndex", param.get("endIndex"));
                quickSortStack.push(rightParam);
            }
        }
    }

    /**
     * 分治(单边循环法)
     * @param arr     待交换的数组
     * @param startIndex    起始下标
     * @param endIndex    结束下标
     */
    private static int partition(int[] arr, int startIndex, int endIndex) {
        // 取第一个位置的元素作为基准元素(也可以选择随机位置)
        int pivot = arr[startIndex];
        int mark = startIndex;

        for(int i=startIndex+1; i<=endIndex; i++){
            if(arr[i]<pivot){
                mark ++;
                int p = arr[mark];
                arr[mark] = arr[i];
                arr[i] = p;
            }
        }

        arr[startIndex] = arr[mark];
        arr[mark] = pivot;
        return mark;
    }

    public static void main(String[] args) {
        int[] arr = new int[] {4,7,6,5,3,2,8,1};
        quickSort(arr, 0, arr.length-1);
        System.out.println(Arrays.toString(arr));
    }

}
  • 堆排序(Heap sort)(不稳定排序)
    时间复杂度O(nlog n)
    • 步骤
      1. 把无序数组构建成二叉堆(n)。
      2. 循环删除堆顶元素,替换到二叉堆的末尾,调整堆产生新的堆顶(log n)。
    • 最坏时间复杂度稳定为O(nlog n)

    • 堆排序的空间复杂度为O(1)

package chapter4.part4;

/**
 * Created by weimengshu on 2018/7/13.
 */
import java.util.Arrays;

public class HeapSort {

    /**
     * 下沉调整
     * @param array     待调整的堆
     * @param parentIndex    要下沉的父节点
     * @param length    堆的有效大小
     */
    public static void downAdjust(int[] array, int parentIndex, int length) {
        // temp保存父节点值,用于最后的赋值
        int temp = array[parentIndex];
        int childIndex = 2 * parentIndex + 1;
        while (childIndex < length) {
            // 如果有右孩子,且右孩子大于左孩子的值,则定位到右孩子
            if (childIndex + 1 < length && array[childIndex + 1] > array[childIndex]) {
                childIndex++;
            }
            // 如果父节点大于等于任何一个孩子的值,直接跳出
            if (temp >= array[childIndex])
                break;
            //无需真正交换,单向赋值即可
            array[parentIndex] = array[childIndex];
            parentIndex = childIndex;
            childIndex = 2 * childIndex + 1;
        }
        array[parentIndex] = temp;
    }

    /**
     * 堆排序(升序)
     * @param array     待调整的堆
     */
    public static void heapSort(int[] array) {
        // 1.把无序数组构建成最大堆。
        for (int i = (array.length-2)/2; i >= 0; i--) {
            downAdjust(array, i, array.length);
        }
        System.out.println(Arrays.toString(array));
        // 2.循环交换集合尾部元素到堆顶,并调节堆产生新的堆顶。
        for (int i = array.length - 1; i > 0; i--) {
            // 最后一个元素和第一元素进行交换
            int temp = array[i];
            array[i] = array[0];
            array[0] = temp;
            // 下沉调整最大堆
            downAdjust(array, 0, i);
        }
    }

    public static void main(String[] args) {
        int[] arr = new int[] {1,3,2,6,5,7,8,9,10,0};
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }
}
  • 计数排序(count sort)(稳定排序)
    时间复杂度O(m+n)(m为计数数组的大小)
    • 优化:(稳定排序)
      • 计数数组(count array)
      • 排序数组(sorted array)
    • 数列最大和最小值差距过大时,数列元素都不是整数时。不适合用计数排序

package chapter4.part5;

/**
 * Created by weimengshu on 2018/7/13.
 */
import java.util.Arrays;

public class CountSort {

    public static int[] countSort(int[] array) {
        //1.得到数列的最大值
        int max = array[0];
        for(int i=1; i<array.length; i++){
            if(array[i] > max){
                max = array[i];
            }
        }
        //2.根据数列最大值确定统计数组的长度
        int[] countArray = new int[max+1];
        //3.遍历数列,填充统计数组
        for(int i=0; i<array.length; i++){
            countArray[array[i]]++;
        }
        //4.遍历统计数组,输出结果
        int index = 0;
        int[] sortedArray = new int[array.length];
        for(int i=0; i<countArray.length; i++){
            for(int j=0; j<countArray[i]; j++){
                sortedArray[index++] = i;
            }
        }
        return sortedArray;
    }

    public static int[] countSortV2(int[] array) {
        //1.得到数列的最大值和最小值,并算出差值d
        int max = array[0];
        int min = array[0];
        for(int i=1; i<array.length; i++) {
            if(array[i] > max) {
                max = array[i];
            }
            if(array[i] < min) {
                min = array[i];
            }
        }
        int d = max - min;
        //2.创建统计数组并统计对应元素个数
        int[] countArray = new int[d+1];
        for(int i=0; i<array.length; i++) {
            countArray[array[i]-min]++;
        }
        //3.统计数组做变形,后面的元素等于前面的元素之和
        for(int i=1;i<countArray.length;i++) {
            countArray[i] += countArray[i-1];
        }
        //4.倒序遍历原始数列,从统计数组找到正确位置,输出到结果数组
        int[] sortedArray = new int[array.length];
        for(int i=array.length-1;i>=0;i--) {
            sortedArray[countArray[array[i]-min]-1]=array[i];
            countArray[array[i]-min]--;
        }
        return sortedArray;
    }

    public static void main(String[] args) {
        int[] array = new int[] {4,4,6,5,3,2,8,1,7,5,6,0,10};
        int[] sortedArray = countSort(array);
        System.out.println(Arrays.toString(sortedArray));

        array = new int[] {95,94,91,98,99,90,99,93,91,92};
        sortedArray = countSort(array);
        System.out.println(Arrays.toString(sortedArray));
    }
}
  • 桶排序(bucket sort)(稳定排序)
    时间复杂度O(n)
package chapter4.part5;

/**
 * Created by weimengshu on 2018/7/13.
 */
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;

public class BucketSort {

    public static double[] bucketSort(double[] array){

        //1.得到数列的最大值和最小值,并算出差值d
        double max = array[0];
        double min = array[0];
        for(int i=1; i<array.length; i++) {
            if(array[i] > max) {
                max = array[i];
            }
            if(array[i] < min) {
                min = array[i];
            }
        }
        double d = max - min;

        //2.初始化桶
        int bucketNum = array.length;
        ArrayList<LinkedList<Double>> bucketList = new ArrayList<LinkedList<Double>>(bucketNum);
        for(int i = 0; i < bucketNum; i++){
            bucketList.add(new LinkedList<Double>());
        }

        //3.遍历原始数组,将每个元素放入桶中
        for(int i = 0; i < array.length; i++){
            int num = (int)((array[i] - min)  * (bucketNum-1) / d);
            bucketList.get(num).add(array[i]);
        }

        //4.对每个桶内部进行排序
        for(int i = 0; i < bucketList.size(); i++){
            //JDK底层采用了归并排序或归并的优化版本
            Collections.sort(bucketList.get(i));
        }

        //5.输出全部元素
        double[] sortedArray = new double[array.length];
        int index = 0;
        for(LinkedList<Double> list : bucketList){
            for(double element : list){
                sortedArray[index] = element;
                index++;
            }
        }
        return sortedArray;
    }

    public static void main(String[] args) {
        double[] array = new double[] {4.12,6.421,0.0023,3.0,2.123,8.122,4.12, 10.09};
        double[] sortedArray = bucketSort(array);
        System.out.println(Arrays.toString(sortedArray));
    }
}

面试中的算法

以下代码自己去git查看

  • 判断链表有环
    两个指针,步长分别为1和2
    • 拓展1:求环的长度
    • 拓展2:求入环点
  • 最小栈的实现
    创建2个栈
  • 求最大公约数(gcd:getGreatCommonDivisor)
    • 暴力枚举法
      时间复杂度O(min(a,b))
    • 辗转相除法(欧几里得算法)
      时间复杂度低,但取模的性能比较差
    • 更相减损法
      两数相差过大,则递归次数过多,不稳定
    • 更相减损法结合移位运算
      时间复杂度O(log(max(a,b)))
  • 判断是否为2的整数幂
    • 移位运算
    • n&(n-1)
  • 无序数组排序后的最大相邻差
    • 普通排序后求
    • 计数排序后求
    • 桶排序求
  • 使用栈实现队列
    使用两个栈来实现队列
  • 寻找全排列的下一个数
    尽量保持高位不变,低位在最小的范围内变换顺序
    变换顺序的范围取决于逆序区域
    称为字典序算法
  • 删除k个数字后的最小值
    把原整数的所有数字从左到右进行比较,若果发现某一位的数字大于它右边的数字,那么删除该数字后,必然会使改数位的值降低。
    • 这一段好好看代码的优化

这种依次求得局部最优解,最终得到全局最优解的思想,叫做贪心算法

  • 实现最大整数相加
    通过把大整数拆分成数组来实现,只需要拆分到可以被直接计算的程度就够了

BigInteger和BigDecimal的底层实现也是拆分的思想

  • 金矿的求解问题(动态规划问题)
    和著名的“背包问题”类似
    • 全局问题
    • 最优子结构
    • 问题的边界
    • 状态转移方程式
      (n金矿数,工人w,含金量g[],开采人数p[])
      F(n,w) = max(F(n-1,w),F(n-1,w-p[n-1]+g[n-1]) (n>0,w>=p[n-1])
    1. 采用递归
    2. 使用数组来实现,2次优化
  • 寻找缺失的整数
    加起来得到和,再依次减去数组里的差值
    • 拓展1:出现奇数次的1个整数寻找
      使用异或运算
    • 拓展1:出现奇数次的2个整数
      分治思想,异或

算法的实际应用

  • 位图运算bitmap
  • LRU(Least Recently Used)
  • A search algorithmn
    • 启发式搜索
  • 红包算法

你可能感兴趣的:(算法)