本文最后更新于:星期四, 六月 18日 2020, 9:01 上午

阅读<<算法基础>>做下的笔记


public class Algorithms {
    private Algorithms() {

    }

    public static Algorithms getInstance() {
        return SingletonHolder.instance;
    }

    private static class SingletonHolder {
        private static final Algorithms instance = new Algorithms();
    }

    /**
     * 线性查找
     *
     * @param arrays 被查找的数组
     * @param count  查找多少个,比如输入3,就只查找数组中对应下标0,1,2的三个元素
     * @param data   要查找的值
     * @param <T>    泛型T
     * @return 如果找到了,返回最后一次出现的下标;如果没找到,返回-1。
     */
    public <T> int linearSearch(T[] arrays, int count, T data) {
        int result = -1;
        for (int i = 0; i < count - 1; i++) {
            if (data.equals(arrays[i])) {
                result = i;
            }
        }
        return result;
    }

    //与上面的方法的区别:在循环体内若找到了data,直接返回第一个找到的下标。
    public <T> int betterLinearSearch(T[] arrays, int count, T data) {
        int result = -1;
        for (int i = 0; i < count - 1; i++) {
            if (arrays[i].equals(data)) {
                return i;
            }
        }
        return result;
    }

    /**
     * 相比上面两个算法,该算法效率更高,因为:
     * 上面用for循环时,除了循环体中的判断,还要进行for循环条件的i<count-1的判断;循环做两次判断。
     * 该算法的while循环,只有循环条件的判断;循环做一次判断
     *
     * @param arrays 同上
     * @param count  同上
     * @param data   同上
     * @param <T>    同上
     * @return 同上
     */
    public <T> int sentinelLinearSearch(T[] arrays, int count, T data) {
        //lastIndex是要查找的最后一个元素的下标,如果输入的查找个数count>数组元素个数,
        // 赋值其为数组最末尾下标,反之赋值count-1。
        int lastIndex = count > arrays.length ? arrays.length - 1 : count - 1;
        //保存要查找的最末尾的值
        T lastData = arrays[lastIndex];
        //将要查找的最末尾的值赋值为data(为了while循环最后可以退出)(后面再还原)
        arrays[lastIndex] = data;
        int i = 0;
        //如果data不等于数组从0开始的元素,i自增
        while (!data.equals(arrays[i])) {
            i++;
        }
        //将先前改变的数组末尾的值还原
        arrays[lastIndex] = lastData;
        //如果i<lastIndex,说明while循环中匹配到了。如果data等于此时array末尾元素,说明也匹配到了
        if (i < lastIndex || data.equals(arrays[lastIndex])) {
            return i;
        } else {
            return -1;
        }
    }

    /**
     * factorial.n : 递归
     * 递归的两个特性:
     * 1、必须有一个或多个不用递归而直接计算出结果的基础情况。
     * 2、每个递归调用最终能迭代到基础情况。
     *
     * @param n
     * @return
     */
    public int factorial(int n) {
        if (n == 0) {
            return 1;
        } else {
            return n * factorial(n - 1);
        }
    }

    //错误,递归迭代不到基础情况
    @Deprecated
    public int badFactorial(int n) {
        if (n == 0) {
            return 1;
        } else {
            return badFactorial(n + 1) / (n + 1);
        }

    }

    /**
     * 递归线性查找
     * 基础情况是第一个if语句和第一个else if语句,else语句进行递归
     * 最后要么到第一个基础情况结束,要么第二个基础情况结束。
     *
     * @param array 被查找的数组
     * @param start 开始下标
     * @param end   结束下标
     * @param data  要查找的元素
     * @param <T>   泛型
     * @return 如果找到,返回找到的第一个元素的下标;否则返回-1
     */
    public <T> int recursiveLinearSearch(T[] array, int start, int end, T data) {
        if (start > end) {
            return -1;
        } else if (data.equals(array[start])) {
            return start;
        } else {//!data.equals(array[start])
            return recursiveLinearSearch(array, start + 1, end, data);
        }
    }

    /**
     * 二分查找(while循环)
     *
     * @param array 从小到大排序的有序数组
     * @param end   要查找的终点
     * @param data  要查找的数据
     * @param <T>   实现了Comparable接口的类型
     * @return 找到返回找到的下标,否则返回-1。
     */
    public <T extends Comparable<T>> int binarySearch(T[] array, int end, T data) {
        if (end > array.length - 1) {
            //如果end超出数组边界
            end = array.length - 1;
        }

        int start = 0;
        while (start < end) {
            int middle = (start + end) / 2;

            int compareResult = data.compareTo(array[middle]);

            if (compareResult == 0) {
                return middle;
            } else if (compareResult < 0) {
                end = middle - 1;
            } else {
                start = middle + 1;
            }
        }
        return -1;
    }

