• 算法


    一、排序算法

    1. 简单选择排序

    表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。
    选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

    n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:

    • 初始状态:无序区为R[1..n],有序区为空;
    • 第i趟排序(i=1,2,3...n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
    • n-1趟结束,数组有序化了。
    function selectionSort(arr) {
        var len = arr.length;
        var minIndex, temp;
        for (var i = 0; i < len - 1; i++) {
            minIndex = i;
            for (var j = i + 1; j < len; j++) {
                if (arr[j] < arr[minIndex]) {     //寻找最小的数
                    minIndex = j;                 //将最小数的索引保存
                }
            }
            temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
        console.timeEnd('选择排序耗时');
        return arr;
    }
    var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
    console.log(selectionSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

    2. 直接插入排序

    插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

    一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

    • 从第一个元素开始,该元素可以认为已经被排序;
    • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
    • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
    • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
    • 将新元素插入到该位置后;
    • 重复步骤2~5。
    function insertionSort(array) {
        if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') {
            console.time('插入排序耗时:');
            for (var i = 1; i < array.length; i++) {
                var key = array[i];
                var j = i - 1;
                while (j >= 0 && array[j] > key) {
                    array[j + 1] = array[j];
                    j--;
                }
                array[j + 1] = key;
            }
            console.timeEnd('插入排序耗时:');
            return array;
        } else {
            return 'array is not an Array!';
        }
    }

    3. 冒泡排序

    冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

    • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
    • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
    • 针对所有的元素重复以上的步骤,除了最后一个;
    • 重复步骤1~3,直到排序完成。
    function bubbleSort3(arr3) {
        var low = 0;
        var high= arr.length-1; //设置变量的初始值
        var tmp,j;
        while (low < high) {
            for (j= low; j< high; ++j) //正向冒泡,找到最大者
                if (arr[j]> arr[j+1]) {
                    tmp = arr[j]; 
                    arr[j]=arr[j+1];
                    arr[j+1]=tmp;
                }
            --high;                 //修改high值, 前移一位
            for (j=high; j>low; --j) //反向冒泡,找到最小者
                if (arr[j]

    4.快速排序

    快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

    快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

    • 从数列中挑出一个元素,称为 "基准"(pivot);
    • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
    • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
    /*方法说明:快速排序
    @param  array 待排序数组*/
    
    var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
    
    var quickSort=function(arr){
        if(arr.length <= 1){
            return arr;
        }
        var keyIndex=Math.floor(arr.length / 2);
        var key=arr.splice(keyIndex,1)[0];
        var left=[];
        var right=[];
        for(var i=0;i<arr.length;i++){
            if(arr[i] < index){
                left.push(arr[i]);
            }else {
                right.push(arr[i]);
            }
        }
        
        return quickSort(left).concat([index],quickSort(right);
    }

    5. 希尔排序

    希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
    希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
    • 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
    • 按增量序列个数k,对序列进行k 趟排序;
    • 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
    function shellSort(arr) {
        var len = arr.length,
            temp,
            gap = 1;
        console.time('希尔排序耗时:');
        while(gap < len/5) {          //动态定义间隔序列
            gap =gap*5+1;
        }
        for (gap; gap > 0; gap = Math.floor(gap/5)) {
            for (var i = gap; i < len; i++) {
                temp = arr[i];
                for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) {
                    arr[j+gap] = arr[j];
                }
                arr[j+gap] = temp;
            }
        }
        console.timeEnd('希尔排序耗时:');
        return arr;
    }
    var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
    console.log(shellSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

    6. 堆排序

    堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

    • 首先遍历数组,判断该节点的父节点是否比他小,如果小就交换位置并继续判断,直到他的父节点比他大 重新以上操作 1,直到数组首位是最大值
    • 然后将首位和末尾交换位置并将数组长度减一,表示数组末尾已是最大值,不需要再比较大小
    • 对比左右节点哪个大,然后记住大的节点的索引并且和父节点对比大小,如果子节点大就交换位置
    • 重复以上操作 3 - 4 直到整个数组都是大根堆。
    function heap(array) {
      checkArray(array);
      // 将最大值交换到首位
      for (let i = 0; i < array.length; i++) {
        heapInsert(array, i);
      }
      let size = array.length;
      // 交换首位和末尾
      swap(array, 0, --size);
      while (size > 0) {
        heapify(array, 0, size);
        swap(array, 0, --size);
      }
      return array;
    }
    
    function heapInsert(array, index) {
      // 如果当前节点比父节点大,就交换
      while (array[index] > array[parseInt((index - 1) / 2)]) {
        swap(array, index, parseInt((index - 1) / 2));
        // 将索引变成父节点
        index = parseInt((index - 1) / 2);
      }
    }
    function heapify(array, index, size) {
      let left = index * 2 + 1;
      while (left < size) {
        // 判断左右节点大小
        let largest =
          left + 1 < size && array[left] < array[left + 1] ? left + 1 : left;
        // 判断子节点和父节点大小
        largest = array[index] < array[largest] ? largest : index;
        if (largest === index) break;
        swap(array, index, largest);
        index = largest;
        left = index * 2 + 1;
      }
    }
    // 将 i 结点以下的堆整理为大顶堆,注意这一步实现的基础实际上是:
    // 假设 结点 i 以下的子堆已经是一个大顶堆,heap_adjust函数实现的
    // 功能是实际上是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面将写一个 for 循环,从第一个非叶子结点开始,对每一个非叶子结点都执行 heap_adjust操作,所以就满足了结点 i 以下的子堆已经是一大顶堆
    function heap_adjust(arr, start, end){
      var temp = arr[start],
      j = start*2;
          // j<end 的目的是对结点 i 以下的结点全部做顺序调整
      for(;j < end; j *= 2){
        if(arr[j] < arr[j+1]){
          j++;// 找到两个孩子中较大的一个,再与父节点比较
        }
        if(temp > arr[j]){
          break;
        }
        arr[start] = arr[j];
        start = j;// 交换后,temp 的下标变为 j
      }
      arr[start] = temp;
    }
    function heap_sort(arr){
          // 初始化大顶堆,从第一个非叶子结点开始
      var len = arr.length;
      for(var i = len/2; i >= 0; i--){
        heap_adjust(arr, i, len);
      }
          // 排序,每一次for循环找出一个当前最大值,数组长度减一
      for(var i = len; i > 0; i--){
        swap(arr, 0, i -1);// 根节点与最后一个节点交换
               // 从根节点开始调整,并且最后一个结点已经为当前最大值,不需要再参与比较,所以第三个参数为 i,即比较到最后一个结点前一个即可 
        heap_adjust(arr, 0, i);
      }
    }
    function swap(arr, i, j){
      var temp = arr[i];
      arr[i] = arr[j];
      arr[j] = temp;
    }

    7. 归并排序

    和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度。代价是需要额外的内存空间。
    归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
    • 把长度为n的输入序列分成两个长度为n/2的子序列;
    • 对这两个子序列分别采用归并排序;
    • 将两个排序好的子序列合并成一个最终的排序序列。
    function mergeSort(arr) {
        const length = arr.length;
        if (length === 1) { //递归算法的停止条件,即为判断数组长度是否为1
            return arr;
        }
        const mid = Math.floor(length / 2);
        const left = arr.slice(0,  mid);
        const right = arr.slice(mid, length);
        return merge(mergeSort(left), mergeSort(right)); //要将原始数组分割直至只有一个元素时,才开始归并
    }
    function merge(left, right) {
        const result = [];
        let il = 0;
        let ir = 0;
        //left, right本身肯定都是从小到大排好序的
        while( il < left.length && ir < right.length) {
            if (left[il] < right[ir]) {
                result.push(left[il]);
                il++;
            } else {
                result.push(right[ir]);
                ir++;
            }  
        }
        //不可能同时存在left和right都有剩余项的情况, 要么left要么right有剩余项, 把剩余项加进来即可
        while (il < left.length) { 
            result.push(left[il]);
            il++;
        }
        while(ir < right.length) {
            result.push(right[ir]);
            ir++;
        }
        return result;
    }

    本部分摘自:https://blog.csdn.net/hellozhxy/article/details/79911867

    二、动态规划,参见背包问题

    动态规划的原理
    动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。

    最优性原理是动态规划的基础,最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。

    背包问题

    在解决问题之前,为描述方便,首先定义一些变量:Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积,定义V(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值,同时背包问题抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 个物品选或不选)。

    1、建立模型,即求max(V1X1+V2X2+…+VnXn);

    2、寻找约束条件,W1X1+W2X2+…+WnXn<capacity;

    3、寻找递推关系式,面对当前商品有两种可能性:

    包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);
    还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}。
    其中V(i-1,j)表示不装,V(i-1,j-w(i))+v(i) 表示装了第i个商品,背包容量减少w(i),但价值增加了v(i);

    由此可以得出递推关系式:

    • j<w(i)      V(i,j)=V(i-1,j)
    • j>=w(i)     V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}
    /**
        * @param m : 表示背包的最大容量
        * @param n : 表示商品的个数
        * @param w : 表示商品重量数组
        * @param p : 表示商品价值数组
        * 
    */
    public static int[][] DP_01bag(int m,int n,int w[],int p[]){
        //c[i][m] 表示前i件物品恰好放入重量为m的背包时的最大价值
        int c[][] = new int[n+1][m+1];
        for(int i=0;i<n+1;i++){
        c[i][0] = 0;
        }
        for(int j=0;j<m+1;j++){
        c[0][j] = 0;
        }
        for(int i=1;i<n+1;i++){
          for(int j=1;j<m+1;j++){
              //当物品为i件重量为j时,如果第i件的重量(w[i-1])小于重量j时,c[i][j]为下列两种情况之一:
             //(1)物品i不放入背包中,所以c[i][j]为c[i-1][j]的值
             //(2)物品i放入背包中,则背包剩余重量为j-w[i-1],所以c[i][j]为c[i-1][j-w[i-1]]的值加上当前物品i的价值    
            if(w[i-1] <= j){
                if(c[i-1][j] <c[i-1][j-w[i-1]]+p[i-1]){
                  c[i][j] = c[i-1][j-w[i-1]]+p[i-1];
                  }else{
                      c[i][j] = c[i-1][j];
                  }            
               }
            }
        }
        return c;
    }       

    本部分参考:https://blog.csdn.net/qq_38410730/article/details/81667885         js版本实现

    三、二叉树

     二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。

    由二叉树定义以及图示分析得出二叉树有以下特点:
    1)每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点。
    2)左子树和右子树是有顺序的,次序不能任意颠倒。
    3)即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。

    性质:

    1)在二叉树的第i层上最多有2i-1 个节点 。(i>=1)
    2)二叉树中如果深度为k,那么最多有2k-1个节点。(k>=1)
    3)n0=n2+1 n0表示度数为0的节点数,n2表示度数为2的节点数。
    4)在完全二叉树中,具有n个节点的完全二叉树的深度为[log2n]+1,其中[log2n]是向下取整。
    5)若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点有如下特性:

    • 若 i=1,则该结点是二叉树的根,无双亲, 否则,编号为 [i/2] 的结点为其双亲结点;
    • 若 2i>n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子结点;
    • 若 2i+1>n,则该结点无右孩子结点, 否则,编号为2i+1 的结点为其右孩子结点。
    满二叉树:在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
    满二叉树的特点有:
    1)叶子只能出现在最下一层。出现在其它层就不可能达成平衡。
    2)非叶子结点的度一定是2。
    3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。
    完全二叉树:对一颗具有n个结点的二叉树按层编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
    特点
    1)叶子结点只能出现在最下层和次下层。
    2)最下层的叶子结点集中在树的左部。
    3)倒数第二层若存在叶子结点,一定在右部连续位置。
    4)如果结点度为1,则该结点只有左孩子,即没有右子树。
    5)同样结点数目的二叉树,完全二叉树深度最小。
    :满二叉树一定是完全二叉树,但反过来不一定成立。

    顺序存储:二叉树的顺序存储结构就是使用一维数组存储二叉树中的结点,并且结点的存储位置,就是数组的下标索引。

    二叉排序树,又叫二叉查找树,它或者是一棵空树;或者是具有以下性质的二叉树:
    1. 若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
    2. 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
    3. 它的左右子树也分别为二叉排序树。

    四、加油站问题(贪心算法)

    贪心选择:在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
    最优子结构:当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。

    问题:一辆汽车加满油后可行驶 n公里。旅途中有若干个加油站。设计一个有效算法,指出应 在哪些加油站停靠加油,使沿途加油次数最少。

    输入:

    第一行有 2 个正整数n和 k(k<=1000 ),表示汽车加满油后可行驶n公里,且旅途中有 k个加油站。 第二行有 k+1 个整数,表示第 k 个加油站与第k-1 个加油站之间的距离。 第 0 个加油站表示出发地,汽车已加满油。 第 k+1 个加油站表示目的地。

    输出:

    输出最少加油次数。如果无法到达目的地,则输出“No Solution!”。

    输入样例:

    7 7
    1 2 3 4 5 1 6 6

    输出样例:

    4

    分析:找到汽车满油量时可以行驶的最大路程范围内的最后一个加油站,加油后则继续用此方法前进。需要检查每一小段路程是否超过汽车满油量时的最大支撑路程。

    function greedy(n,k) {
            var count=0;
            var arr =[];
            //随机分配arr的每个数,即第i和i-1个加油站之间的距离
            for (var i =0 ;i <=k;i++){
                arr[i] = parseInt(Math.random()*10+1);
            }
            console.log(arr);//5 8 6 1 9 10 8 4 3 6 9
            var  num=0;
            for(var j=0;j<=k;j++)
            {
                count+=arr[j];
                if(count>n)
                {
                    num++;
                    count=arr[j];
                }
            }
            console.log('汽车最少要需要加油的次数为:'+num);//8
            return ;
        }
        console.log(greedy(10,10));

    本部分摘自:https://blog.csdn.net/m0_37686205/article/details/90115668

    五、二分法

    二分法查找,也称为折半法,是一种在有序数组中查找特定元素的搜索算法。

    二分法查找的思路如下:

    (1)首先,从数组的中间元素开始搜索,如果该元素正好是目标元素,则搜索过程结束,否则执行下一步。

    (2)如果目标元素大于/小于中间元素,则在数组大于/小于中间元素的那一半区域查找,然后重复步骤(1)的操作。

    (3)如果某一步数组为空,则表示找不到目标元素。

    二分法查找的时间复杂度O(logn)。

    //非递归
    function binarySearch(arr,key){
        var low=0; //数组最小索引值
        var high=arr.length-1; //数组最大索引值
        while(low<=high){
            var mid=Math.floor((low+high)/2);
            if(key==arr[mid]){
                return mid;
            }else if(key>arr[mid]){
                low=mid+1;
            }else{
                high=mid-1;
            }
        }
        return -1; //low>high的情况,这种情况下key的值大于arr中最大的元素值或者key的值小于arr中最小的元素值
    }
    //递归
    function binarySearch(arr,low,high,key){
        if(low>high){return -1;}
        var mid=Math.floor((low+high)/2);
        if(key==arr[mid]){
            return mid;
        }else if(key<arr[mid]){
            high=mid-1;
            return binarySearch(arr,low,high,key);
        }else{
            low=mid+1;
            return binarySearch(arr,low,high,key);
        }
    }

    六、二叉树遍历

    1. 前序遍历

    前序遍历通俗的说就是从二叉树的根结点出发,当第一次到达结点时就输出结点数据,按照先向左在向右的方向访问。

    从根结点出发,则第一次到达结点A,故输出A;
    继续向左访问,第一次访问结点B,故输出B;
    按照同样规则,输出D,输出H;
    当到达叶子结点H,返回到D,此时已经是第二次到达D,故不在输出D,进而向D右子树访问,D右子树不为空,则访问至I,第一次到达I,则输出I;
    I为叶子结点,则返回到D,D左右子树已经访问完毕,则返回到B,进而到B右子树,第一次到达E,故输出E;
    向E左子树,故输出J;
    按照同样的访问规则,继续输出C、F、G;
    二叉树的前序遍历输出为:
    ABDHIEJCFG

    先序递归遍历思路:先遍历根结点,将值存入数组,然后递归遍历:先左结点,将值存入数组,继续向下遍历;直到(二叉树为空)子树为空,则遍历结束;然后再回溯遍历右结点,将值存入数组,这样递归循环,直到(二叉树为空)子树为空,则遍历结束。

    先序非递归遍历思路:

    • 初始化一个栈,将根节点压入栈中;
    • 当栈为非空时,循环执行步骤3到4,否则执行结束;
    • 从队列取得一个结点(取的是栈中最后一个结点),将该值放入结果数组;
    • 若该结点的右子树为非空,则将该结点的右子树入栈,若该结点的左子树为非空,则将该结点的左子树入栈;(注意:先将右结点压入栈中,后压入左结点,从栈中取得时候是取最后一个入栈的结点,而先序遍历要先遍历左子树,后遍历右子树)
    //递归
    let result = [];
    let dfs = function (node) {
        if(node) {
            result.push(node.value);
            dfs(node.left);
            dfs(node.right);
        }
    }
    //非递归
    let dfs = function (nodes) {
        let result = [];
        let stack = [];
        stack.push(nodes);
        while(stack.length) { // 等同于 while(stack.length !== 0) 直到栈中的数据为空
            let node = stack.pop(); // 取的是栈中最后一个j
            result.push(node.value);
            if(node.right) stack.push(node.right); // 先压入右子树
            if(node.left) stack.push(node.left); // 后压入左子树
        }
        return result;
    }
    /*
         * 1、有左儿子,入栈 
         * 2、无左儿子,自己出栈并看右儿子是否为空 
         * 3、右儿子为空,出栈 
         * 4、右儿子不为空,入栈 
         * 5、入栈时输出
         */
        public void preIterate(TreeNode<String> root) {
            Stack<TreeNode<String>> stack = new Stack<TreeNode<String>>();
            TreeNode<String> now = root;
            do {
                while (now != null) {
                    System.out.print(now.getData() + " ");
                    stack.push(now);
                    now = now.getLeft();
                }
                if (stack.size() == 0) {
                    break;
                }
                now = stack.pop();
                now = now.getRight();
            } while (stack.size() >= 0);
        }

    2. 中序遍历

     中序遍历就是从二叉树的根结点出发,当第二次到达结点时就输出结点数据,按照先向左在向右的方向访问。

    从根结点出发,则第一次到达结点A,不输出A,继续向左访问,第一次访问结点B,不输出B;继续到达D,H;
    到达H,H左子树为空,则返回到H,此时第二次访问H,故输出H;
    H右子树为空,则返回至D,此时第二次到达D,故输出D;
    由D返回至B,第二次到达B,故输出B;
    按照同样规则继续访问,输出J、E、A、F、C、G;
    二叉树的中序遍历输出为:
    HDIBJEAFCG

    中序递归遍历的思路:先递归遍历左子树,从最后一个左子树开始存入数组,然后回溯遍历双亲结点,再是右子树,这样递归循环。

    非递归遍历的思路:将当前结点压入栈,然后将左子树当做当前结点,如果当前结点为空,将双亲结点取出来,将值保存进数组,然后将右子树当做当前结点,进行循环。

    //递归
    let result = [];
    let dfs = function (node) {
         if(node) {
            dfs(node.left);
            result.push(node.value); // 直到该结点无左子树 将该结点存入结果数组 接下来并开始遍历右子树
            dfs(node.right);
        }
    }
    //非递归
    function dfs(node) {
        let result = [];
        let stack = [];
        while(stack.length || node) { // 是 || 不是 &&
            if(node) {
                stack.push(node);
                node = node.left;
            } else {
                node = stack.pop();
                result.push(node.value);
                //node.right && stack.push(node.right);
                node = node.right; // 如果没有右子树 会再次向栈中取一个结点即双亲结点
            }
        }
        return result;
    }
    /*
         * 1、有左儿子,入栈 
         * 2、无左儿子,自己出栈并看右儿子是否为空 
         * 3、右儿子为空,出栈 
         * 4、右儿子不为空,入栈 
         * 5、出栈时输出
         */
        public void midIterate(TreeNode<String> root) {
            Stack<TreeNode<String>> stack = new Stack<TreeNode<String>>();
            TreeNode<String> now = root;
            do {
                while (now != null) {
                    stack.push(now);
                    now = now.getLeft();
                }
                if (stack.size() == 0) {
                    break;
                }
                now = stack.pop();
                System.out.print(now.getData() + " ");
                now = now.getRight();
            } while (stack.size() >= 0);
        }

    3. 后序遍历

     后序遍历就是从二叉树的根结点出发,当第三次到达结点时就输出结点数据,按照先向左在向右的方向访问。

    从根结点出发,则第一次到达结点A,不输出A,继续向左访问,第一次访问结点B,不输出B;继续到达D,H;
    到达H,H左子树为空,则返回到H,此时第二次访问H,不输出H;
    H右子树为空,则返回至H,此时第三次到达H,故输出H;
    由H返回至D,第二次到达D,不输出D;
    继续访问至I,I左右子树均为空,故第三次访问I时,输出I;
    返回至D,此时第三次到达D,故输出D;
    按照同样规则继续访问,输出J、E、B、F、G、C,A;
    二叉树的后序遍历输出为:
    HIDJEBFGCA
    后序遍历递归思路:先走左子树,当左子树没有孩子结点时,将此结点的值放入数组中,然后回溯遍历双亲结点的右结点,递归遍历。
    后序遍历非递归遍历思路:先把根结点和左树推入栈,然后取出左树,再推入右树,取出,最后取根结点。
    • 初始化一个栈,将根节点压入栈中,并标记为当前节点(node);
    • 当栈为非空时,执行步骤3,否则执行结束;
    • 如果当前节点(node)有左子树且没有被 touched,则执行4;如果当前结点有右子树,被 touched left 但没有被 touched right 则执行5 否则执行6;
    • 对当前节点(node)标记 touched left,将当前节点的左子树赋值给当前节点(node=node.left) 并将当前节点(node)压入栈中,回到3;
    • 对当前节点(node)标记 touched right,将当前节点的右子树赋值给当前节点(node=node.right) 并将当前节点(node)压入栈中,回到3;
    • 清理当前节点(node)的 touched 标记,弹出栈中的一个节点并访问,然后再将栈顶节点标记为当前节点(item),回到3;
    //递归
    result = [];
    function dfs(node) {
        if(node) {
            dfs(node.left);
            dfs(node.right);
            result.push(node.value);
        }
    }
    //非递归
    function dfs(node) {
        let result = [];
        let stack = [];
        stack.push(node);
        while(stack.length) {
            // 不能用node.touched !== 'left' 标记‘left’做判断,
            // 因为回溯到该结点时,遍历右子树已经完成,该结点标记被更改为‘right’ 若用标记‘left’判断该if语句会一直生效导致死循环
            if(node.left && !node.touched) { // 不要写成if(node.left && node.touched !== 'left')
                // 遍历结点左子树时,对该结点做 ‘left’标记;为了子结点回溯到该(双亲)结点时,便不再访问左子树
                node.touched = 'left';
                node = node.left;
                stack.push(node);
                continue;
            }
            if(node.right && node.touched !== 'right') { // 右子树同上
                node.touched = 'right';
                node = node.right;
                stack.push(node);
                continue;
            }
            node = stack.pop(); // 该结点无左右子树时,从栈中取出一个结点,访问(并清理标记)
            node.touched && delete node.touched; // 可以不清理不影响结果 只是第二次对同一颗树再执行该后序遍历方法时,结果就会出错啦因为你对这棵树做的标记还留在这棵树上
            result.push(node.value);
            node = stack.length ? stack[stack.length - 1] : null;
            //node = stack.pop(); 这时当前结点不再从栈中取(弹出),而是不改变栈数据直接访问栈中最后一个结点
            //如果这时当前结点去栈中取(弹出)会导致回溯时当该结点左右子树都被标记过时 当前结点又变成从栈中取会漏掉对结点的访问(存入结果数组中)
        }
        return result; // 返回值
    }
    /*
         * 1、有儿子,入栈 
         * 2、无儿子,输出自己 
         * 3、儿子被输出过,输出自己
         */
        public void postIterate(TreeNode<String> root) {
            Stack<TreeNode<String>> stack = new Stack<TreeNode<String>>();
            TreeNode<String> now = root;
            TreeNode<String> pre = null;
            stack.push(now);
            while (stack.size() > 0) {
                now = stack.peek();
                if (now.getLeft() == null && now.getRight() == null || pre != null
                        && (now.getLeft() == pre || now.getRight() == pre)) {
                    System.out.print(now.getData() + " ");
                    pre = now;
                    stack.pop();
                } else {
                    if (now.getRight() != null) {
                        stack.push(now.getRight());
                    }
                    if (now.getLeft() != null) {
                        stack.push(now.getLeft());
                    }
                }
            }
        }

    4. 层序遍历

     层次遍历就是按照树的层次自上而下的遍历二叉树。

    二叉树的层次遍历结果为:
    ABCDEFGHIJ

    //递归
    let result = [];
    let stack = [tree]; // 先将要遍历的树压入栈
    let count = 0; // 用来记录执行到第一层
    let bfs = function () {
        let node = stack[count];
        if(node) {
            result.push(node.value);
            if(node.left) stack.push(node.left);
            if(node.right) stack.push(node.right);
            count++;
            bfs();
        }
    }
    //非递归
    function bfs(node) {
        let result = [];
        let queue = [];
        queue.push(node);
        let pointer = 0;
        while(pointer < queue.length) {
            let node = queue[pointer++]; // // 这里不使用 shift 方法(复杂度高),用一个指针代替
            result.push(node.value);
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
        return result;
    }
    public class Solution {
        public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
            ArrayList<Integer> lists=new ArrayList<Integer>();
            if(root==null)
                return lists;
            Queue<TreeNode> queue=new LinkedList<TreeNode>();
            queue.offer(root);
            while(!queue.isEmpty()){
                TreeNode tree=queue.poll();
                if(tree.left!=null)
                    queue.offer(tree.left);
                if(tree.right!=null)
                    queue.offer(tree.right);
                lists.add(tree.val);
            }
            return lists;
        }
    }
    //按照每一层输出  [[3],[9,20],[15,7]]
    public List<List<Integer>> levelOrder(TreeNode root) {
            List<List<Integer>> lists=new ArrayList();
            if(root==null)
                return lists;
            levelOrderMethod(root,lists,0);
            return lists;
        }
        
        public void levelOrderMethod(TreeNode root,List<List<Integer>> lists,int level) {
            List<Integer> list=new ArrayList<Integer>();
            if (lists.size() == level)
                lists.add(list);
            lists.get(level).add(root.val);
    
            if(root.left!=null)
                levelOrderMethod(root.left,lists,level+1);
            if(root.right!=null)
                levelOrderMethod(root.right,lists,level+1);
        }

    注:已知前序遍历序列和后序遍历序列,不可以唯一确定一棵二叉树。

    本部分参考链接:https://www.jianshu.com/p/5e9ea25a1aae

    七、单链表反转

    1. Java实现

    public static class Node {
        public int value;
        public Node next;
    
        public Node(int data) {
            this.value = data;
        }
    }
    //递归实现
    public Node reverse(Node head) {
        if (head == null || head.next == null)
            return head;
        Node temp = head.next;
        Node newHead = reverse(head.next);
        temp.next = head;
        head.next = null;
        return newHead;
    }
    //遍历实现
    public static Node reverseList(Node node) {
      Node pre = null;
      Node next = null;
      while (node != null) {
          next = node.next;
          node.next = pre;
          pre = node;
          node = next;
      }
      return pre;
    }

    递归过程:

    • 程序到达Node newHead = reverse(head.next);时进入递归
    • 我们假设此时递归到了3结点,此时head=3结点,temp=3结点.next(实际上是4结点)
    • 执行Node newHead = reverse(head.next);传入的head.next是4结点,返回的newHead是4结点。
    • 接下来就是弹栈过程了
      • 程序继续执行 temp.next = head就相当于4->3
      • head.next = null 即把3结点指向4结点的指针断掉。
      • 返回新链表的头结点newHead。

      • 注意:当retuen后,系统会恢复2结点压栈时的现场,此时的head=2结点;temp=2结点.next(3结点),再进行上述的操作。最后完成整个链表的翻转。 

    遍历过程:

    • 准备两个空结点 pre用来保存先前结点、next用来做临时变量
    • 在头结点node遍历的时候此时为1结点
      • next = 1结点.next(2结点)
      • 1结点.next=pre(null)
      • pre = 1结点
      • node = 2结点
    • 进行下一次循环node=2结点
      • next = 2结点.next(3结点)
      • 2结点.next=pre(1结点)=>即完成2->1
      • pre = 2结点
      • node = 3结点
    • 进行循环...

    本部分摘自:https://www.cnblogs.com/keeya/p/9218352.html

    2. js实现

    构造链表

    //节点构造函数
    function Node(val){
        this.val = val
        this.next = null
    }
    //定义链表
    function List(array){
        this.head = null
        let i = 0,temp = null
        while(i < array.length){
            if( i === 0){
                this.head = new Node(array[i])
                temp = this.head
            }else{
                let newNode = new Node(array[i])
                temp.next = newNode
                temp = temp.next
            }
            i++
        }
    }
    //遍历链表
    function traverseList(listHead){
        while(listHead){
            console.log(listHead.val)    
            listHead = listHead.next
        }
    }

    反转

    var reverseList = function (head) {
        let pre = null
        while (head) {
            next = head.next
            head.next = pre
            pre = head
            head = next
        }
        return pre
    };

    八、取1000个数字里面的质数

    function getNum(min,max) {
        //求范围内的所有质数
        var array=new Array();
        //判断是否是质数
        for(var i=min; i=max;i++){
            var isPrime=true;
            for(var j=2;j<i;j++){
                //被2或其他小于它的数字整除就不是质数
                if(i%j==0){
                    isPrime=false;
                    break;
                }
            }
        if (isPrime){
            //true代表是质数
            //向数组中添加这个数字
            array.push(i);
        }
       }
    return array;
    }        

    九、找出数组中和为给定值的两个元素,如:[1, 2, 3, 4, 5]中找出和为6的两个元素。

    1. 暴力法

    直接遍历两次数组,时间复杂度为O(N*N)

    function sameSum(arr, num){
            for (var i =0 ;i<arr.length;i++){
                for (var j = i+1;j<arr.length;j++){
                    if (arr[i]+arr[j]==num){
                        console.log('数组中两个元素和为'+num+'的两个数为:'+arr[i]+'和'+arr[j]);
                        break;
                    }
                    //break;
                    //如果只要输出一组这样的组合就只要在内层循环里break就可以
                }
            }
        }
        var arr =[1,2,3,4];
        sameSum(arr,5);
        //数组中两个元素和为5的两个数为:1和4
        //数组中两个元素和为5的两个数为:2和3

    2. 降低复杂度

    先将整型数组排序,排序之后定义两个指针left和right。left指向已排序数组中的第一个元素,right指向已排序数组中的最后一个元素,将 arr[left]+arr[right]与 给定的元素比较,若前者大,right–;若前者小,left++;若相等,则找到了一对整数之和为指定值的元素。此方法采用了排序,排序的时间复杂度为O(NlogN),排序之后扫描整个数组求和比较的时间复杂度为O(N)。故总的时间复杂度为O(NlogN),空间复杂度为O(1)。

    //先将乱序数组排好序
        function bubbleSort(arr){
            var leng = arr.length;
            for(var i =0 ;i<leng;i++){
                for(var j =0;j<leng-i-1;j++){
                    if(arr[j]>=arr[j+1]){
                        var temp = arr[j];
                        arr[j] = arr[j+1];
                        arr[j+1] = temp;
                    }
                }
            }
            return arr;
        }
        //降低复杂度的方法
        function sameSum(array, num)
        {
            var newArr = bubbleSort(array);
            if(newArr == '' || newArr.length == 0){
                return false;
            }
    
            var left = 0, right = newArr.length -1;
    
            while(left < right){
                if(newArr[left] + newArr[right] > num){
                    right--;
                }
                else if(newArr[left] + newArr[right] < num){
                    left++;
                }
                else{
                   console.log('数组中两个元素和为'+num+'的两个数为:'+newArr[left]+'和'+newArr[right]);
                    left++;
                    right--;
                }
            }
        }
        var arr1 = [2,1,4,3,5];
        sameSum(arr1,6);//1 + 5 = 6  2 + 4 = 6

    十、线性顺序存储结构和链式存储结构有什么区别?以及优缺点

    链表存储结构的内存地址不一定是连续的,但顺序存储结构的内存地址一定是连续的;
    链式存储适用于在较频繁地插入、删除、更新元素时,而顺序存储结构适用于频繁查询时使用。

    顺序存储结构和链式存储结构的优缺点:

    空间上

    顺序比链式节约空间。是因为链式结构每一个节点都有一个指针存储域。

    存储操作上:

    顺序支持随机存取,方便操作

    插入和删除上:

    链式的要比顺序的方便(因为插入的话顺序表也很方便,问题是顺序表的插入要执行更大的空间复杂度,包括一个从表头索引以及索引后的元素后移,而链表是索引后,插入就完成了)
    例如:当你在字典中查询一个字母j的时候,你可以选择两种方式,第一,顺序查询,从第一页依次查找直到查询到j。第二,索引查询,从字典的索引中,直接查出j的页数,直接找页数,或许是比顺序查询最快的。

    十一、图

    1. 概念

    一个图就是一些顶点的集合,这些顶点通过一系列结对(连接)。顶点用圆圈表示,边就是这些圆圈之间的连线。顶点之间通过边连接。

    (1)图是由顶点集合以及顶点间的关系集合组成的一种数据结构。

      Graph = (V,E)  V是顶点的又穷非空集合;E是顶点之间关系的有穷集合,也叫边集合。

    (2)有向图:顶点对<x,y>是有序的;无向图:顶点对<x,y>是无序的。

    (3)无向边:若顶点Vi到Vj之间的边没有方向,则称这条边为无向边,用无序偶对(Vi,Vj)来表示。

      如果图中任意两个顶点时间的边都是无向边,则称该图为无向图

    (4)完全无向图:若有n个顶点的无向图有n(n-1)/2 条边, 则此图为完全无向图。

      完全有向图:有n个顶点的有向图有n(n-1)条边, 则此图为完全有向图。

    (5)树中根节点到任意节点的路径是唯一的,但是图中顶点与顶点之间的路径却不是唯一的。

      路径的长度是路径上的边或弧的数目。

    (6)如果对于图中任意两个顶点都是连通的,则成G是连通图。

    (7)图按照边或弧的多少分稀疏图和稠密图。 如果任意两个顶点之间都存在边叫完全图,有向的叫有向图。

       若无重复的边或顶点到自身的边则叫简单图。

    (8)图中顶点之间有邻接点。无向图顶点的边数叫做度。有向图顶点分为入度和出度。

    (9)图上的边和弧上带权则称为网。

    (10)有向的连通图称为强连通图。

    2. 存储结构

    邻接列表:在邻接列表实现中,每一个顶点会存储一个从它这里开始的边的列表。比如,如果顶点A 有一条边到B、C和D,那么A的列表中会有3条边

    邻接列表只描述了指向外部的边。A 有一条边到B,但是B没有边到A,所以 A没有出现在B的邻接列表中。查找两个顶点之间的边或者权重会比较费时,因为遍历邻接列表直到找到为止。

    邻接矩阵:在邻接矩阵实现中,由行和列都表示顶点,由两个顶点所决定的矩阵对应元素表示这里两个顶点是否相连、如果相连这个值表示的是相连边的权重。例如,如果从顶点A到顶点B有一条权重为 5.6 的边,那么矩阵中第A行第B列的位置的元素值应该是5.6:

    3. 图的遍历
    深度优先遍历的思想类似于树的先序遍历。其遍历过程可以描述为:从图中某个顶点v出发,访问该顶点,然后依次从v的未被访问的邻接点出发继续深度优先遍历图中的其余顶点,直至图中所有与v有路径相通的顶点都被访问完为止。
    假设给定图G的初始状态是所有顶点均未曾访问过,在G中任选一顶点vi 为初始出发点,则深度优先遍历可定义如下:首先访问出发点,并将其标记为已访问过,然后,依次从vi 出发遍历vi 的每一个邻接点,若vj未曾访问过,则以vj 为新的出发点继续进行深度优先遍历,直至图中所有和vi有路径相通的顶点都被访问到为止。因此,若G是连通图,则从初始出发点开始的遍历过程结束,也就意味着完成了对图G的遍历。
    连通图的深度优先遍历递归算法可描述为:
    (1)访问顶点vi并标记顶点vi为已访问;
    (2)查找顶点v的第一个邻接顶点vj;
    (3)若顶点v的邻接顶点vj存在,则继续执行,否则算法结束;
    (4)若顶点vj尚未被访问,则深度优先遍历递归访问顶点vj;
    (5)查找顶点vi的邻接顶点vj的下一个邻接顶点,转到步骤(3)。
     当寻找顶点vi的邻接顶点vj成功时继续进行,当寻找顶点vi的邻接顶点vj失败时回溯到上一次递归调用的地方继续进行。为了在遍历过程中便于区分顶点是否被访问,需附设访问标志数组visited[ ],其初值为0,一旦某个顶点被访问,则其相应的分量置为1。
    广度优先遍历算法是一个分层遍历的过程,和树的层序遍历算法类同,是从图的某一顶点V0出发,访问此顶点后,依次访问V0的各个未曾访问过的邻接点;然后分别从这些邻接点出发,直至图中所有已被访问的顶点的邻接点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个未被访问的顶点作起点,重复上述过程,直至图中所有顶点都被访问为止。
    图的广度优先遍历算法需要一个队列以保持访问过的顶点的顺序,以便按顺序访问这些顶点邻接顶点。
    连通图的广度优先遍历算法为:
    (1)访问初始顶点v并标记顶点v为已访问;
    (2)顶点v入队列;
    (3)当队列非空时则继续执行,否则算法结束;
    (4)出队列取得队头顶点u;
    (5)查找顶点u的第一个邻接顶点w;
    (6)若顶点u的邻接顶点w不存在,则转到步骤(3),否则执行后序语句:
    • 若顶点w尚未被访问,则访问顶点w并标记顶点w为已访问;
    • 顶点w入队列;
    • 查找顶点u的邻接顶点w后的下一个邻接顶点,转到步骤(6)。
    import java.util.*;
    
    /**
     * 使用邻接矩阵实现图<p>
     * 深度优先遍历与广度优先遍历<p>
     * 求最短路径:<p>
     *      1. Dijkstra 算法 <p>
     *      2. Ford 算法 <p>
     *      3. 通用型的纠正标记算法<p>
     * Created by Henvealf on 16-5-22.
     */
    public class Graph<T> {
        private int[][] racs;       //邻接矩阵
        private T[] verticeInfo;   //各个点所携带的信息.
    
        private int verticeNum;             //节点的数目,
        private int[] visitedCount;         //记录访问
        private int[] currDist;             //最短路径算法中用来记录每个节点的当前路径长度.
    
        public Graph(int[][] racs, T[] verticeInfo){
            if(racs.length != racs[0].length){
                throw new IllegalArgumentException("racs is not a adjacency matrix!");
            }
            if(racs.length != verticeInfo.length ){
                throw new IllegalArgumentException ("Argument of 2 verticeInfo's length is error!");
            }
            this.racs = racs;
            this.verticeInfo = verticeInfo;
            verticeNum = racs.length;
            visitedCount = new int[verticeNum];
        }
    
        /**
         * 深度遍历的递归
         * @param begin  从第几个节点开始遍历
         */
        public void DFS(int begin, Queue<T> edges){
            visitedCount[begin] = 1;                         //标记begin为已访问
            edges.offer(verticeInfo[begin]);                 //加入记录队列
            for(int a = 0; a < verticeNum; a++){             //遍历相邻的点
                if((racs[begin][a] != Integer.MAX_VALUE)&& visitedCount[a] == 0){   //相邻的点未被访问过
                    DFS(a,edges);
                }
            }
        }
    
        /**
         * 开始深度优先遍历
         * @return  返回保持有遍历之后的顺序的队列
         */
        public Queue<T> depthFirstSearch(){
            initVisitedCount();         //将记录访问次序的数组初始化为0
            Queue<T> edges = new LinkedList<>();    //用于存储遍历过的点,用于输出
            int begin = -1;
            while((begin = findNotVisited()) != -1){        //不等于-1说明还有未访问过的点
                DFS(begin,edges);
            }
            return edges;
        }
    
        /**
         * 广度优先遍历
         * @return  返回保持有遍历之后的顺序的队列
         */
        public Queue<T> breadthFirstSearch(){
            initVisitedCount();                          //将记录访问次序的数组初始化为0
            Queue<Integer> tallyQueue = new LinkedList<>();             //初始化队列
            Queue<T> edges = new LinkedList<>();    //用于存储遍历过的点,用于输出
            int nowVertice = -1;                         //当前所在的点
            while((nowVertice = findNotVisited()) != -1){   //寻找还未被访过问的点
                visitedCount[nowVertice] = 1;             //设置访问标记
                edges.offer(verticeInfo[nowVertice]);
                tallyQueue.offer(nowVertice);               //将当前孤立部分一个顶点加入记录队列中
                while(!tallyQueue.isEmpty()){                           //只要队列不为空
                    nowVertice = tallyQueue.poll();                     //取出队首的节点
                    for(int a = 0; a < verticeNum; a++){     //遍历所有和nowVertice相邻的节点
                        if((racs[nowVertice][a] != Integer.MAX_VALUE) && visitedCount[a] == 0) {                      //没有访问过
                            visitedCount[a] = 1;                        //记为标记过
                            tallyQueue.offer(a);                        //加入队列,上面会继续取出.来遍历
                            edges.offer(verticeInfo[a]);                      //记录
                        }
                    }
                }
            }
            return edges;
        }
    
        /**
         * 寻找没有被访问过的顶点.
         * @return > 0 即为还未被访问过的顶点.   -1 说明所有的节点都被访问过了.
         */
        private int findNotVisited(){
            for(int i = 0; i < verticeNum; i ++){
                if(visitedCount[i] == 0){
                    return i;
                }
            }
            return -1;
        }
    
        /**
         * 将记录访问的数组初始化为0
         */
        private void initVisitedCount(){
            for(int i = 0; i < visitedCount.length; i ++){
                visitedCount[i] = 0;
            }
        }
    }

    后续遇到问题会继续补充······

  • 相关阅读:
    Java基础之线程最详细最精简的介绍
    Android基础之Android硬件
    Iphone客户端程序员半年工作总结
    Java基础之synchronized的讲解
    物联网能否落地?可裁剪嵌入式OS成关键
    java基础之Thread与Runnable的区别
    Android基础之小问题集锦
    Java基础之字符串匹配大全
    BPM 是与非 什么是BPM,如何辨别是否BPM产品,以及如何选择BPM产品
    Hello China操作系统的安装和使用
  • 原文地址:https://www.cnblogs.com/lxy-starry/p/11239333.html
Copyright © 2020-2023  润新知