    /**
     * 二分查找(递归)
     *
     * @param array 从小到大排序的有序数组
     * @param start 起点
     * @param end   终点
     * @param data  要查找的数据
     * @param <T>   实现了Comparable接口的类型
     * @return 同上
     */
    public <T extends Comparable<T>> int recursiveBinarySearch(T[] array, int start, int end, T data) {
        if (end > array.length - 1) {//如果end超出数组边界
            end = array.length - 1;
        }
        if (start < 0) {//如果start超出数组边界
            start = 0;
        }

        if (start > end) {
            return -1;
        } else {
            int middle = (start + end) / 2;

            int compareResult = data.compareTo(array[middle]);
            if (compareResult == 0) {
                return middle;
            } else if (compareResult < 0) {
                return recursiveBinarySearch(array, start, middle - 1, data);
            } else {//compareResult>0
                return recursiveBinarySearch(array, middle + 1, end, data);
            }
        }
    }

    /**
     * 选择排序法
     *
     * @param array
     * @return
     */
    public int[] selectionSort(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            int temp = array[i];
            int minIndex = i;
            //查找最小值的外部的变量minValue。
            int minValue = array[i];
            for (int j = i + 1; j < array.length; j++) {
                if (array[j] < minValue) {
                    minValue = array[j];//一定要在这里将array[j]记录下来,赋值给minValue
                    minIndex = j;
                }
            }
            if (minIndex != i) {
                array[i] = array[minIndex];
                array[minIndex] = temp;
            }
        }
        return array;
    }

    private int findMin(int[] array, int start, int end) {
        int min = array[start];
        int index = start;
        for (int i = start + 1; i < end; i++) {
            if (array[i] < min) {
                index = i;
                min = array[i];
            }
        }
        return index;
    }

    public int[] anotherSelectionSort(int[] array) {
        for (int i = 0, len = array.length; i < len - 1; i++) {
            int temp = array[i];
            int min = findMin(array, i, len);
            array[i] = array[min];
            array[min] = temp;
        }
        return array;
    }

    /**
     * 插入排序
     *
     * @param array    插入排序的数组
     * @param endIndex 排序的最后的下标
     */
    public void insertionSort(int[] array, int endIndex) {
        for (int i = 1; i <= endIndex; i++) {
            //由于下方while循环的第一次迭代会覆盖array[i],所以必须讲array[i]保存在key中
            int key = array[i];
            int j = i - 1;
            while (j >= 0 && key < array[j]) {//如果j没有越界,并且在前面找到比自己小的array[j]
                //把array[j]往后移动一个位置
                array[j + 1] = array[j];
                //自己递减,以便下次循环接着往前找
                j--;
            }
            //最后将key放到挪开的那个空的位置上去
            array[j + 1] = key;
        }
    }


    /**
     * 归并排序法
     *
     * @param array 要被归并排序的数组
     * @param p     previous,起点下标
     * @param r     rear,终点下标
     */
    public void mergeSort(int[] array, int p, int r) {
        if (p >= r) {//如果起点大于等于终点,说明递归到了基础情况,返回.
            return;
        } else {
            int q = (p + r) / 2;//取中间的那个数
            //分成两部分来递归,注意!mergeSort()执行完,就说明排好一次序,
            // 因此merge()方法总能拿到数组内部两截排序好的情况.
            mergeSort(array, p, q);//前半截
            mergeSort(array, q + 1, r);//后半截
            //递归到最小的基础情况,开始归并排序.
            merge(array, p, q, r);
        }
    }

    /**
     * 真正执行归并的算法
     * 有点复杂,忘了的话还是看书回忆原理.
     * <p>
     * 传入的数组分为两截,从p到q和q+1到r,都是有序的.(如果两截都只有一个元素也成为两部分各自是有序的)
     * 将这两部分有序数组分别拷贝到两个新的数组中,因为是有序的,从下标0开始循环递归比较两个新的数组.
     * 较小的那个放入array中,依次递归下去,最后将生成新的有序的array.
     * <p>
     * "哨兵"处理的思路:
     * 两个新拷贝的数组b,c一定率先有一方全部"出队",并且游标自增,
     * 那么下一轮的判断条件就会造成数组越界(游标自增到了数组外面),
     * 新的做法是给数组b,c额外多申请一个空间,放入MAX值.
     * 那么当b或c中有意义的元素全部出队之后,游标到了哨兵的位置,且此时不会数组越界.
     * 因为哨兵是MaxValue,所以下一轮以及之后的所有比较,都会让另一个数组出队,直到有意义的元素全部出队.
     * k=r,排序结束.
     *
     * @param array 要归并排序的数组
     * @param p     起点下标
     * @param q     中点下标
     * @param r     终点下标
     */
    private void merge(int[] array, int p, int q, int r) {
        int n1 = q - p + 1;//新数组b的长度
        int n2 = r - q;//新数组c的长度
        int[] b = new int[n1 + 1];//用于拷贝,为了下面的操作不用每次判断是否数组越界,额外地多申请一个地址,用"哨兵"技巧,所以n1+1.
        int[] c = new int[n2 + 1];//用于拷贝,同上
        /*关于为什么要拷贝出新的数组而不在原址上操作,是因为在上原址操作更加麻烦,性能更差,详见<<算法基础>>page.44*/
        for (int i = 0, len = b.length; i < len - 1; i++) {
            b[i] = array[p + i];//拷贝
        }
        for (int i = 0, len = c.length; i < len - 1; i++) {
            c[i] = array[q + 1 + i];//拷贝
        }
        //将两个数组最末尾的元素设成最大值,做"哨兵".
        b[n1] = Integer.MAX_VALUE;
        c[n2] = Integer.MAX_VALUE;

        int i = 0, j = 0;//i是数组b的开始下标,j是数组c的开始下标.

        for (int k = p; k <= r; k++) {//开始排序,k是原数组array的开始下标,从p取到r.
            if (b[i] <= c[j]) {
                array[k] = b[i];
                i++;
            } else {
                array[k] = c[j];
                j++;
            }
        }

    }

    /**
     * 快速排序法
     *
     * @param array 快排数组
     * @param p     起点下标
     * @param r     终点下标
     */
    public void quickSort(int[] array, int p, int r) {
        if (p >= r) {
            //基础情况,数组为空,返回
            return;
        } else {
            //将数组划分成各自无序的left组和right组两部分,但是left组全部小于right组。
            //q就是分界线。
            int q = partition(array, p, r);
            quickSort(array, p, q - 1);//递归下去,让left组也这样分
            quickSort(array, q + 1, r);//递归下去,让right组也这样分
            //最后结果是数组只有两个元素,一个left一个right,接着递归下去就是基础情况,return。
        }
    }

    /**
     * 划分
     * 结果:
     * 将传入的数组划分成left和right两组
     * 选择array[r]作为主元,小于array[r]的都进入left组,大于array[r]的都进入right组。
     * 最后q作为分界线返回。
     *
     * 变量说明:
     * 将数组array分成四部分:left组,right组,unknown组,主元r。
     * left组全部小于主元且无序,right全部大于主元且无序,unknown组是待排序的元素的组。
     *
     * 算法中有四个指针p,r,q,u。
     * 其中q和u会变动。
     * q是指向right组的第一个元素,u组指向unknown组的第一个元素。
     *
     * @param array 要划分的数组
     * @param p 起点下标
     * @param r 终点下标
     * @return 返回分界线指针(也就是主元)的下标。
     */
    private int partition(int[] array, int p, int r) {
        int q = p;//q指向right组的第一个元素
        for (int u = p; u < r; u++) {//u指向unknown组的第一个元素
            if (array[u] <= array[r]) {//如果未知元素小于主元
                //那么让right组的第一个元素和unknown交换
                int temp = array[q];
                array[q] = array[u];
                array[u] = temp;
                //q++,让q继续正确指向right组第一个元素
                q++;
                //这样就让unknown组较小的元素放到了right组左边,即Left组
            }//else{
                //就是array[u]>array[r]
                //那么就是u++,让unknown指针后移,刚好把对应元素放进了right组.
            // }
        }
        //最后让主元和right组第一个元素互换即可
        //(主元到达分割线位置,原位置的元素依然在right组,只是跑到最后去了 )
        int temp = array[q];
        array[q] = array[r];
        array[r] = temp;
        return q;
    }
}

快排的partision的指针图示:

的类型
* @return 找到返回找到的下标,否则返回-1。
*/
public <T extends Comparable> int binarySearch(T[] array, int end, T data) {
if (end > array.length - 1) {
//如果end超出数组边界
end = array.length - 1;
}

    int start = 0;
    while (start < end) {
        int middle = (start + end) / 2;

        int compareResult = data.compareTo(array[middle]);

        if (compareResult == 0) {
            return middle;
        } else if (compareResult < 0) {
            end = middle - 1;
        } else {
            start = middle + 1;
        }
    }
    return -1;
}

/**
 * 二分查找(递归)
 *
 * @param array 从小到大排序的有序数组
 * @param start 起点
 * @param end   终点
 * @param data  要查找的数据
 * @param <T>   实现了Comparable接口的类型
 * @return 同上
 */
public <T extends Comparable<T>> int recursiveBinarySearch(T[] array, int start, int end, T data) {
    if (end > array.length - 1) {//如果end超出数组边界
        end = array.length - 1;
    }
    if (start < 0) {//如果start超出数组边界
        start = 0;
    }

    if (start > end) {
        return -1;
    } else {
        int middle = (start + end) / 2;

        int compareResult = data.compareTo(array[middle]);
        if (compareResult == 0) {
            return middle;
        } else if (compareResult < 0) {
            return recursiveBinarySearch(array, start, middle - 1, data);
        } else {//compareResult>0
            return recursiveBinarySearch(array, middle + 1, end, data);
        }
    }
}

/**
 * 选择排序法
 *
 * @param array
 * @return
 */
public int[] selectionSort(int[] array) {
    for (int i = 0; i < array.length - 1; i++) {
        int temp = array[i];
        int minIndex = i;
        //查找最小值的外部的变量minValue。
        int minValue = array[i];
        for (int j = i + 1; j < array.length; j++) {
            if (array[j] < minValue) {
                minValue = array[j];//一定要在这里将array[j]记录下来,赋值给minValue
                minIndex = j;
            }
        }
        if (minIndex != i) {
            array[i] = array[minIndex];
            array[minIndex] = temp;
        }
    }
    return array;
}

private int findMin(int[] array, int start, int end) {
    int min = array[start];
    int index = start;
    for (int i = start + 1; i < end; i++) {
        if (array[i] < min) {
            index = i;
            min = array[i];
        }
    }
    return index;
}

public int[] anotherSelectionSort(int[] array) {
    for (int i = 0, len = array.length; i < len - 1; i++) {
        int temp = array[i];
        int min = findMin(array, i, len);
        array[i] = array[min];
        array[min] = temp;
    }
    return array;
}

/**
 * 插入排序
 *
 * @param array    插入排序的数组
 * @param endIndex 排序的最后的下标
 */
public void insertionSort(int[] array, int endIndex) {
    for (int i = 1; i <= endIndex; i++) {
        //由于下方while循环的第一次迭代会覆盖array[i],所以必须讲array[i]保存在key中
        int key = array[i];
        int j = i - 1;
        while (j >= 0 && key < array[j]) {//如果j没有越界,并且在前面找到比自己小的array[j]
            //把array[j]往后移动一个位置
            array[j + 1] = array[j];
            //自己递减,以便下次循环接着往前找
            j--;
        }
        //最后将key放到挪开的那个空的位置上去
        array[j + 1] = key;
    }
}


/**
 * 归并排序法
 *
 * @param array 要被归并排序的数组
 * @param p     previous,起点下标
 * @param r     rear,终点下标
 */
public void mergeSort(int[] array, int p, int r) {
    if (p >= r) {//如果起点大于等于终点,说明递归到了基础情况,返回.
        return;
    } else {
        int q = (p + r) / 2;//取中间的那个数
        //分成两部分来递归,注意!mergeSort()执行完,就说明排好一次序,
        // 因此merge()方法总能拿到数组内部两截排序好的情况.
        mergeSort(array, p, q);//前半截
        mergeSort(array, q + 1, r);//后半截
        //递归到最小的基础情况,开始归并排序.
        merge(array, p, q, r);
    }
}

/**
 * 真正执行归并的算法
 * 有点复杂,忘了的话还是看书回忆原理.
 * <p>
 * 传入的数组分为两截,从p到q和q+1到r,都是有序的.(如果两截都只有一个元素也成为两部分各自是有序的)
 * 将这两部分有序数组分别拷贝到两个新的数组中,因为是有序的,从下标0开始循环递归比较两个新的数组.
 * 较小的那个放入array中,依次递归下去,最后将生成新的有序的array.
 * <p>
 * "哨兵"处理的思路:
 * 两个新拷贝的数组b,c一定率先有一方全部"出队",并且游标自增,
 * 那么下一轮的判断条件就会造成数组越界(游标自增到了数组外面),
 * 新的做法是给数组b,c额外多申请一个空间,放入MAX值.
 * 那么当b或c中有意义的元素全部�

算法     

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

Broadcast全解 上一篇
Comparable接口是什么 下一篇