数据结构与算法

 

 

查找和排序算法是算法的入门知识,其经典思想可以用于很多算法当中。因为其实现代码较短,应用较常见。所以在面试中经常会问到排序算法及其相关的问题。但万变不离其宗,只要熟悉了思想,灵活运用也不是难事。一般在面试中最常考的是快速排序和归并排序,并且经常有面试官要求现场写出这两种排序的代码。对这两种排序的代码一定要信手拈来才行。还有插入排序、冒泡排序、堆排序、基数排序、桶排序等。

面试官对于这些排序可能会要求比较各自的优劣、各种算法的思想及其使用场景。还有要会分析算法的时间和空间复杂度。通常查找和排序算法的考察是面试的开始,如果这些问题回答不好,估计面试官都没有继续面试下去的兴趣都没了。所以想开个好头就要把常见的排序算法思想及其特点要熟练掌握,有必要时要熟练写出代码。

接下来我们就分析一下常见的排序算法及其使用场景。限于篇幅,某些算法的详细演示和图示请自行寻找详细的参考。

冒泡排序

冒泡排序是最简单的排序之一了,其大体思想就是通过与相邻元素的比较和交换来把小的数交换到最前面。这个过程类似于水泡向上升一样,因此而得名。举个栗子,对5,3,8,6,4这个无序序列进行冒泡排序。首先从后向前冒泡,4和6比较,把4交换到前面,序列变成5,3,8,4,6。同理4和8交换,变成5,3,4,8,6,3和4无需交换。5和3交换,变成3,5,4,8,6,3.这样一次冒泡就完了,把最小的数3排到最前面了。对剩下的序列依次冒泡就会得到一个有序序列。冒泡排序的时间复杂度为O(n^2)。

实现代码:

/**
 *@Description:冒泡排序算法实现
 *@author 王旭
 */
public class BubbleSort {
 
    public static void bubbleSort(int[] arr) {
        if(arr == null || arr.length == 0)
            return ;
        for(int i=0; i) {
            for(int j=arr.length-1; j>i; j--) {
                if(arr[j]<arr[j-1]) {
                    swap(arr, j-1, j);
                }
            }
        }
    }
 
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20
·         21
·         22
·         23
·         24

抑或简单理解一点的正向排序

public class BubbleSort {
 
    public static void bubbleSort(int[] arr) {
        if(arr == null || arr.length == 0)
            return ;
        for(int i=1;i<arr.length-1;i++) {
            for(int j=0; j>arr.length-i; j++) {
                if(arr[j]>arr[j+1]) {
                    swap(arr, j+1, j);
                }
            }
        }
    }
 
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20

选择排序

选择排序的思想其实和冒泡排序有点类似,都是在一次排序后把最小的元素放到最前面。但是过程不同,冒泡排序是通过相邻的比较和交换。而选择排序是通过对整体的选择。举个栗子,对5,3,8,6,4这个无序序列进行简单选择排序,首先要选择5以外的最小数来和5交换,也就是选择3和5交换,一次排序后就变成了3,5,8,6,4.对剩下的序列一次进行选择和交换,最终就会得到一个有序序列。其实选择排序可以看成冒泡排序的优化,因为其目的相同,只是选择排序只有在确定了最小数的前提下才进行交换,大大减少了交换的次数。选择排序的时间复杂度为O(n^2)。

实现代码:

/**
 *@Description:简单选择排序算法的实现
 *@author 王旭
 */
public class SelectSort {
 
    public static void selectSort(int[] arr) {
        if(arr == null || arr.length == 0)
            return ;
        int minIndex = 0;
        for(int i=0; i//只需要比较n-1次
            minIndex = i;
            for(int j=i+1; j//从i+1开始比较,因为minIndex默认为i了,i就没必要比了。
                if(arr[j]  arr[minIndex]) {
                    minIndex = j;
                }
            }
 
            if(minIndex != i) { //如果minIndex不为i,说明找到了更小的值,交换之。
                swap(arr, i, minIndex);
            }
        }
 
    }
 
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
 
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20
·         21
·         22
·         23
·         24
·         25
·         26
·         27
·         28
·         29
·         30
·         31
·         32

插入排序

插入排序不是通过交换位置而是通过比较找到合适的位置插入元素来达到排序的目的的。相信大家都有过打扑克牌的经历,特别是牌数较大的。在分牌时可能要整理自己的牌,牌多的时候怎么整理呢?就是拿到一张牌,找到一个合适的位置插入。这个原理其实和插入排序是一样的。举个栗子,对5,3,8,6,4这个无序序列进行简单插入排序,首先假设第一个数的位置时正确的,想一下在拿到第一张牌的时候,没必要整理。然后3要插到5前面,把5后移一位,变成3,5,8,6,4.想一下整理牌的时候应该也是这样吧。然后8不用动,6插在8前面,8后移一位,4插在5前面,从5开始都向后移一位。注意在插入一个数的时候要保证这个数前面的数已经有序。简单插入排序的时间复杂度也是O(n^2)。

实现代码:

/**
 *@Description:简单插入排序算法实现
 *@author 王旭
 */
public class InsertSort {
 
    public static void insertSort(int[] arr) {
        if(arr == null || arr.length == 0)
            return ;
 
        for(int i=1; i//假设第一个数位置时正确的;要往后移,必须要假设第一个。
 
          int j = i;
            int target = arr[i]; //待插入的
 
            //后移
            while(j > 0 & target ]) {
                arr[j] = arr[j-1];
                j --;
            }
 
            //插入 
            arr[j] = target;
        }
 
    }
 
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20
·         21
·         22
·         23
·         24
·         25
·         26
·         27
·         28

快速排序

快速排序一听名字就觉得很高端,在实际应用当中快速排序确实也是表现最好的排序算法。快速排序虽然高端,但其实其思想是来自冒泡排序,冒泡排序是通过相邻元素的比较和交换把最小的冒泡到最顶端,而快速排序是比较和交换小数和大数,这样一来不仅把小数冒泡到上面同时也把大数沉到下面。

举个栗子:对5,3,8,6,4这个无序序列进行快速排序,思路是右指针找比基准数小的,左指针找比基准数大的,交换之。

5,3,8,6,4 用5作为比较的基准,最终会把5小的移动到5的左边,比5大的移动到5的右边。

5,3,8,6,4 首先设置i,j两个指针分别指向两端,j指针先扫描(思考一下为什么?)4比5小停止。然后i扫描,8比5大停止。交换i,j位置。

5,3,4,6,8 然后j指针再扫描,这时j扫描4时两指针相遇。停止。然后交换4和基准数。

4,3,5,6,8 一次划分后达到了左边比5小,右边比5大的目的。之后对左右子序列递归排序,最终得到有序序列。

上面留下来了一个问题为什么一定要j指针先动呢?首先这也不是绝对的,这取决于基准数的位置,因为在最后两个指针相遇的时候,要交换基准数到相遇的位置。一般选取第一个数作为基准数,那么就是在左边,所以最后相遇的数要和基准数交换,那么相遇的数一定要比基准数小。所以j指针先移动才能先找到比基准数小的数。

快速排序是不稳定的,其时间平均时间复杂度是O(nlgn)。

实现代码:

/**
 *@Description:实现快速排序算法
 *@author 王旭
 */
public class QuickSort {
    //一次划分
    public static int partition(int[] arr, int left, int right) {
        int pivotKey = arr[left];
        int pivotPointer = left;
 
        while(left  right) {
            while(left = pivotKey)
                right --;
            while(left  pivotKey)
                left ++;
            swap(arr, left, right); //把大的交换到右边,把小的交换到左边。
        }
        swap(arr, pivotPointer, left); //最后把pivot交换到中间
        return left;
    }
 
    public static void quickSort(int[] arr, int left, int right) {
        if(left >= right)
            return ;
        int pivotPos = partition(arr, left, right);
        quickSort(arr, left, pivotPos-1);
        quickSort(arr, pivotPos+1, right);
    }
 
    public static void sort(int[] arr) {
        if(arr == null || arr.length == 0)
            return ;
        quickSort(arr, 0, arr.length-1);
    }
 
    public static void swap(int[] arr, int left, int right) {
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }
 
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20
·         21
·         22
·         23
·         24
·         25
·         26
·         27
·         28
·         29
·         30
·         31
·         32
·         33
·         34
·         35
·         36
·         37
·         38
·         39
·         40
·         41
·         42

其实上面的代码还可以再优化,上面代码中基准数已经在pivotKey中保存了,所以不需要每次交换都设置一个temp变量,在交换左右指针的时候只需要先后覆盖就可以了。这样既能减少空间的使用还能降低赋值运算的次数。优化代码如下:

/**
 *@Description:实现快速排序算法
 *@author 王旭
 */
public class QuickSort {
 
    /**
     * 划分
     * @param arr
     * @param left
     * @param right
     * @return
     */
    public static int partition(int[] arr, int left, int right) {
        int pivotKey = arr[left];
 
        while(left  right) {
            while(left = pivotKey)
                right --;
            arr[left] = arr[right]; //把小的移动到左边
            while(left  pivotKey)
                left ++;
            arr[right] = arr[left]; //把大的移动到右边
        }
        arr[left] = pivotKey; //最后把pivot赋值到中间
        return left;
    }
 
    /**
     * 递归划分子序列
     * @param arr
     * @param left
     * @param right
     */
    public static void quickSort(int[] arr, int left, int right) {
        if(left >= right)
            return ;
        int pivotPos = partition(arr, left, right);
        quickSort(arr, left, pivotPos-1);
        quickSort(arr, pivotPos+1, right);
    }
 
    public static void sort(int[] arr) {
        if(arr == null || arr.length == 0)
            return ;
        quickSort(arr, 0, arr.length-1);
    }
 
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20
·         21
·         22
·         23
·         24
·         25
·         26
·         27
·         28
·         29
·         30
·         31
·         32
·         33
·         34
·         35
·         36
·         37
·         38
·         39
·         40
·         41
·         42
·         43
·         44
·         45
·         46
·         47
·         48
·         49

总结快速排序的思想:冒泡+二分+递归分治,慢慢体会。。。

堆排序

堆排序是借助堆来实现的选择排序,思想同简单的选择排序,以下以大顶堆为例。注意:如果想升序排序就使用大顶堆,反之使用小顶堆。原因是堆顶元素需要交换到序列尾部。

首先,实现堆排序需要解决两个问题:

如何由一个无序序列键成一个堆?
如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?
·         1
·         2
·         3

第一个问题,可以直接使用线性数组来表示一个堆,由初始的无序序列建成一个堆就需要自底向上从第一个非叶元素开始挨个调整成一个堆。

第二个问题,怎么调整成堆?首先是将堆顶元素和最后一个元素交换。然后比较当前堆顶元素的左右孩子节点,因为除了当前的堆顶元素,左右孩子堆均满足条件,这时需要选择当前堆顶元素与左右孩子节点的较大者(大顶堆)交换,直至叶子节点。我们称这个自堆顶自叶子的调整成为筛选。

从一个无序序列建堆的过程就是一个反复筛选的过程。若将此序列看成是一个完全二叉树,则最后一个非终端节点是n/2取底个元素,由此筛选即可。举个栗子:

49,38,65,97,76,13,27,49序列的堆排序建初始堆和调整的过程如下:

实现代码:

/**
 *@Description:堆排序算法的实现,以大顶堆为例。
 *@author 王旭
 */
public class HeapSort {
 
    /**
     * 堆筛选,除了start之外,start~end均满足大顶堆的定义。
     * 调整之后start~end称为一个大顶堆。
     * @param arr 待调整数组
     * @param start 起始指针
     * @param end 结束指针
     */
    public static void heapAdjust(int[] arr, int start, int end) {
        int temp = arr[start];
 
        for(int i=2*start+1; i) {
            //左右孩子的节点分别为2*i+1,2*i+2
 
            //选择出左右孩子较小的下标
            if(i ]) {
                i ++; 
            }
            if(temp >= arr[i]) {
                break; //已经为大顶堆,=保持稳定性。
            }
            arr[start] = arr[i]; //将子节点上移
            start = i; //下一轮筛选
        }
 
        arr[start] = temp; //插入正确的位置
    }
 
    public static void heapSort(int[] arr) {
        if(arr == null || arr.length == 0)
            return ;
 
        //建立大顶堆
        for(int i=arr.length/2; i>=0; i--) {
            heapAdjust(arr, i, arr.length-1);
        }
 
        for(int i=arr.length-1; i>=0; i--) {
            swap(arr, 0, i);
            heapAdjust(arr, 0, i-1);
        }
 
    }
 
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
 
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20
·         21
·         22
·         23
·         24
·         25
·         26
·         27
·         28
·         29
·         30
·         31
·         32
·         33
·         34
·         35
·         36
·         37
·         38
·         39
·         40
·         41
·         42
·         43
·         44
·         45
·         46
·         47
·         48
·         49
·         50
·         51
·         52
·         53
·         54
·         55
·         56

希尔排序

希尔排序是插入排序的一种高效率的实现,也叫缩小增量排序。简单的插入排序中,如果待排序列是正序时,时间复杂度是O(n),如果序列是基本有序的,使用直接插入排序效率就非常高。希尔排序就利用了这个特点。基本思想是:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。

举个栗子:

从上述排序过程可见,希尔排序的特点是,子序列的构成不是简单的逐段分割,而是将某个相隔某个增量的记录组成一个子序列。如上面的例子,第一堂排序时的增量为5,第二趟排序的增量为3。由于前两趟的插入排序中记录的关键字是和同一子序列中的前一个记录的关键字进行比较,因此关键字较小的记录就不是一步一步地向前挪动,而是跳跃式地往前移,从而使得进行最后一趟排序时,整个序列已经做到基本有序,只要作记录的少量比较和移动即可。因此希尔排序的效率要比直接插入排序高。

希尔排序的分析是复杂的,时间复杂度是所取增量的函数,这涉及一些数学上的难题。但是在大量实验的基础上推出当n在某个范围内时,时间复杂度可以达到O(n^1.3)。

实现代码:

/**
 *@Description:希尔排序算法实现
 *@author 王旭
 */
public class ShellSort {
 
    /**
     * 希尔排序的一趟插入
     * @param arr 待排数组
     * @param d 增量
     */
    public static void shellInsert(int[] arr, int d) {
        for(int i=d; i) {
            int j = i - d;
            int temp = arr[i];    //记录要插入的数据  
            while (j>=0 & arr[j]>temp) {  //从后向前,找到比其小的数的位置   
                arr[j+d] = arr[j];    //向后挪动  
                j -= d;  
            }  
 
            if (j != i - d)    //存在比其小的数 
                arr[j+d] = temp;
 
        }
    }
 
    public static void shellSort(int[] arr) {
        if(arr == null || arr.length == 0)
            return ;
        int d = arr.length / 2;
        while(d >= 1) {
            shellInsert(arr, d);
            d /= 2;
        }
    }
 
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20
·         21
·         22
·         23
·         24
·         25
·         26
·         27
·         28
·         29
·         30
·         31
·         32
·         33
·         34
·         35
·         36
·         37

归并排序

归并排序是另一种不同的排序方法,因为归并排序使用了递归分治的思想,所以理解起来比较容易。其基本思想是,先递归划分子问题,然后合并结果。把待排序列看成由两个有序的子序列,然后合并两个子序列,然后把子序列看成由两个有序序列。。。。。倒着来看,其实就是先两两合并,然后四四合并。。。最终形成有序序列。空间复杂度为O(n),时间复杂度为O(nlogn)。

举个栗子:

实现代码:

/**
 *@Description:归并排序算法的实现
 *@author 王旭
 */
public class MergeSort {
 
    public static void mergeSort(int[] arr) {
        mSort(arr, 0, arr.length-1);
    }
 
    /**
     * 递归分治
     * @param arr 待排数组
     * @param left 左指针
     * @param right 右指针
     */
    public static void mSort(int[] arr, int left, int right) {
        if(left >= right)
            return ;
        int mid = (left + right) / 2;
 
        mSort(arr, left, mid); //递归排序左边
        mSort(arr, mid+1, right); //递归排序右边
        merge(arr, left, mid, right); //合并
    }
 
    /**
     * 合并两个有序数组
     * @param arr 待合并数组
     * @param left 左指针
     * @param mid 中间指针
     * @param right 右指针
     */
    public static void merge(int[] arr, int left, int mid, int right) {
        //[left, mid] [mid+1, right]
        int[] temp = new int[right - left + 1]; //中间数组
 
        int i = left;
        int j = mid + 1;
        int k = 0;
        while(i  right) {
            if(arr[i]  arr[j]) {
                temp[k++] = arr[i++];
            }
            else {
                temp[k++] = arr[j++];
            }
        }
 
        while(i  mid) {
            temp[k++] = arr[i++];
        }
 
        while(j  right) {
            temp[k++] = arr[j++];
        }
 
        for(int p=0; p) {
            arr[left + p] = temp[p];
        }
 
    }
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20
·         21
·         22
·         23
·         24
·         25
·         26
·         27
·         28
·         29
·         30
·         31
·         32
·         33
·         34
·         35
·         36
·         37
·         38
·         39
·         40
·         41
·         42
·         43
·         44
·         45
·         46
·         47
·         48
·         49
·         50
·         51
·         52
·         53
·         54
·         55
·         56
·         57
·         58
·         59
·         60
·         61
·         62
·         63

计数排序

如果在面试中有面试官要求你写一个O(n)时间复杂度的排序算法,你千万不要立刻说:这不可能!虽然前面基于比较的排序的下限是O(nlogn)。但是确实也有线性时间复杂度的排序,只不过有前提条件,就是待排序的数要满足一定的范围的整数,而且计数排序需要比较多的辅助空间。其基本思想是,用待排序的数作为计数数组的下标,统计每个数字的个数。然后依次输出即可得到有序序列。

实现代码:

/**
 *@Description:计数排序算法实现
 *@author 王旭
 */
public class CountSort {
 
    public static void countSort(int[] arr) {
        if(arr == null || arr.length == 0)
            return ;
 
        int max = max(arr);
 
        int[] count = new int[max+1];
        Arrays.fill(count, 0);
 
        for(int i=0; i) {
            count[arr[i]] ++;
        }
 
        int k = 0;
        for(int i=0; i) {
            for(int j=0; j) {
                arr[k++] = i;
            }
        }
 
    }
 
    public static int max(int[] arr) {
        int max = Integer.MIN_VALUE;
        for(int ele : arr) {
            if(ele > max)
                max = ele;
        }
 
        return max;
    }
 
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20
·         21
·         22
·         23
·         24
·         25
·         26
·         27
·         28
·         29
·         30
·         31
·         32
·         33
·         34
·         35
·         36
·         37
·         38
·         39

桶排序

桶排序算是计数排序的一种改进和推广,但是网上有许多资料把计数排序和桶排序混为一谈。其实桶排序要比计数排序复杂许多。

对桶排序的分析和解释借鉴这位兄弟的文章(有改动):http://hxraid.iteye.com/blog/647759

桶排序的基本思想:

假设有一组长度为N的待排关键字序列K[1….n]。首先将这个序列划分成M个的子区间(桶) 。然后基于某种映射函数,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) ,那么该关键字k就作为B[i]中的元素(每个桶B[i]都是一组大小为N/M的序列)。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排)。然后依次枚举输出B[0]….B[M]中的全部内容即是一个有序序列。bindex=f(key) 其中,bindex 为桶数组B的下标(即第bindex个桶), k为待排序列的关键字。桶排序之所以能够高效,其关键在于这个映射函数,它必须做到:如果关键字k1

举个栗子:

假如待排序列K= {49、 38 、 35、 97 、 76、 73 、 27、 49 }。这些数据全部在1—100之间。因此我们定制10个桶,然后确定映射函数f(k)=k/10。则第一个关键字49将定位到第4个桶中(49/10=4)。依次将所有关键字全部堆入桶中,并在每个非空的桶中进行快速排序后得到如图所示。只要顺序输出每个B[i]中的数据就可以得到有序序列了。

桶排序分析:

桶排序利用函数的映射关系,减少了几乎所有的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,希尔排序中的子序列,归并排序中的子问题,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做先进的比较排序即可。

对N个关键字进行桶排序的时间复杂度分为两个部分:

(1) 循环计算每个关键字的桶映射函数,这个时间复杂度是O(N)。

(2) 利用先进的比较排序算法对每个桶内的所有数据进行排序,其时间复杂度为 ∑ O(Ni*logNi) 。其中Ni 为第i个桶的数据量。

很显然,第(2)部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN)了)。因此,我们需要尽量做到下面两点:

(1) 映射函数f(k)能够将N个数据平均的分配到M个桶中,这样每个桶就有[N/M]个数据量。

(2) 尽量的增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。

对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:

O(N)+O(M(N/M)log(N/M))=O(N+N(logN-logM))=O(N+NlogN-N*logM)

当N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)。

总结: 桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。 当然桶排序的空间复杂度 为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。

实现代码:

/**
 *@Description:桶排序算法实现
 *@author 王旭
 */
public class BucketSort {
 
    public static void bucketSort(int[] arr) {
        if(arr == null & arr.length == 0)
            return ;
 
        int bucketNums = 10; //这里默认为10,规定待排数[0,100)
        List> buckets = new ArrayList>(); //桶的索引
 
        for(int i=0; i) {
            buckets.add(new LinkedList()); //用链表比较合适
        }
 
        //划分桶
        for(int i=0; i) {
            buckets.get(f(arr[i])).add(arr[i]);
        }
 
        //对每个桶进行排序
        for(int i=0; i) {
            if(!buckets.get(i).isEmpty()) {
                Collections.sort(buckets.get(i)); //对每个桶进行快排
            }
        }
 
        //还原排好序的数组
        int k = 0;
        for(List bucket : buckets) {
            for(int ele : bucket) {
                arr[k++] = ele;
            }
        }
    }
 
    /**
     * 映射函数
     * @param x
     * @return
     */
    public static int f(int x) {
        return x / 10;
    }
 
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20
·         21
·         22
·         23
·         24
·         25
·         26
·         27
·         28
·         29
·         30
·         31
·         32
·         33
·         34
·         35
·         36
·         37
·         38
·         39
·         40
·         41
·         42
·         43
·         44
·         45
·         46
·         47
·         48

基数排序

基数排序又是一种和前面排序方式不同的排序方式,基数排序不需要进行记录关键字之间的比较。基数排序是一种借助多关键字排序思想对单逻辑关键字进行排序的方法。所谓的多关键字排序就是有多个优先级不同的关键字。比如说成绩的排序,如果两个人总分相同,则语文高的排在前面,语文成绩也相同则数学高的排在前面。。。如果对数字进行排序,那么个位、十位、百位就是不同优先级的关键字,如果要进行升序排序,那么个位、十位、百位优先级一次增加。基数排序是通过多次的收分配和收集来实现的,关键字优先级低的先进行分配和收集。

举个栗子:

实现代码:

/**
 *@Description:基数排序算法实现
 *@author 王旭
 */
public class RadixSort {
 
    public static void radixSort(int[] arr) {
        if(arr == null & arr.length == 0)
            return ;
 
        int maxBit = getMaxBit(arr);
 
        for(int i=1; i) {
 
            List> buf = distribute(arr, i); //分配
            collecte(arr, buf); //收集
        }
 
    }
 
    /**
     * 分配
     * @param arr 待分配数组
     * @param iBit 要分配第几位
     * @return
     */
    public static List> distribute(int[] arr, int iBit) {
        List> buf = new ArrayList>();
        for(int j=0; j) {
            buf.add(new LinkedList());
        }
        for(int i=0; i) {
            buf.get(getNBit(arr[i], iBit)).add(arr[i]);
        }
        return buf;
    }
 
    /**
     * 收集
     * @param arr 把分配的数据收集到arr中
     * @param buf 
     */
    public static void collecte(int[] arr, List> buf) {
        int k = 0;
        for(List bucket : buf) {
            for(int ele : bucket) {
                arr[k++] = ele;
            }
        }
 
    }
 
    /**
     * 获取最大位数
     * @param x
     * @return
     */
    public static int getMaxBit(int[] arr) {
        int max = Integer.MIN_VALUE;
        for(int ele : arr) {
            int len = (ele+"").length();
            if(len > max)
                max = len;
        }
        return max;
    }
 
    /**
     * 获取x的第n位,如果没有则为0.
     * @param x
     * @param n
     * @return
     */
    public static int getNBit(int x, int n) {
 
        String sx = x + "";
        if(sx.length()  n)
            return 0;
        else
            return sx.charAt(sx.length()-n) - '0';
    }
 
}
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9
·         10
·         11
·         12
·         13
·         14
·         15
·         16
·         17
·         18
·         19
·         20
·         21
·         22
·         23
·         24
·         25
·         26
·         27
·         28
·         29
·         30
·         31
·         32
·         33
·         34
·         35
·         36
·         37
·         38
·         39
·         40
·         41
·         42
·         43
·         44
·         45
·         46
·         47
·         48
·         49
·         50
·         51
·         52
·         53
·         54
·         55
·         56
·         57
·         58
·         59
·         60
·         61
·         62
·         63
·         64
·         65
·         66
·         67
·         68
·         69
·         70
·         71
·         72
·         73
·         74
·         75
·         76
·         77
·         78
·         79
·         80
·         81
·         82
·         83

总结

在前面的介绍和分析中我们提到了冒泡排序、选择排序、插入排序三种简单的排序及其变种快速排序、堆排序、希尔排序三种比较高效的排序。后面我们又分析了基于分治递归思想的归并排序还有计数排序、桶排序、基数排序三种线性排序。我们可以知道排序算法要么简单有效,要么是利用简单排序的特点加以改进,要么是以空间换取时间在特定情况下的高效排序。但是这些排序方法都不是固定不变的,需要结合具体的需求和场景来选择甚至组合使用。才能达到高效稳定的目的。没有最好的排序,只有最适合的排序。

下面就总结一下排序算法的各自的使用场景和适用场合。

从平均时间来看,快速排序是效率最高的,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后者相比较的结果是,在n较大时归并排序使用时间较少,但使用辅助空间较多。
 
上面说的简单排序包括除希尔排序之外的所有冒泡排序、插入排序、简单选择排序。其中直接插入排序最简单,但序列基本有序或者n较小时,直接插入排序是好的方法,因此常将它和其他的排序方法,如快速排序、归并排序等结合在一起使用。
 
基数排序的时间复杂度也可以写成O(d*n)。因此它最使用于n值很大而关键字较小的的序列。若关键字也很大,而序列中大多数记录的最高关键字均不同,则亦可先按最高关键字不同,将序列分成若干小的子序列,而后进行直接插入排序。
 
从方法的稳定性来比较,基数排序是稳定的内排方法,所有时间复杂度为O(n^2)的简单排序也是稳定的。但是快速排序、堆排序、希尔排序等时间性能较好的排序方法都是不稳定的。稳定性需要根据具体需求选择。
上面的算法实现大多数是使用线性存储结构,像插入排序这种算法用链表实现更好,省去了移动元素的时间。具体的存储结构在具体的实现版本中也是不同的。
·         1
·         2
·         3
·         4
·         5
·         6
·         7
·         8
·         9

附:基于比较排序算法时间下限为O(nlogn)的证明:

基于比较排序下限的证明是通过决策树证明的,决策树的高度Ω(nlgn),这样就得出了比较排序的下限。

首先要引入决策树。 首先决策树是一颗二叉树,每个节点表示元素之间一组可能的排序,它予以京进行的比较相一致,比较的结果是树的边。 先来说明一些二叉树的性质,令T是深度为d的二叉树,则T最多有2^片树叶。 具有L片树叶的二叉树的深度至少是logL。 所以,对n个元素排序的决策树必然有n!片树叶(因为n个数有n!种不同的大小关系),所以决策树的深度至少是log(n!),即至少需要log(n!)次比较。 而 log(n!)=logn+log(n-1)+log(n-2)+…+log2+log1>=logn+log(n-1)+log(n-2)+…+log(n/2) >=(n/2)log(n/2) >=(n/2)logn-n/2=O(nlogn) 所以只用到比较的排序算法最低时间复杂度是O(nlogn)。

 

1、线性表

2、线性链表

3、栈

4、队列

5、串

6、数组

7、广义表

8、树和二叉树

二叉树:每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分,其次序不能任意颠倒

二叉树的性质:

  性质1:在二叉树的第 i 层至多有2i-1个结点。

  性质2:深度为k的二叉树至多有2k-1个结点(k>=1)。

  性质3:对任何一颗二叉树T,如果其终端结点数为n0度为2的结点数为n2则n0=n2+1。

设n1为二叉树T中度为1的节点数,因为二叉树中所有结点的度均小于或等于2,所以其结点总数为:n=n0+n1+n2;再看二叉树中的分支数,除了根节点外,其余结点都有一个分支进入,设B为分支数,则n=B+1;由于这些分支是由度为1或2的结点射出的,所以有B=n1+2*n2,于是得n=n1+2n2+1,所以n0=n2+1

满二叉树:一棵深度为k且有2k-1个结点的二叉树。每一层上的结点数都是最大结点数

完全二叉树:如果对满二叉树的结点进行连续编号,约定编号从根结点起,自上而下,自左至右。深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应。

特点:(1)叶子结点只可能在层次最大的两层上出现;(2)对任一结点,若其右分支下的子孙的最大层次为h,则其左分支下的子孙的最大层次必为lh+1。

  性质4:具有n个结点的完全二叉树的深度为log2n+1(2为下标,取log2n最小整数)。

  性质5:如果对一棵有n个结点的完全二叉树(其深度为log2n+1)(取log2n最小整数)的结点按层序编号(从第1层到第log2n+1层,每层从左到右),则对任一结点i(1<=i<=n),有:

    (1)如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲PARENT(i)是结点i/2(取最小整数)。

    (2)如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子LCHILD(i)是结点2i。

    (3)如果2i+1>n,则结点i无右孩子;否则其右孩子RCHILD(i)是结点2i+1。

最优二叉树(赫夫曼树):从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支数目称为路径长度。树的路径长度是从树根到每一个结点的路径长度之和。结点的带权路径长度为从该结点到树根之间的路径长度与结点上权的乘积。树的带权路径长度为树中所有叶子结点的带权路径长度之和。

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

平衡二叉树:又称AVL树。它或者是一棵空树,或者是具有下列性质的二叉树。它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度只差的绝对值不超过1。若将二叉树上结点的平衡因子BF定义为该结点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有结点的平衡因子只可能是-1、0和1。

红黑树:红黑树是一种自平衡排序二叉树,树中每个节点的值,都大于或等于在它的左子树中的所有节点的值,并且小于或等于在它的右子树中的所有节点的值,这确保红黑树运行时可以快速地在树中查找和定位的所需节点。

  • 性质 1:每个节点要么是红色,要么是黑色。
  • 性质 2:根节点永远是黑色的。
  • 性质 3:所有的叶节点都是空节点(即 null),并且是黑色的。
  • 性质 4:每个红色节点的两个子节点都是黑色。(从每个叶子到根的路径上不会有两个连续的红色节点)
  • 性质 5:从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。

Java 中实现的红黑树可能有如图所示结构:

  

备注:本文中所有关于红黑树中的示意图采用白色代表红色。黑色节点还是采用了黑色表示。

根据性质 5:红黑树从根节点到每个叶子节点的路径都包含相同数量的黑色节点,因此从根节点到叶子节点的路径中包含的黑色节点数被称为树的“黑色高度(black-height)”。

性质 4 则保证了从根节点到叶子节点的最长路径的长度不会超过任何其他路径的两倍。假如有一棵黑色高度为 3 的红黑树:从根节点到叶节点的最短路径长度是 2,该路径上全是黑色节点(黑节点 - 黑节点 - 黑节点)。最长路径也只可能为 4,在每个黑色节点之间插入一个红色节点(黑节点 - 红节点 - 黑节点 - 红节点 - 黑节点),性质 4 保证绝不可能插入更多的红色节点。由此可见,红黑树中最长路径就是一条红黑交替的路径。

B-树:B-数是一种平衡的多路查找树,一棵m阶B-树,或为空树,或为满足下列特性的m叉树:(m≥3)

(1)根结点只有1个,关键字字数的范围[1,m-1],分支数量范围[2,m];

(2)除根以外的非叶结点,每个结点包含分支数范围[[m/2],m],即关键字字数的范围是[[m/2]-1,m-1],其中[m/2]表示取大于m/2的最小整数;

(3)非叶结点是由叶结点分裂而来的,所以叶结点关键字个数也满足[[m/2]-1,m-1];

(4)所有的非终端结点包含信息:(n,P0,K1,P1,K2,P2,……,Kn,Pn),

其中Ki为关键字,Pi为指向子树根结点的指针,并且Pi-1所指子树中的关键字均小于Ki,而Pi所指的关键字均大于Ki(i=1,2,……,n),n+1表示B-树的阶,n表示关键字个数,即[ceil(m/ 2)-1]<= n <= m-1;

(5)所有叶子结点都在同一层,并且指针域为空,具有如下性质:

  根据B-树定义,第一层为根有一个结点,至少两个分支,第二层至少2个结点,i≥3时,每一层至少有2乘以([m/2])的i-2次方个结点([m/2]表示取大于m/2的最小整数)。若m阶树中共有N个结点,那么可以推导出N必然满足N≥2*(([m/2])的h-1次方)-1 (h≥1),因此若查找成功,则高度h≤1+log[m/2](N+1)/2,h也是磁盘访问次数(h≥1),保证了查找算法的高效率。

B+树:B+树是B-树的变体,也是一种多路搜索树,其定义基本与B-树同,除了:

1)非叶子结点的子树指针与关键字个数相同;

2)非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);

3)为所有叶子结点增加一个链指针;

4)所有关键字都在叶子结点出现;

9、图

各种排序算法的比较:

一、插入排序

  1、直接插入

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

  2、Shell排序

  不稳定,平均O(n3/2),最坏O(n2)。它的基本思想是先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。

代码实现:

package com.yyq;

import java.util.Arrays;

/**

*Created by Administrator on 2015/9/9.

*/

public classInsertSort {

public static voidinsertDirectlySort(inta[]) {

if (a == null) return;

int len = a.length;

try {

for (int i = 0; i < len;i++) {

for (int j = i + 1; j < len && j > 0; j--) {

if (a[j] < a[j -1]) {

int temp = a[j];

a[j] = a[j - 1];

a[j - 1] = temp;

}

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

public static voidshellSort(int data[]){

if (data == null) return;

int j = 0;

int temp = 0;

int len = data.length / 2;

for (int increment  = len; increment > 0; increment /= 2) {

for (int i = increment; i< data.length; i++) {

temp = data[i];

for (j = i; j >=increment; j -= increment) {

if(temp < data[j- increment]){

data[j] = data[j - increment];

}else{

break;

}

}

data[j] = temp;

}

}

}

public static voidTest(int a[],int b[]) {

System.out.println("The SourceSecquence:");

if (a == null) return;

System.out.println(Arrays.toString(a));

insertDirectlySort(a);

System.out.println("InsertDirectlySortResult: ");

System.out.println(Arrays.toString(a));

shellSort(b);

System.out.println("ShellSortResult:");

System.out.println(Arrays.toString(b));

System.out.println();

}

public static void main(String[] args){

int a1[] = null;

int a2[] = {1};

int a3[] = {3, 6, 1, 8, 2, 9, 4};

int a4[] = {1, 3, 5, 7, 9};

int a5[] = {6, 9, 4, 8, -1};

int a6[] = {9, 5, 4, 2, 1};

int b1[] = null;

int b2[] = {1};

int b3[] = {3, 6, 1, 8, 2, 9, 4};

int b4[] = {1, 3, 5, 7, 9};

int b5[] = {6, 9, 4, 8, -1};

int b6[] = {9, 5, 4, 2, 1};

Test(a1,b1);

Test(a2,b2);

Test(a3,b3);

Test(a4,b4);

Test(a5,b5);

Test(a6,b6);

}

}

输出结果:

The Source Secquence:

The Source Secquence:

[1]

InsertDirectlySort Result:

[1]

ShellSort Result:

[1]

The Source Secquence:

[3, 6, 1, 8, 2, 9, 4]

InsertDirectlySort Result:

[1, 2, 3, 4, 6, 8, 9]

ShellSort Result:

[1, 2, 3, 4, 6, 8, 9]

The Source Secquence:

[1, 3, 5, 7, 9]

InsertDirectlySort Result:

[1, 3, 5, 7, 9]

ShellSort Result:

[1, 3, 5, 7, 9]

The Source Secquence:

[6, 9, 4, 8, -1]

InsertDirectlySort Result:

[-1, 4, 6, 8, 9]

ShellSort Result:

[-1, 4, 6, 8, 9]

The Source Secquence:

[9, 5, 4, 2, 1]

InsertDirectlySort Result:

[1, 2, 4, 5, 9]

ShellSort Result:

[1, 2, 4, 5, 9]

二、选择排序

  1、直接选择

  不稳定,平均和最坏都是O(n2)。是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾(目前已被排序的序列)。以此类推,直到所有元素均排序完毕。

  2、堆排序

  不稳定,平均和最坏都是O(nlogn),辅助存储O(1)。利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

package com.yyq;

import java.util.Arrays;

/**

*Created by Administrator on 2015/9/9.

*/

public classSelectSort {

public static voidselectDirectlySort(int[]a) {

if (a == null) return;

int min = 0;

int i = 0;

int j = 0;

int index = 0;

int len = a.length;

for (i = 0; i < len - 1; i++) {

min = a[i];

index = i;

for (j = i + 1; j < len; j++) {

if (a[j] < min) {

min = a[j];

index = j;

}

}

a[index] =a[i];

a[i] = min;

}

}

public static voidheapSort(int[] array){

if (array == null) return;

buildHeap(array);//构建堆

int n = array.length;

int i = 0;

for (i = n - 1; i >= 1; i--) {

swap(array, 0,i);

heapify(array, 0, i);

}

}

//构建

public static void buildHeap(int[] array) {

int n = array.length;//数组中元素的个数

for (int i = n / 2 -1; i >= 0; i--)

heapify(array, i, n);

}

public static voidheapify(int[] A, intidx, int max) {

int left = 2 * idx + 1;// 左孩子的下标(如果存在的话)

int right = 2 * idx + 2;// 左孩子的下标(如果存在的话)

int largest = 0;//寻找3个节点中最大值节点的下标

if (left < max && A[left] > A[idx])

largest =left;

else

largest =idx;

if (right < max && A[right] > A[largest])

largest =right;

if (largest != idx) {

swap(A, largest, idx);

heapify(A, largest, max);

}

}

public static voidswap(int[] array, inti, int j) {

int temp = 0;

temp =array[i];

array[i] =array[j];

array[j] =temp;

}

public static voidTest(int a[], int b[]) {

System.out.println("The Source Secquence:");

if (a == null) return;

System.out.println(Arrays.toString(a));

selectDirectlySort(a);

System.out.println("BubbleSortResult: ");

System.out.println(Arrays.toString(a));

heapSort(b);

System.out.println("QuickSortResult:");

System.out.println(Arrays.toString(b));

System.out.println();

}

public static void main(String[] args) {

int a1[] = null;

int a2[] = {1};

int a3[] = {3, 6, 1, 8, 2, 9, 4};

int a4[] = {1, 3, 5, 7, 9};

int a5[] = {6, 9, 4, 8, -1};

int a6[] = {9, 5, 4, 2, 1};

int b1[] = null;

int b2[] = {1};

int b3[] = {3, 6, 1, 8, 2, 9, 4};

int b4[] = {1, 3, 5, 7, 9};

int b5[] = {6, 9, 4, 8, -1};

int b6[] = {9, 5, 4, 2, 1};

Test(a1, b1);

Test(a2, b2);

Test(a3, b3);

Test(a4, b4);

Test(a5, b5);

Test(a6, b6);

}

}

输出结果:

The Source Secquence:

The Source Secquence:

[1]

BubbleSort Result:

[1]

QuickSort Result:

[1]

The Source Secquence:

[3, 6, 1, 8, 2, 9, 4]

BubbleSort Result:

[1, 2, 3, 4, 6, 8, 9]

QuickSort Result:

[1, 2, 3, 4, 6, 8, 9]

The Source Secquence:

[1, 3, 5, 7, 9]

BubbleSort Result:

[1, 3, 5, 7, 9]

QuickSort Result:

[1, 3, 5, 7, 9]

The Source Secquence:

[6, 9, 4, 8, -1]

BubbleSort Result:

[-1, 4, 6, 8, 9]

QuickSort Result:

[-1, 4, 6, 8, 9]

The Source Secquence:

[9, 5, 4, 2, 1]

BubbleSort Result:

[1, 2, 4, 5, 9]

QuickSort Result:

[1, 2, 4, 5, 9]

三、交换排序

  1、冒泡排序

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

  2、快速排序

  不稳定,平均O(nlogn),最坏O(n2),辅助存储O(logn)。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

package com.yyq;

import java.util.Arrays;

/**

*Created by Administrator on 2015/9/10.

*/

public classChangeSort {

public static voidswap(int[] array, inti, int j){

int temp = array[i];

array[i] =array[j];

array[j] =temp;

}

public static voidbubbleSort(int[]array){

if (array == null) return;

int len = array.length;;

for(int i = 0; i < len-1;i++){

for (int j = len-1; j >i; j--){

if (array[j] <array[j-1] ){

swap(array,j,j-1);

}

}

}

}

public static voidquickSort(int[] array, intlow, int high){

if (array == null || low< 0 || high < 0 || low >= array.length) return;

int pivotloc = partition(array,low, high);

if(low < high){

quickSort(array, low, pivotloc-1);

quickSort(array,pivotloc+1,high);

}

}

public static intpartition(int[] array, intlow, int high){

int pivokey = array[low];

while(low < high){

while(low < high && array[high] >= pivokey)

{

high--;

}

array[low] =array[high];

while(low < high && array[low] <= pivokey)

{

low++;

}

array[high] =array[low];

}

array[low] =pivokey;

return low;

}

public static voidTest(int a[],int b[]) {

System.out.println("The SourceSecquence:");

if (a == null) return;

System.out.println(Arrays.toString(a));

bubbleSort(a);

System.out.println("BubbleSort Result:");

System.out.println(Arrays.toString(a));

quickSort(b, 0, b.length-1);

System.out.println("QuickSortResult:");

System.out.println(Arrays.toString(b));

System.out.println();

}

public static void main(String[] args){

int a1[] = null;

int a2[] = {1};

int a3[] = {3, 6, 1, 8, 2, 9, 4};

int a4[] = {1, 3, 5, 7, 9};

int a5[] = {6, 9, 4, 8, -1};

int a6[] = {9, 5, 4, 2, 1};

int b1[] = null;

int b2[] = {1};

int b3[] = {3, 6, 1, 8, 2, 9, 4};

int b4[] = {1, 3, 5, 7, 9};

int b5[] = {6, 9, 4, 8, -1};

int b6[] = {9, 5, 4, 2, 1};

Test(a1,b1);

Test(a2,b2);

Test(a3,b3);

Test(a4,b4);

Test(a5,b5);

Test(a6,b6);

}

}

输出结果:

The Source Secquence:

The Source Secquence:

[1]

BubbleSort Result:

[1]

QuickSort Result:

[1]

The Source Secquence:

[3, 6, 1, 8, 2, 9, 4]

BubbleSort Result:

[1, 2, 3, 4, 6, 8, 9]

QuickSort Result:

[1, 2, 3, 4, 6, 8, 9]

The Source Secquence:

[1, 3, 5, 7, 9]

BubbleSort Result:

[1, 3, 5, 7, 9]

QuickSort Result:

[1, 3, 5, 7, 9]

The Source Secquence:

[6, 9, 4, 8, -1]

BubbleSort Result:

[-1, 4, 6, 8, 9]

QuickSort Result:

[-1, 4, 6, 8, 9]

The Source Secquence:

[9, 5, 4, 2, 1]

BubbleSort Result:

[1, 2, 4, 5, 9]

QuickSort Result:

[1, 2, 4, 5, 9]

四、归并排序(台湾译作:合并排序):

  稳定,平均和最坏都是O(nlogn),辅助存储O(n)。是建立在归并操作上的一种有效的排序算法。将两个(或两个以上)有序表合并成一个新的有序表 即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

package com.yyq;

import java.util.Arrays;

/**

*Created by Administrator on 2015/9/10.

*/

public classMergingSort {

public static voidTest(int a[]) {

System.out.println("The SourceSecquence:");

if (a == null) return;

System.out.println(Arrays.toString(a));

mergeSort(a,0,a.length-1);

System.out.println("MergeSortResult: ");

System.out.println(Arrays.toString(a));

System.out.println();

}

public static void main(String[] args){

int a1[] = null;

int a2[] = {1};

int a3[] = {3, 6, 1, 8, 2, 9, 4};

int a4[] = {1, 3, 5, 7, 9};

int a5[] = {6, 9, 4, 8, -1};

int a6[] = {9, 5, 4, 2, 1};

Test(a1);

Test(a2);

Test(a3);

Test(a4);

Test(a5);

Test(a6);

}

public static int[]mergeSort(int[] nums, intlow, int high) {

if (nums == null || low< 0 || low > nums.length-1 || high < 0) return nums;

int mid = (low + high) / 2;

if (low < high) {

// 左边

mergeSort(nums, low, mid);

// 右边

mergeSort(nums, mid + 1, high);

// 左右归并

merge(nums, low, mid, high);

}

return nums;

}

public static voidmerge(int[] nums, intlow, int mid, int high) {

int[] temp = new int[high - low + 1];

int i = low;// 左指针

int j = mid + 1;// 右指针

int k = 0;

// 把较小的数先移到新数组中

while (i <= mid && j <= high) {

if (nums[i] < nums[j]) {

temp[k++] = nums[i++];

} else {

temp[k++] = nums[j++];

}

}

// 把左边剩余的数移入数组

while (i <= mid) {

temp[k++] = nums[i++];

}

// 把右边边剩余的数移入数组

while (j <= high) {

temp[k++] = nums[j++];

}

// 把新数组中的数覆盖nums数组

for (int k2 = 0; k2< temp.length; k2++) {

nums[k2 + low] = temp[k2];

}

}

}

输出结果:

The Source Secquence:

The Source Secquence:

[1]

MergeSort Result:

[1]

The Source Secquence:

[3, 6, 1, 8, 2, 9, 4]

MergeSort Result:

[1, 2, 3, 4, 6, 8, 9]

The Source Secquence:

[1, 3, 5, 7, 9]

MergeSort Result:

[1, 3, 5, 7, 9]

The Source Secquence:

[6, 9, 4, 8, -1]

MergeSort Result:

[-1, 4, 6, 8, 9]

The Source Secquence:

[9, 5, 4, 2, 1]

MergeSort Result:

[1, 2, 4, 5, 9]

五、基数排序又称“桶子法”:

  稳定,平均和最坏都是O(d(n+rd)),对于n个记录,每个记录含d个关键字(即位数),每个关键字的取值范围为rd个值。它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用。

package com.yyq;

import java.util.Arrays;

/**

*Created by Administrator on 2015/9/10.

*/

public class RadixSort{

public static voidradixSort(int[] array, intnum, int radix) {

if (array == null) return;

int k = 0;

int n = 1;

int index = 1;

int len = array.length;

//分成nums.length个桶

int[][] radixArray = newint[radix][radix];

//每个桶放的个数组成的数组

int[] tempArray = newint[radix];

for (int i = 0; i <tempArray.length; i++){

tempArray[i] = 0;

}

//还在位数内

while (index <= num) {

for (int i = 0; i < len;i++) {

//个,十,百,千...

int temp = (array[i] / n) % 10;

//存入特定桶的特定位置

radixArray[temp][tempArray[temp]] = array[i];

tempArray[temp] = tempArray[temp] + 1;

}

for (int i = 0; i <radix; i++) {

if (tempArray[i] !=0) {

for (int j = 0; j < tempArray[i]; j++) {

//数组重组

array[k] = radixArray[i][j];

k++;

}

//重置,以防下次循环时数据出错

tempArray[i] = 0;

}

}

//重置,以防下次循环时数据出错

k = 0;

//进位

n *= 10;

index++;

}

}

// 基数排序的实现

public static void Test(int a[]) {

System.out.println("The SourceSecquence:");

if (a == null) return;

System.out.println(Arrays.toString(a));

int num = 0;

int max_num = num;

for (int i = 0; i <a.length; i++){

int temp = a[i];

while(temp != 0){

num++;

temp = temp / 10;

}

if (num > max_num){

max_num = num;

}

num = 0;

}

System.out.println("The largestnumber'length is:"+max_num);

radixSort(a, max_num,10);

System.out.println("RadixSortResult: ");

System.out.println(Arrays.toString(a));

System.out.println();

}

public static void main(String[] args) {

int a1[] = null;

int a2[] = {1};

int a3[] = {3, 6, 1, 8, 2, 9, 4};

int a4[] ={110, 35, 4855, 2726,562599};

int a5[] = {278, 109, 930, 184, 505, 269, 800, 831};

int a6[] = {9, 35, 4, 2, 1};

Test(a1);

Test(a2);

Test(a3);

Test(a4);

Test(a5);

Test(a6);

}

}

输出结果:

The Source Secquence:

The Source Secquence:

[1]

The largest number'length is:1

RadixSort Result:

[1]

The Source Secquence:

[3, 6, 1, 8, 2, 9, 4]

The largest number'length is:1

RadixSort Result:

[1, 2, 3, 4, 6, 8, 9]

The Source Secquence:

[110, 35, 4855, 2726, 562599]

The largest number'length is:6

RadixSort Result:

[35, 110, 2726, 4855, 562599]

The Source Secquence:

[278, 109, 930, 184, 505, 269, 800, 831]

The largest number'length is:3

RadixSort Result:

[109, 184, 269, 278, 505, 800, 831, 930]

The Source Secquence:

[9, 35, 4, 2, 1]

The largest number'length is:2

RadixSort Result:

[1, 2, 4, 9, 35]

1.题目:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

代码:

[java] view plain copy

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.Comparator;
  4. import java.util.Iterator;
  5. public class Solution {
  6. public static String PrintMinNumber(int [] numbers) {
  7. String result = "";
  8. int length=numbers.length;
  9. if(length<1){
  10. return result;
  11. }
  12. ArrayList<Integer> list=new ArrayList<Integer>();
  13. for(int i=0;i<length;i++){
  14. list.add(numbers[i]);
  15. }
  16. Collections.sort(list,new Comparator<Integer>() {
  17. @Override
  18. public int compare(Integer o1, Integer o2) {
  19. String result1=o1+""+o2;
  20. String result2=o2+""+o1;
  21. return result1.compareTo(result2);
  22. }
  23. });
  24. Iterator<Integer> iterator=list.iterator();
  25. while(iterator.hasNext()){
  26. result+=(iterator.next()+"");
  27. }
  28. return result;
  29. }
  30. }

2.题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

代码:

[java] view plain copy

  1. /**
  2. * Definition for binary tree
  3. * public class TreeNode {
  4. *     int val;
  5. *     TreeNode left;
  6. *     TreeNode right;
  7. *     TreeNode(int x) { val = x; }
  8. * }
  9. */
  10. public class Solution {
  11. public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
  12. return DFS(pre,in,0,pre.length-1,0,in.length-1);
  13. }
  14. private TreeNode DFS(int []pre,int []in,int prestart,int preend,int instart,int endstart){
  15. if(prestart>preend||instart>endstart){
  16. return null;
  17. }
  18. TreeNode root=new TreeNode(pre[prestart]);
  19. for(int indexstart=instart;indexstart<=endstart;indexstart++){
  20. if(pre[prestart]==in[indexstart]){
  21. root.left=DFS(pre, in, prestart+1, prestart+indexstart-instart, instart, indexstart-1);
  22. root.right=DFS(pre, in, indexstart-instart+prestart+1, preend, indexstart+1, endstart);
  23. }
  24. }
  25. return root;
  26. }
  27. }

3.题目:给定一颗二叉搜索树,请找出其中的第k大的结点。例如, 5 / \3 7 /\ /\ 2 4 6 8 中,按结点数值大小顺序第三个结点的值为4。

代码:

[java] view plain copy

  1. /*
  2. public class TreeNode {
  3. int val = 0;
  4. TreeNode left = null;
  5. TreeNode right = null;
  6. public TreeNode(int val) {
  7. this.val = val;
  8. }
  9. }
  10. */
  11. import java.util.ArrayList;
  12. import java.util.Arrays;
  13. import java.util.Collections;
  14. import java.util.Comparator;
  15. import java.util.Iterator;
  16. public class Solution {
  17. //思路:二叉搜索树的中序遍历就是按顺序排列的,所以,直接中序查找就可以了
  18. int index=0;
  19. TreeNode KthNode(TreeNode pRoot, int k) {
  20. if(pRoot!=null){
  21. TreeNode left=KthNode(pRoot.left, k);
  22. if(left!=null)
  23. return left;
  24. index++;
  25. if(index==k)
  26. return pRoot;
  27. TreeNode right=KthNode(pRoot.right, k);
  28. if(right!=null)
  29. return right;
  30. }
  31. return null;
  32. }
  33. }

题目描述

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)

代码:

[java]viewplaincopy

  1. public class Solution {
  2. public int FindGreatestSumOfSubArray(int[] array) {
  3. if(array.length==0){
  4. return 0;
  5. }
  6. int sum=array[0];
  7. int Maxsum=array[0];
  8. for(int i=1;i<array.length;i++){
  9. if(sum<0){
  10. sum=0;
  11. }
  12. sum+=array[i];
  13. Maxsum=Math.max(Maxsum, sum);
  14. }
  15. return Maxsum;
  16. }
  17. }

题目描述

在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。

代码:

[java]viewplaincopy

  1. import java.util.HashSet;
  2. import java.util.Iterator;
  3. import java.util.LinkedHashSet;
  4. import java.util.Set;
  5. public class Solution {
  6. // Parameters:
  7. //    numbers:     an array of integers
  8. //    length:      the length of array numbers
  9. //    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
  10. //                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
  11. //    这里要特别注意~返回任意重复的一个,赋值duplication[0]
  12. // Return value:       true if the input is valid, and there are some duplications in the array number
  13. //                     otherwise false
  14. //  private static final int Max=(int) (1e5+10);
  15. private static int []vis;
  16. public static boolean duplicate(int numbers[], int length, int[] duplication) {
  17. if(length<1){
  18. return false;
  19. }
  20. vis=new int [length];
  21. for(int i=0;i<length;i++){
  22. vis[numbers[i]]++;
  23. }
  24. Set<Integer> set=new HashSet<Integer>();
  25. for(int i=0;i<length;i++){
  26. if(vis[numbers[i]]>1){
  27. set.add(numbers[i]);
  28. }
  29. }
  30. Iterator<Integer> iterator=set.iterator();
  31. int cnt=0;
  32. while(iterator.hasNext()){
  33. duplication[cnt++]=iterator.next();
  34. break;
  35. }
  36. //      for(int i=0;i<cnt;i++){
  37. //          System.out.print(duplication[i]+" ");
  38. //      }
  39. if(cnt!=0){
  40. return true;
  41. }
  42. return false;
  43. }
  44. }

题目描述

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“OhMy God!”不是顺子.....LL不高兴了,他想了想,决定大\小王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“SoLucky!”。LL决定去买体育彩票啦。现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何。为了方便起见,你可以认为大小王是0。

代码:

[java]viewplaincopy

  1. import java.util.HashSet;
  2. import java.util.Set;
  3. public class Solution {
  4. //思路:判断不合法的情况:1.numbers长度不为5,2.numbers中除0外,有重复的数,3.最大值减最小值>=5
  5. //剩下的就是合法的情况了
  6. public boolean isContinuous(int[] numbers) {
  7. int length=numbers.length;
  8. if(length!=5){
  9. return false;
  10. }
  11. Set<Integer> hashSet=new HashSet<Integer>();
  12. int ans=0;//0的个数
  13. int Max=-1,Min=100;
  14. for(int i=0;i<length;i++){
  15. if(numbers[i]!=0){
  16. hashSet.add(numbers[i]);
  17. Max=Math.max(Max, numbers[i]);
  18. Min=Math.min(Min, numbers[i]);
  19. }else{
  20. ans++;
  21. }
  22. }
  23. if(ans+hashSet.size()!=length){
  24. return false;
  25. }
  26. if(Max-Min>=5){
  27. return false;
  28. }
  29. return true;
  30. }
  31. }

题目描述

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

代码:

[java]viewplaincopy

  1. public class Solution {
  2. public  String LeftRotateString(String str, int n) {
  3. if(str.length()==0){
  4. return str;
  5. }
  6. n%=(str.length());
  7. if(str.length()<1)
  8. return null;
  9. for(int i=0;i<n;i++)
  10. str=GetString(str);
  11. return str;
  12. }
  13. private  String GetString(String str){
  14. return str.substring(1, str.length())+str.charAt(0);
  15. }
  16. }

题目描述

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

代码:

[java]viewplaincopy

  1. public class Solution {
  2. public static String ReverseSentence(String str) {
  3. String string=str.trim();
  4. String a="";
  5. if(string.equals(a)){
  6. return str;
  7. }
  8. StringBuilder result=new StringBuilder();
  9. String []split=str.split(" ");
  10. for(int i=split.length-1;i>=0;i--){
  11. result.append((split[i]+" "));
  12. }
  13. return result.toString().trim();
  14. }
  15. }

题目描述

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5};针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1},{2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1},{2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1},{2,3,4,2,6,[2,5,1]}。

代码:

[java]viewplaincopy

  1. import java.util.ArrayList;
  2. public class Solution {
  3. public static ArrayList<Integer> maxInWindows(int [] num, int size)
  4. {
  5. ArrayList<Integer> list=new ArrayList<Integer>();
  6. int length=num.length;
  7. if(size<=0){
  8. return list;
  9. }
  10. if(length>=1){
  11. int Max=Integer.MIN_VALUE;
  12. for(int i=0;i<length;i++){
  13. Max=Math.max(Max, num[i]);
  14. }
  15. if(size>length){
  16. return list;
  17. }else{
  18. for(int i=0;i<length-size+1;i++){
  19. int MAX=Integer.MIN_VALUE;
  20. for(int j=i;j<size+i;j++){
  21. MAX=Math.max(MAX, num[j]);
  22. }
  23. list.add(MAX);
  24. }
  25. }
  26. }
  27. return list;
  28. }
  29. }

题目描述

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

代码:

[java]viewplaincopy

  1. public class Solution {
  2. private final static int Max=(int) (1e5+10);
  3. public int LastRemaining_Solution(int n, int m) {
  4. int []array=new int[Max];
  5. int i=-1,count=n,step=0;
  6. while(count>0){//模拟环
  7. i++;
  8. if(i>=n){
  9. i=0;
  10. }
  11. if(array[i]==-1)
  12. continue;
  13. step++;
  14. if(step==m){
  15. step=0;
  16. count--;
  17. array[i]=-1;
  18. }
  19. }
  20. return i;
  21. }
  22. }

题目描述

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

代码:

[java]viewplaincopy

  1. import java.util.ArrayList;
  2. import java.util.LinkedList;
  3. import java.util.Queue;
  4. /*
  5. public class TreeNode {
  6. int val = 0;
  7. TreeNode left = null;
  8. TreeNode right = null;
  9. public TreeNode(int val) {
  10. this.val = val;
  11. }
  12. }
  13. */
  14. public class Solution {
  15. public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
  16. ArrayList<ArrayList<Integer>> list=new ArrayList<ArrayList<Integer>>();
  17. if(pRoot==null){
  18. return list;
  19. }
  20. int ans=1;
  21. Queue<TreeNode> queue=new LinkedList<TreeNode>();
  22. queue.add(pRoot);
  23. while(!queue.isEmpty()){
  24. ArrayList<Integer> nodes=new ArrayList<Integer>();
  25. int size=queue.size();
  26. for(int i=0;i<size;i++){
  27. TreeNode root=queue.poll();
  28. if(ans%2==0){
  29. nodes.add(0,root.val);
  30. }else{
  31. nodes.add(root.val);
  32. }
  33. if(root.left!=null){
  34. queue.add(root.left);
  35. }
  36. if(root.right!=null){
  37. queue.add(root.right);
  38. }
  39. }
  40. list.add(nodes);
  41. ans++;
  42. }
  43. return list;
  44. }
  45. }

题目描述

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

代码:

[java]viewplaincopy

  1. /*
  2. public class TreeNode {
  3. int val = 0;
  4. TreeNode left = null;
  5. TreeNode right = null;
  6. public TreeNode(int val) {
  7. this.val = val;
  8. }
  9. }
  10. */
  11. import java.util.ArrayList;
  12. import java.util.LinkedList;
  13. import java.util.Queue;
  14. public class Solution {
  15. ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
  16. ArrayList<ArrayList<Integer>> list=new ArrayList<ArrayList<Integer>>();
  17. if(pRoot==null){
  18. return list;
  19. }
  20. Queue<TreeNode> queue=new LinkedList<TreeNode>();
  21. queue.add(pRoot);
  22. while(!queue.isEmpty()){
  23. ArrayList<Integer> arrayList=new ArrayList<Integer>();
  24. int size=queue.size();
  25. for(int i=0;i<size;i++){
  26. TreeNode root=queue.poll();
  27. arrayList.add(root.val);
  28. if(root.left!=null){
  29. queue.add(root.left);
  30. }
  31. if(root.right!=null){
  32. queue.add(root.right);
  33. }
  34. }
  35. list.add(arrayList);
  36. }
  37. return list;
  38. }
  39. }

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

代码:

[java]viewplaincopy

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.Comparator;
  4. public class Solution {
  5. private ArrayList<Integer> list=new ArrayList<Integer>();
  6. public void Insert(Integer num) {
  7. list.add(num);
  8. Collections.sort(list,new Comparator<Integer>() {
  9. @Override
  10. public int compare(Integer o1, Integer o2) {
  11. return o1-o2;
  12. }
  13. });
  14. }
  15. public Double GetMedian() {
  16. int length=list.size();
  17. int MID=length>>1;
  18. double mid=0;
  19. if((length&1)==0){
  20. Integer a1=list.get(MID);
  21. Integer a2=list.get(MID-1);
  22. mid=(Double.valueOf(a1+"")+Double.valueOf(a2+""))/2;
  23. }else{
  24. Integer a3=list.get(MID);
  25. mid=Double.valueOf(a3+"");
  26. }
  27. return mid;
  28. }
  29. }

题目描述

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列?Good Luck!

输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

代码:

[java]viewplaincopy

  1. import java.util.ArrayList;
  2. public class Solution {
  3. public static ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
  4. ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>();
  5. if (sum < 0) {
  6. return list;
  7. }
  8. for (int i = 1; i <= sum; i++) {
  9. for (int j = i; j <= sum; j++) {
  10. int n = j - i + 1;
  11. int ans = i*n+(n*(n-1))/2;
  12. if (ans != sum) {
  13. continue;
  14. }
  15. ArrayList<Integer> arrayList = new ArrayList<>();
  16. for (int k = i; k <= j; k++) {
  17. arrayList.add(k);
  18. }
  19. if(arrayList.size()>=2){//至少包括两个数
  20. list.add(arrayList);
  21. }
  22. }
  23. }
  24. return list;
  25. }
  26. }

题目描述

有一副由NxN矩阵表示的图像,这里每个像素用一个int表示,请编写一个算法,在不占用额外内存空间的情况下(即不使用缓存矩阵),将图像顺时针旋转90度。

给定一个NxN的矩阵,和矩阵的阶数N,请返回旋转后的NxN矩阵,保证N小于等于500,图像元素小于等于256。

测试样例:

[[1,2,3],[4,5,6],[7,8,9]],3
返回:[[7,4,1],[8,5,2],[9,6,3]]

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class Transform {
  3. public  int[][] transformImage(int[][] mat, int n) {
  4. int [][]A=new int[n][n];
  5. int x=0,y=n-1;
  6. for(int i=0;i<n;i++){
  7. for(int j=0;j<n;j++){
  8. A[x][y]=mat[i][j];
  9. if(x==n-1){
  10. y--;
  11. x=0;
  12. }else{
  13. x++;
  14. }
  15. }
  16. }
  17. return A;
  18. }
  19. }

题目描述

假定我们都知道非常高效的算法来检查一个单词是否为其他字符串的子串。请将这个算法编写成一个函数,给定两个字符串s1和s2,请编写代码检查s2是否为s1旋转而成,要求只能调用一次检查子串的函数。

给定两个字符串s1,s2,请返回bool值代表s2是否由s1旋转而成。字符串中字符为英文字母和空格,区分大小写,字符串长度小于等于1000。

测试样例:

"Hello world","worldhello "
返回:false
"waterbottle","erbottlewat"
返回:true

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class ReverseEqual {
  3. public boolean checkReverseEqual(String s1, String s2) {
  4. if(s1==null||s2==null||s1.length()!=s2.length()){
  5. return false;
  6. }
  7. return (s1+s1).contains(s2);
  8. }
  9. }

题目描述

输入一个链表,输出该链表中倒数第k个结点。

[java]viewplaincopy

  1. /*
  2. public class ListNode {
  3. int val;
  4. ListNode next = null;
  5. ListNode(int val) {
  6. this.val = val;
  7. }
  8. }*/
  9. import java.util.LinkedHashMap;
  10. public class Solution {
  11. public ListNode FindKthToTail(ListNode head, int k) {
  12. LinkedHashMap<Integer, ListNode> map=new LinkedHashMap<Integer, ListNode>();
  13. int cnt=0;
  14. while(head!=null){
  15. map.put(cnt++, head);
  16. head=head.next;
  17. }
  18. return map.get(cnt-k);
  19. }
  20. }

题目描述

实现一个算法,删除单向链表中间的某个结点,假定你只能访问该结点。

给定带删除的节点,请执行删除操作,若该节点为尾节点,返回false,否则返回true

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. /*
  3. public class ListNode {
  4. int val;
  5. ListNode next = null;
  6. ListNode(int val) {
  7. this.val = val;
  8. }
  9. }*/
  10. public class Remove {
  11. public boolean removeNode(ListNode pNode) {
  12. if(pNode==null){
  13. return false;
  14. }
  15. if(pNode.next==null){
  16. return false;
  17. }
  18. return true;
  19. }
  20. }

题目描述

编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前

给定一个链表的头指针ListNode* pHead,请返回重新排列后的链表的头指针。注意:分割以后保持原来的数据顺序不变。

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. import java.util.Collections;
  3. import java.util.Comparator;
  4. import java.util.LinkedList;
  5. /*
  6. public class ListNode {
  7. int val;
  8. ListNode next = null;
  9. ListNode(int val) {
  10. this.val = val;
  11. }
  12. }*/
  13. public class Partition {
  14. public ListNode partition(ListNode pHead, int x) {
  15. if(pHead==null||pHead.next==null){
  16. return pHead;
  17. }
  18. ListNode cur=pHead;
  19. ListNode Ahead=new ListNode(-1);
  20. ListNode Bhead=new ListNode(-1);
  21. ListNode Atemp=Ahead;
  22. ListNode Btemp=Bhead;
  23. while(cur!=null){
  24. if(cur.val<x){
  25. Atemp.next=new ListNode(cur.val);
  26. Atemp=Atemp.next;
  27. }else{
  28. Btemp.next=new ListNode(cur.val);
  29. Btemp=Btemp.next;
  30. }
  31. cur=cur.next;
  32. }
  33. ListNode newhead=Ahead;
  34. while(newhead.next!=null&&newhead.next.val!=-1){
  35. newhead=newhead.next;
  36. }
  37. newhead.next=Bhead.next;
  38. return Ahead.next;//取Ahead->next而不取Ahead是因为Ahead头的val是-1,不是链表中的值
  39. }
  40. }

题目描述

有两个用链表表示的整数,每个结点包含一个数位。这些数位是反向存放的,也就是个位排在链表的首部。编写函数对这两个整数求和,并用链表形式返回结果。

给定两个链表ListNode* A,ListNode* B,请返回A+B的结果(ListNode*)。

测试样例:

{1,2,3},{3,2,1}
返回:{4,4,4}

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. /*
  3. public class ListNode {
  4. int val;
  5. ListNode next = null;
  6. ListNode(int val) {
  7. this.val = val;
  8. }
  9. }*/
  10. public class Plus {
  11. public static ListNode plusAB(ListNode a, ListNode b) {
  12. if (a == null && b == null) {
  13. return null;
  14. }
  15. ListNode Ahead = a;
  16. ListNode Bhead = b;
  17. ListNode newhead = new ListNode(-1);
  18. ListNode newtemp = newhead;
  19. int temp = 0;
  20. while (Ahead != null || Bhead != null) {
  21. // 三种情况:1:Ahead!=null&&Bhead!=null
  22. // 2:Ahead==null&&Bhead!=null
  23. // 3:Ahead!=null&&Bhead==null
  24. if (Ahead != null && Bhead != null) {
  25. ListNode node = new ListNode((Ahead.val + Bhead.val + temp) % 10);
  26. temp = (Ahead.val + Bhead.val + temp) / 10;
  27. newtemp.next = node;
  28. newtemp = newtemp.next;
  29. Ahead = Ahead.next;
  30. Bhead = Bhead.next;
  31. } else if (Ahead == null && Bhead != null) {
  32. ListNode node = new ListNode((Bhead.val + temp) % 10);
  33. temp = (Bhead.val + temp) / 10;
  34. newtemp.next = node;
  35. newtemp = newtemp.next;
  36. Bhead = Bhead.next;
  37. } else if (Ahead != null && Bhead == null) {
  38. ListNode node = new ListNode((Ahead.val + temp) % 10);
  39. temp = (Ahead.val + temp) / 10;
  40. newtemp.next = node;
  41. newtemp = newtemp.next;
  42. Ahead = Ahead.next;
  43. }
  44. }
  45. if (temp != 0) {
  46. ListNode node = new ListNode(temp);
  47. newtemp.next = node;
  48. newtemp = newtemp.next;
  49. }
  50. return newhead.next;
  51. }
  52. }

题目描述

输入一个链表,反转链表后,输出链表的所有元素。

代码:

[java]viewplaincopy

  1. /*
  2. public class ListNode {
  3. int val;
  4. ListNode next = null;
  5. ListNode(int val) {
  6. this.val = val;
  7. }
  8. }*/
  9. import java.util.LinkedHashMap;
  10. public class Solution {
  11. public ListNode ReverseList(ListNode head) {
  12. if(head==null){
  13. return null;
  14. }
  15. ListNode newhead=null;
  16. ListNode phead=head;
  17. ListNode prehead=null;
  18. while(phead!=null){
  19. ListNode pnext=phead.next;
  20. if(pnext==null){
  21. newhead=phead;
  22. }
  23. phead.next=prehead;
  24. prehead=phead;
  25. phead=pnext;
  26. }
  27. return newhead;
  28. }
  29. }

题目描述

请编写一个函数,检查链表是否为回文。

给定一个链表ListNode* pHead,请返回一个bool,代表链表是否为回文。

测试样例:

{1,2,3,2,1}
返回:true
{1,2,3,2,3}
返回:false

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. /*
  3. public class ListNode {
  4. int val;
  5. ListNode next = null;
  6. ListNode(int val) {
  7. this.val = val;
  8. }
  9. }*/
  10. public class Palindrome {
  11. public boolean isPalindrome(ListNode pHead) {
  12. if (pHead == null) {
  13. return false;
  14. }
  15. LinkedList<Integer> linkedList = new LinkedList<Integer>();
  16. while (pHead != null) {
  17. linkedList.add(pHead.val);
  18. pHead = pHead.next;
  19. }
  20. return Check(linkedList);
  21. }
  22. // 检查是否为回文串
  23. private boolean Check(LinkedList<Integer> linkedList) {
  24. boolean result = true;
  25. int len = linkedList.size();
  26. int length =len>>1;
  27. for (int i = 0; i < length; i++) {
  28. if (linkedList.get(i) != linkedList.get(len - i - 1)) {
  29. result = false;
  30. break;
  31. }
  32. }
  33. return result;
  34. }
  35. }

题目描述

用两个栈来实现一个队列,完成队列的Push和Pop操作。队列中的元素为int类型。

代码:

[java]viewplaincopy

  1. import java.util.Stack;
  2. public class Solution {
  3. Stack<Integer> stack1 = new Stack<Integer>();
  4. Stack<Integer> stack2 = new Stack<Integer>();
  5. public void push(int node) {
  6. stack1.push(node);
  7. }
  8. public int pop() {
  9. while(!stack1.isEmpty()){
  10. stack2.push(stack1.pop());
  11. }
  12. int node=stack2.pop();
  13. while(!stack2.isEmpty()){
  14. stack1.push(stack2.pop());
  15. }
  16. return node;
  17. }
  18. }

题目描述

有一些数的素因子只有3、5、7,请设计一个算法,找出其中的第k个数。

给定一个数int k,请返回第k个数。保证k小于等于100。

测试样例:

3
返回:7

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class KthNumber {
  3. private static final int Max = (int) (1e5 + 10);
  4. private static int cnt;
  5. private static int []A;
  6. public static int findKth(int k) {
  7. InitData();
  8. return A[k];
  9. }
  10. private static void InitData() {
  11. cnt=1;
  12. int a=0,b=0,c=0;
  13. A=new int[Max];
  14. A[0]=1;
  15. while(cnt<=100){
  16. int temp=Math.min(A[a]*3, Math.min(A[b]*5, A[c]*7));
  17. if(temp==A[a]*3)
  18. a++;
  19. if(temp==A[b]*5)
  20. b++;
  21. if(temp==A[c]*7)
  22. c++;
  23. A[cnt++]=temp;
  24. }
  25. }
  26. }

题目描述

现在我们有一个int数组,请你找出数组中每个元素的下一个比它大的元素。

给定一个int数组A及数组的大小n,请返回一个int数组,代表每个元素比他大的下一个元素,若不存在则为-1。保证数组中元素均为正整数。

测试样例:

[11,13,10,5,12,21,3],7
返回:[13,21,12,12,21,-1,-1]

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class NextElement {
  3. public static int[] findNext(int[] A, int n) {
  4. int []B=new int[n];
  5. for(int i=0;i<n;i++){
  6. int temp=-1;
  7. for(int j=i+1;j<n;j++){
  8. if(A[i]<A[j]){
  9. temp=A[j];
  10. break;
  11. }
  12. }
  13. B[i]=temp;
  14. }
  15. return B;
  16. }
  17. }

题目描述

现在有一个数组,请找出数组中每个元素的后面比它大的最小的元素,若不存在则为-1。

给定一个int数组A及数组的大小n,请返回每个元素所求的值组成的数组。保证A中元素为正整数,且n小于等于1000。

测试样例:

[11,13,10,5,12,21,3],7
[12,21,12,12,21,-1,-1]

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class NextElement {
  3. public static int[] findNext(int[] A, int n) {
  4. int []B=new int[n];
  5. for(int i=0;i<n;i++){
  6. int temp=Integer.MAX_VALUE;
  7. boolean ok=false;
  8. for(int j=i+1;j<n;j++){
  9. if(A[i]<A[j]){
  10. ok=true;
  11. temp=Math.min(temp, A[j]);
  12. }
  13. }
  14. if(ok){
  15. B[i]=temp;
  16. }else{
  17. B[i]=-1;
  18. }
  19. }
  20. return B;
  21. }
  22. }

题目描述

请编写一个程序,按升序对栈进行排序(即最大元素位于栈顶),要求最多只能使用一个额外的栈存放临时数据,但不得将元素复制到别的数据结构中。

给定一个int[] numbers(C++中为vector&ltint>),其中第一个元素为栈顶,请返回排序后的栈。请注意这是一个栈,意味着排序过程中你只能访问到第一个元素。

测试样例:

[1,2,3,4,5]
返回:[5,4,3,2,1]

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class TwoStacks {
  3. public  ArrayList<Integer> twoStacksSort(int[] numbers) {
  4. ArrayList<Integer> list=new ArrayList<>();
  5. if(numbers.length==0){
  6. return list;
  7. }
  8. Stack<Integer> stack1=new Stack<Integer>();
  9. Stack<Integer> stack2=new Stack<Integer>();
  10. for(int i=0;i<numbers.length;i++){
  11. stack1.push(numbers[i]);
  12. }
  13. while(!stack1.isEmpty()){
  14. int temp=stack1.pop();
  15. while(!stack2.isEmpty()&&stack2.peek()>temp){
  16. stack1.push(stack2.pop());
  17. }
  18. stack2.push(temp);
  19. }
  20. int len=stack2.size();
  21. for(int i=0;i<len;i++){
  22. list.add(stack2.pop());
  23. }
  24. return list;
  25. }
  26. }

题目描述

实现一个函数,检查二叉树是否平衡,平衡的定义如下,对于树中的任意一个结点,其两颗子树的高度差不超过1。

给定指向树根结点的指针TreeNode* root,请返回一个bool,代表这棵树是否平衡。

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. /*
  3. public class TreeNode {
  4. int val = 0;
  5. TreeNode left = null;
  6. TreeNode right = null;
  7. public TreeNode(int val) {
  8. this.val = val;
  9. }
  10. }*/
  11. public class Balance {
  12. public boolean isBalance(TreeNode root) {
  13. if (root == null)
  14. return true;
  15. TreeNode left=root.left;
  16. TreeNode right=root.right;
  17. int val=Math.abs(GetHigh(left)-GetHigh(right));//判断左数和右树的高度差
  18. if(val>1)//如果大于1,不符合
  19. return false;
  20. //如果不大于1,继续判断左树的子树和右树的子树
  21. return isBalance(left)&&isBalance(right);
  22. }
  23. //获取一棵树的高度
  24. private int GetHigh(TreeNode root){
  25. if(root==null)
  26. return 0;
  27. int lefthigh=GetHigh(root.left);
  28. int righthigh=GetHigh(root.right);
  29. return lefthigh>righthigh?(lefthigh+1):(righthigh+1);
  30. }
  31. }

题目描述

对于一个有向图,请实现一个算法,找出两点之间是否存在一条路径。

给定图中的两个结点的指针UndirectedGraphNode* a,UndirectedGraphNode*b(请不要在意数据类型,图是有向图),请返回一个bool,代表两点之间是否存在一条路径(a到b或b到a)。

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. import java.util.ArrayList;
  3. /*
  4. public class UndirectedGraphNode {
  5. int label = 0;
  6. UndirectedGraphNode left = null;
  7. UndirectedGraphNode right = null;
  8. ArrayList<UndirectedGraphNode> neighbors = new ArrayList<UndirectedGraphNode>();
  9. public UndirectedGraphNode(int label) {
  10. this.label = label;
  11. }
  12. }*/
  13. public class Path {
  14. public boolean checkPath(UndirectedGraphNode a, UndirectedGraphNode b) {
  15. if(a==b){
  16. return true;
  17. }
  18. HashMap<UndirectedGraphNode, Boolean> map=new HashMap<UndirectedGraphNode, Boolean>();
  19. boolean ok=Check(a,b,map);//从a开始找,b不动
  20. map.clear();
  21. return ok||Check(b,a,map);//从b开始找,a不动
  22. }
  23. private boolean Check(UndirectedGraphNode a, UndirectedGraphNode b,HashMap<UndirectedGraphNode, Boolean> map){
  24. if(a==b){
  25. return true;
  26. }
  27. map.put(a, true);
  28. for(int i=0;i<a.neighbors.size();i++){//从a的邻居找,看看有没有等于b的
  29. if(!map.containsKey(a.neighbors.get(i))&&Check(a.neighbors.get(i), b, map)){
  30. return true;
  31. }
  32. }
  33. return false;
  34. }
  35. }

题目描述

对于一个元素各不相同且按升序排列的有序序列,请编写一个算法,创建一棵高度最小的二叉查找树。

给定一个有序序列int[] vals,请返回创建的二叉查找树的高度。

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class MinimalBST {
  3. public int buildMinimalBST(int[] vals) {
  4. int length=vals.length;
  5. if(length==0){
  6. return 0;
  7. }
  8. int sum=1;
  9. for(int i=1;i<=10000;i++){
  10. sum<<=1;
  11. if(sum-1>=length){
  12. return i;
  13. }
  14. }
  15. return 0;
  16. }
  17. }

题目描述

对于一棵二叉树,请设计一个算法,创建含有某一深度上所有结点的链表。

给定二叉树的根结点指针TreeNode* root,以及链表上结点的深度,请返回一个链表ListNode,代表该深度上所有结点的值,请按树上从左往右的顺序链接,保证深度不超过树的高度,树上结点的值为非负整数且不超过100000。

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. /*
  3. public class ListNode {
  4. int val;
  5. ListNode next = null;
  6. ListNode(int val) {
  7. this.val = val;
  8. }
  9. }*/
  10. /*
  11. public class TreeNode {
  12. int val = 0;
  13. TreeNode left = null;
  14. TreeNode right = null;
  15. public TreeNode(int val) {
  16. this.val = val;
  17. }
  18. }*/
  19. public class TreeLevel {
  20. public ListNode getTreeLevel(TreeNode root, int dep) {
  21. ListNode listNode=new ListNode(-1);
  22. ListNode head=listNode;
  23. if(root==null||dep==0){
  24. return null;
  25. }
  26. Queue<TreeNode> queue=new LinkedList<TreeNode>();
  27. queue.add(root);
  28. int ans=1;
  29. while(!queue.isEmpty()){
  30. int size=queue.size();
  31. if(ans==dep){
  32. for(int i=0;i<size;i++){
  33. TreeNode node=queue.poll();
  34. ListNode newhead=new ListNode(node.val);
  35. head.next=newhead;
  36. head=head.next;
  37. }
  38. break;
  39. }else{
  40. for(int i=0;i<size;i++){
  41. TreeNode node=queue.poll();
  42. TreeNode left=node.left;
  43. TreeNode right=node.right;
  44. if(left!=null){
  45. queue.add(left);
  46. }
  47. if(right!=null){
  48. queue.add(right);
  49. }
  50. }
  51. }
  52. ans++;
  53. }
  54. return listNode.next;
  55. }
  56. }

题目描述

请实现一个函数,检查一棵二叉树是否为二叉查找树。

给定树的根结点指针TreeNode* root,请返回一个bool,代表该树是否为二叉查找树。

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. /*
  3. public class TreeNode {
  4. int val = 0;
  5. TreeNode left = null;
  6. TreeNode right = null;
  7. public TreeNode(int val) {
  8. this.val = val;
  9. }
  10. }*/
  11. public class Checker {
  12. /***
  13. * 二叉排序树(BinarySortTree),又称二叉查找树、二叉搜索树。
  14. * 它或者是一棵空树;或者是具有下列性质的二叉树:
  15. * 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  16. * 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  17. * 左、右子树也分别为二叉排序树。若子树为空,查找不成功。
  18. * @param root
  19. * @return
  20. */
  21. public boolean checkBST(TreeNode root) {
  22. if (root == null) {
  23. return true;
  24. }
  25. return Check(root,Integer.MIN_VALUE,Integer.MAX_VALUE);
  26. }
  27. private boolean Check(TreeNode root,int min,int max) {
  28. if (root == null) {
  29. return true;
  30. }
  31. int rootval = root.val;
  32. TreeNode left = root.left;
  33. TreeNode right = root.right;
  34. if(rootval<min||rootval>max)
  35. return false;
  36. return Check(left, min, rootval)&&Check(right, rootval, max);
  37. }
  38. }

题目描述

请设计一个算法,寻找二叉树中指定结点的下一个结点(即中序遍历的后继)。

给定树的根结点指针TreeNode* root和结点的值intp,请返回值为p的结点的后继结点的值。保证结点的值大于等于零小于等于100000且没有重复值,若不存在后继返回-1。

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. /*
  3. public class TreeNode {
  4. int val = 0;
  5. TreeNode left = null;
  6. TreeNode right = null;
  7. public TreeNode(int val) {
  8. this.val = val;
  9. }
  10. }*/
  11. public class Successor {
  12. private static LinkedList<TreeNode> list;
  13. public int findSucc(TreeNode root, int p) {
  14. int result=-1;
  15. list=new LinkedList<TreeNode>();
  16. if(root==null){
  17. return result;
  18. }
  19. DFS(root);
  20. for(int i=0;i<list.size()-1;i++){
  21. int val=list.get(i).val;
  22. if(val==p){
  23. if(list.get(i+1)!=null){
  24. result=list.get(i+1).val;
  25. }
  26. break;
  27. }
  28. }
  29. return result;
  30. }
  31. //先中序遍历存下每个节点的信息
  32. private void DFS(TreeNode root){
  33. if(root==null){
  34. return;
  35. }
  36. DFS(root.left);
  37. list.add(root);
  38. DFS(root.right);
  39. }
  40. }

题目描述

请设计一个算法,计算n的阶乘有多少个尾随零。

给定一个int n,请返回n的阶乘的尾零个数。保证n为正整数。

测试样例:

5
返回:1

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class Factor {
  3. public int getFactorSuffixZero(int n) {
  4. int ans2 = 0, ans5 = 0, ans = 0;
  5. for (int i = 1; i <= n; i++) {
  6. if (i % 10 == 0) {
  7. int temp = i;
  8. while (temp > 0) {
  9. if (temp % 10 == 0) {
  10. ans++;
  11. temp /= 10;
  12. }else if(temp%5==0){
  13. ans5++;
  14. temp/=5;
  15. }else if(temp%2==0){
  16. ans2++;
  17. temp/=2;
  18. }else{
  19. break;
  20. }
  21. }
  22. } else if (i % 2 == 0) {
  23. int temp = i;
  24. while (temp > 0) {
  25. if (temp % 10 == 0) {
  26. ans++;
  27. temp /= 10;
  28. }else if(temp%5==0){
  29. ans5++;
  30. temp/=5;
  31. }else if(temp%2==0){
  32. ans2++;
  33. temp/=2;
  34. }else{
  35. break;
  36. }
  37. }
  38. } else if (i % 5 == 0) {
  39. int temp = i;
  40. while (temp > 0) {
  41. if (temp % 10 == 0) {
  42. ans++;
  43. temp /= 10;
  44. }else if(temp%5==0){
  45. ans5++;
  46. temp/=5;
  47. }else if(temp%2==0){
  48. ans2++;
  49. temp/=2;
  50. }else{
  51. break;
  52. }
  53. }
  54. }
  55. }
  56. return ans + Math.min(ans2, ans5);
  57. }
  58. }

题目描述

有一棵无穷大的满二叉树,其结点按根结点一层一层地从左往右依次编号,根结点编号为1。现在有两个结点a,b。请设计一个算法,求出a和b点的最近公共祖先的编号。

给定两个int a,b。为给定结点的编号。请返回a和b的最近公共祖先的编号。注意这里结点本身也可认为是其祖先。

测试样例:

2,3
返回:1

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class LCA {
  3. private static final int Max=(int) (1e6+10);
  4. private static int dp1[];
  5. private static int dp2[];
  6. public static int getLCA(int a, int b) {
  7. dp1=new int[Max];
  8. dp2=new int[Max];
  9. int cnt1=0,cnt2=0;
  10. while(a>0){//储存父节点的信息
  11. dp1[cnt1++]=a;
  12. a>>=1;
  13. }
  14. while(b>0){//储存父节点的信息
  15. dp2[cnt2++]=b;
  16. b>>=1;
  17. }
  18. for(int i=0;i<cnt1;i++){
  19. for(int j=0;j<cnt2;j++){
  20. if(dp1[i]==dp2[j]){
  21. return dp1[i];
  22. }
  23. }
  24. }
  25. return 1;
  26. }
  27. }

题目描述

输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

代码:

[java]viewplaincopy

  1. import java.util.ArrayList;
  2. /**
  3. public class TreeNode {
  4. int val = 0;
  5. TreeNode left = null;
  6. TreeNode right = null;
  7. public TreeNode(int val) {
  8. this.val = val;
  9. }
  10. }
  11. */
  12. public class Solution {
  13. private ArrayList<Integer> arrayList=new ArrayList<Integer>();
  14. private  ArrayList<ArrayList<Integer>> list=new ArrayList<ArrayList<Integer>>();
  15. public  ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
  16. if(root==null){
  17. return list;
  18. }
  19. arrayList.add(root.val);
  20. target-=root.val;
  21. if(target==0&&root.left==null&&root.right==null){
  22. list.add(new ArrayList<Integer>(arrayList));
  23. }
  24. FindPath(root.left,target);
  25. FindPath(root.right,target);
  26. if(arrayList!=null){
  27. int size=arrayList.size();
  28. if(size>1){
  29. arrayList.remove(size-1);
  30. }
  31. }
  32. return list;
  33. }
  34. }

题目描述

有两个32位整数n和m,请编写算法将m的二进制数位插入到n的二进制的第j到第i位,其中二进制的位数从低位数到高位且以0开始。

给定两个数int n和int m,同时给定int j和int i,意义如题所述,请返回操作后的数,保证n的第j到第i位均为零,且m的二进制位数小于等于i-j+1。

测试样例:

1024,19,2,6
返回:1100

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class BinInsert {
  3. public static int binInsert(int n, int m, int j, int i) {
  4. int sum=n;
  5. int cnt=j;
  6. while(m>0){
  7. if((m&1)==1){
  8. sum+=POW(2, cnt);
  9. }
  10. cnt++;
  11. m>>=1;
  12. }
  13. return sum;
  14. }
  15. private static  int POW(int a,int b){
  16. int sum=1;
  17. while(b>0){
  18. if((b&1)==1){
  19. sum*=a;
  20. }
  21. b>>=1;
  22. a*=a;
  23. }
  24. return sum;
  25. }
  26. }

题目描述

有一个介于0和1之间的实数,类型为double,返回它的二进制表示。如果该数字无法精确地用32位以内的二进制表示,返回“Error”。

给定一个double num,表示0到1的实数,请返回一个string,代表该数的二进制表示或者“Error”。

测试样例:

0.625
返回:0.101

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class BinDecimal {
  3. public String printBin(double num) {
  4. if(num>1||num<0){
  5. return "Error";
  6. }
  7. StringBuilder builder=new StringBuilder();
  8. builder.append("0.");
  9. while(num>0){
  10. if(builder.length()>32){
  11. return "Error";
  12. }
  13. double r=num*2.0;
  14. if(r>=1.0){
  15. builder.append(1);
  16. num=r-1.0;
  17. }else{
  18. builder.append(0);
  19. num=r;
  20. }
  21. }
  22. return builder.toString();
  23. }
  24. }

题目描述

有一个正整数,请找出其二进制表示中1的个数相同、且大小最接近的那两个数。(一个略大,一个略小)

给定正整数int x,请返回一个vector,代表所求的两个数(小的在前)。保证答案存在。

测试样例:

2
返回:[1,4]

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class CloseNumber {
  3. public  int[] getCloseNumber(int x) {
  4. int small = 0,big=0;
  5. int ans=Get1Count(x);
  6. for(int i=x-1;i>=1;i--){
  7. if(Get1Count(i)==ans){
  8. small=i;
  9. break;
  10. }
  11. }
  12. for(int j=x+1;;j++){
  13. if(Get1Count(j)==ans){
  14. big=j;
  15. break;
  16. }
  17. }
  18. return new int[]{small,big};
  19. }
  20. private  int Get1Count(int n){
  21. int ans=0;
  22. while(n>0){
  23. if((n&1)==1)
  24. ans++;
  25. n>>=1;
  26. }
  27. return ans;
  28. }
  29. }

题目描述

编写一个函数,确定需要改变几个位,才能将整数A转变成整数B。

给定两个整数int A,int B。请返回需要改变的数位个数。

测试样例:

10,5
返回:4

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class Transform {
  3. public int calcCost(int A, int B) {
  4. return Get1Count(A^B);//求A和B中不相同的的个数,全部转换成1,最后算A^B中有多少个1就是了
  5. }
  6. private int Get1Count(int ans){
  7. int sum=0;
  8. while(ans>0){
  9. if((ans&1)==1)
  10. sum++;
  11. ans>>=1;
  12. }
  13. return sum;
  14. }
  15. }

题目描述

请编写程序交换一个数的二进制的奇数位和偶数位。(使用越少的指令越好)

给定一个int x,请返回交换后的数int。

测试样例:

10
返回:5

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class Exchange {
  3. public int exchangeOddEven(int x) {
  4. String ans=Integer.toBinaryString(x);
  5. if((ans.length()&1)==1){
  6. ans="0"+ans;
  7. }
  8. char []array=ans.toCharArray();
  9. for(int i=0;i<array.length;i+=2){
  10. char temp=array[i];
  11. array[i]=array[i+1];
  12. array[i+1]=temp;
  13. }
  14. return Integer.valueOf(new String(array),2);
  15. }
  16. }

题目描述

有一个排过序的字符串数组,但是其中有插入了一些空字符串,请设计一个算法,找出给定字符串的位置。算法的查找部分的复杂度应该为log级别。

给定一个string数组str,同时给定数组大小n和需要查找的string x,请返回该串的位置(位置从零开始)。

测试样例:

["a","b","","c","","d"],6,"c"
返回:3

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class Finder {
  3. public  int findString(String[] str, int n, String x) {
  4. ArrayList<Node> list=new ArrayList<Node>();
  5. for(int i=0;i<n;i++){
  6. Node node=new Node(str[i],i);
  7. list.add(node);
  8. }
  9. Collections.sort(list,new Comparator<Node>() {
  10. @Override
  11. public int compare(Node o1, Node o2) {
  12. return o1.ans.compareTo(o2.ans);
  13. }
  14. });
  15. int low=0;
  16. int high=list.size()-1;
  17. while(low<=high){
  18. int mid=(low+high)>>1;
  19. String result=list.get(mid).ans;
  20. if(result.equals(x)){
  21. return list.get(mid).index;
  22. }else if(result.compareTo(x)>0){
  23. high=mid-1;
  24. }else{
  25. low=mid+1;
  26. }
  27. }
  28. return -1;
  29. }
  30. class Node{
  31. String ans;
  32. int index;
  33. public Node(){
  34. }
  35. public Node(String ans,int index){
  36. this.ans=ans;
  37. this.index=index;
  38. }
  39. }
  40. }

题目描述

有一个NxM的整数矩阵,矩阵的行和列都是从小到大有序的。请设计一个高效的查找算法,查找矩阵中元素x的位置。

给定一个int有序矩阵mat,同时给定矩阵的大小n和m以及需要查找的元素x,请返回一个二元数组,代表该元素的行号和列号(均从零开始)。保证元素互异。

测试样例:

[[1,2,3],[4,5,6]],2,3,6
返回:[1,2]

代码:

[java]viewplaincopy

  1. import java.util.*;
  2. public class Finder {
  3. public  int[] findElement(int[][] mat, int n, int m, int x) {
  4. int []rws=new int[2];
  5. int row=0,col=mat[0].length-1;
  6. while(row<mat.length&&col>=0){
  7. if(mat[row][col]==x){
  8. rws[0]=row;
  9. rws[1]=col;
  10. break;
  11. }else if(mat[row][col]<x){
  12. row++;
  13. }else{
  14. col--;
  15. }
  16. }
  17. return rws;
  18. }
  19. }

最近准备面试,因此整理一份Java中常用的数据结构资料,方便面试;

java.util包中三个重要的接口及特点:List(列表)、Set(保证集合中元素唯一)、Map(维护多个key-value键值对,保证key唯一)。其不同子类的实现各有差异,如是否同步(线程安全)、是否有序。

常用类继承树:

以下结合源码讲解常用类实现原理及相互之间的差异。

Collection (所有集合类的接口)
List、Set都继承自Collection接口,查看JDK API,操作集合常用的方法大部分在该接口中定义了。

Collections (操作集合的工具类)
对于集合类的操作不得不提到工具类Collections,它提供了许多方便的方法,如求两个集合的差集、并集、拷贝、排序等等。
由于大部分的集合接口实现类都是不同步的,可以使用Collections.synchronized*方法创建同步的集合类对象。
如创建一个同步的List:
List synList = Collections.synchronizedList(new ArrayList());
其实现原理就是重新封装new出来的对象,操作对象时用关键字synchronized同步。看源码很容易理解。
Collections部分源码:

[java] view plain copy

  1. static class SynchronizedCollection<e> implements Collection<e>, Serializable {
  2. final Collection<e> c;  // Backing Collection
  3. final Object mutex;     // Object on which to synchronize
  4. SynchronizedCollection(Collection<e> c) {
  5. if (c==null)
  6. throw new NullPointerException();
  7. this.c = c;
  8. mutex = this;
  9. }
  10. //...
  11. public boolean add(E e) {
  12. //操作集合时简单调用原本的ArrayList对象,只是做了同步
  13. synchronized (mutex) {return c.add(e);}
  14. }
  15. //...
  16. }

List(列表)

ArrayList、Vector是线性表,使用Object数组作为容器去存储数据的,添加了很多方法维护这个数组,使其容量可以动态增长,极大地提升了开发效率。它们明显的区别是ArrayList是非同步的,Vector是同步的。不用考虑多线程时应使用ArrayList来提升效率。
ArrayList、Vector 部分源码:

[java] view plain copy

  1. public boolean add(E e) {
  2. ensureCapacityInternal(size + 1);  // Increments modCount!!
  3. //可以看出添加的对象放到elementData数组中去了
  4. elementData[size++] = e;
  5. return true;
  6. }
  7. //ArrayList.remove
  8. public E remove(int index) {
  9. rangeCheck(index);
  10. modCount++;
  11. E oldValue = elementData(index);
  12. int numMoved = size - index - 1;
  13. if (numMoved > 0)
  14. //移除元素时数组产生的空位由System.arraycopy方法将其后的所有元素往前移一位,System.arraycopy调用虚拟机提供的本地方法来提升效率
  15. System.arraycopy(elementData, index+1, elementData, index,
  16. numMoved);
  17. elementData[--size] = null; // Let gc do its work
  18. return oldValue;
  19. }
  20. //Vector add方法上多了synchronized关键字
  21. public synchronized boolean add(E e) {
  22. modCount++;
  23. ensureCapacityHelper(elementCount + 1);
  24. elementData[elementCount++] = e;
  25. return true;
  26. }

LinkedList是链表,略懂数据结构就知道其实现原理了。链表随机位置插入、删除数据时比线性表快,遍历比线性表慢。
双向链表原理图:

LinkedList部分源码:

[java] view plain copy

  1. public class LinkedList<e>
  2. extends AbstractSequentialList<e>
  3. implements List<e>, Deque<e>, Cloneable, java.io.Serializable
  4. {
  5. //头尾节点
  6. transient Node<e> first;
  7. transient Node<e> last;
  8. }
  9. //节点类
  10. private static class Node<e> {
  11. //节点存储的数据
  12. E item;
  13. Node<e> next;
  14. Node<e> prev;
  15. Node(Node<e> prev, E element, Node<e> next) {
  16. this.item = element;
  17. this.next = next;
  18. this.prev = prev;
  19. }
  20. }

由此可根据实际情况来选择使用ArrayList(非同步、非频繁删除时选择)、Vector(需同步时选择)、LinkedList(频繁在任意位置插入、删除时选择)。

Map(存储键值对,key唯一)

HashMap结构的实现原理是将put进来的key-value封装成一个Entry对象存储到一个Entry数组中,位置(数组下标)由key的哈希值与数组长度计算而来。如果数组当前下标已有值,则将数组当前下标的值指向新添加的Entry对象。
有点晕,看图吧:

看完图再看源码,非常清晰,都不需要注释。

[java] view plain copy

  1. public class HashMap<k,v>
  2. extends AbstractMap<k,v>
  3. implements Map<k,v>, Cloneable, Serializable
  4. {
  5. transient Entry<k,v>[] table;
  6. public V put(K key, V value) {
  7. if (key == null)
  8. return putForNullKey(value);
  9. int hash = hash(key);
  10. int i = indexFor(hash, table.length);
  11. //遍历当前下标的Entry对象链,如果key已存在则替换
  12. for (Entry<k,v> e = table[i]; e != null; e = e.next) {
  13. Object k;
  14. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  15. V oldValue = e.value;
  16. e.value = value;
  17. e.recordAccess(this);
  18. return oldValue;
  19. }
  20. }
  21. addEntry(hash, key, value, i);
  22. return null;
  23. }
  24. }
  25. static class Entry<k,v> implements Map.Entry<k,v> {
  26. final K key;
  27. V value;
  28. Entry<k,v> next;
  29. int hash;
  30. }

TreeMap是由Entry对象为节点组成的一颗红黑树,put到TreeMap的数据默认按key的自然顺序排序,new TreeMap时传入Comparator自定义排序。红黑树网上很多资料,我讲不清,这里就不介绍了。

Set(保证容器内元素唯一性)
之所以先讲Map是因为Set结构其实就是维护一个Map来存储数据的,利用Map结构key值唯一性
HashSet部分源码:

[java] view plain copy

  1. public class HashSet<e>
  2. extends AbstractSet<e>
  3. implements Set<e>, Cloneable, java.io.Serializable
  4. {
  5. //无意义对象来作为Map的value
  6. private static final Object PRESENT = new Object();
  7. public boolean add(E e) {
  8. return map.put(e, PRESENT)==null;
  9. }
  10. }

[java] view plain copy

  1. <span style="font-size:18px;"><strong> HashMap 支持key=null 但是 Hashtable 不支持 key =null</strong></span><span style="font-size:14px;">
  2. </span>

HashMap和Hashtable的区别

HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。

1.    HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。

2.    HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。

3.    另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。

4.    由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。

5.    HashMap不能保证随着时间的推移Map中的元素次序是不变的。

要注意的一些重要术语:

1)sychronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。

2)Fail-safe和iterator迭代器相关。如果某个集合对象创建了Iterator或者ListIterator,然后其它的线程试图“结构上”更改集合对象,将会抛出ConcurrentModificationException异常。但其它线程可以通过set()方法更改集合对象是允许的,因为这并没有从“结构上”更改集合。但是假如已经从结构上进行了更改,再调用set()方法,将会抛出IllegalArgumentException异常。

3)结构上的更改指的是删除或者插入一个元素,这样会影响到map的结构。

我们能否让HashMap同步?

HashMap可以通过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);

结论

Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java 5或以上的话,请使用ConcurrentHashMap吧。

HashSet、TreeSet分别默认维护一个HashMap、TreeMap。

1.栈和队列的共同特点是(只允许在端点处插入和删除元素)
4.栈通常采用的两种存储结构是(线性存储结构和链表存储结构)
5.下列关于栈的叙述正确的是(D)
     A.栈是非线性结构B.栈是一种树状结构C.栈具有先进先出的特征D.栈有后进先出的特征
6.链表不具有的特点是(B)A.不必事先估计存储空间       B.可随机访问任一元素
C.插入删除不需要移动元素      D.所需空间与线性表长度成正比
7.用链表表示线性表的优点是(便于插入和删除操作)
8.在单链表中,增加头结点的目的是(方便运算的实现)
9.循环链表的主要优点是(从表中任一结点出发都能访问到整个链表)
10.线性表L=(a1,a2,a3,……ai,……an),下列说法正确的是(D)
     A.每个元素都有一个直接前件和直接后件  B.线性表中至少要有一个元素
     C.表中诸元素的排列顺序必须是由小到大或由大到小
     D.除第一个和最后一个元素外,其余每个元素都有一个且只有一个直接前件和直接后件
11.线性表若采用链式存储结构时,要求内存中可用存储单元的地址(D)
A.必须是连续的 B.部分地址必须是连续的C.一定是不连续的 D.连续不连续都可以
12.线性表的顺序存储结构和线性表的链式存储结构分别是(随机存取的存储结构、顺序存取的存储结构)
13.树是结点的集合,它的根结点数目是(有且只有1)
14.在深度为5的满二叉树中,叶子结点的个数为(31)
15.具有3个结点的二叉树有(5种形态)
16.设一棵二叉树中有3个叶子结点,有8个度为1的结点,则该二叉树中总的结点数为(13)
17.已知二叉树后序遍历序列是dabec,中序遍历序列是debac,它的前序遍历序列是(cedba)
18.已知一棵二叉树前序遍历和中序遍历分别为ABDEGCFH和DBGEACHF,则该二叉树的后序遍历为(DGEBHFCA)
19.若某二叉树的前序遍历访问顺序是abdgcefh,中序遍历访问顺序是dgbaechf,则其后序遍历的结点访问顺序是(gdbehfca)
20.数据库保护分为:安全性控制、完整性控制、并发性控制和数据的恢复。

1. 在计算机中,算法是指(解题方案的准确而完整的描述)
2.在下列选项中,哪个不是一个算法一般应该具有的基本特征(无穷性)
说明:算法的四个基本特征是:可行性、确定性、有穷性和拥有足够的情报。
3. 算法一般都可以用哪几种控制结构组合而成(顺序、选择、循环)
4.算法的时间复杂度是指(算法执行过程中所需要的基本运算次数)
5. 算法的空间复杂度是指(执行过程中所需要的存储空间)     
6. 算法分析的目的是(分析算法的效率以求改进)     
7. 下列叙述正确的是(C)
A.算法的执行效率与数据的存储结构无关
B.算法的空间复杂度是指算法程序中指令(或语句)的条数
C.算法的有穷性是指算法必须能在执行有限个步骤之后终止
D.算法的时间复杂度是指执行算法程序所需要的时间
8.数据结构作为计算机的一门学科,主要研究数据的逻辑结构、对各种数据结构进行的运算,以及(数据的存储结构)
9. 数据结构中,与所使用的计算机无关的是数据的(C)
A.存储结构   B.物理结构    C.逻辑结构     D.物理和存储结构
10. 下列叙述中,错误的是(B)
A.数据的存储结构与数据处理的效率密切相关
B.数据的存储结构与数据处理的效率无关
C.数据的存储结构在计算机中所占的空间不一定是连续的
D.一种数据的逻辑结构可以有多种存储结构
11. 数据的存储结构是指(数据的逻辑结构在计算机中的表示)
12. 数据的逻辑结构是指(反映数据元素之间逻辑关系的数据结构)
13. 根据数据结构中各数据元素之间前后件关系的复杂程度,一般将数据结构分为(线性结构和非线性结构)
14. 下列数据结构具有记忆功能的是(C)A.队列B.循环队列C.栈D.顺序表
15. 下列数据结构中,按先进后出原则组织数据的是(B)
A.线性链表   B.栈           C.循环链表        D.顺序表
16. 递归算法一般需要利用(队列)实现。
17. 下列关于栈的叙述中正确的是(D)A.在栈中只能插入数据B.在栈中只能删除数据
C.栈是先进先出的线性表           D.栈是先进后出的线性表
20. 由两个栈共享一个存储空间的好处是(节省存储空间,降低上溢发生的机率)

21. 应用程序在执行过程中,需要通过打印机输出数据时,一般先形成一个打印作业,将其存放在硬盘中的一个指定(队列)中,当打印机空闲时,就会按先来先服务的方式从中取出待打印的作业进行打印。
22.下列关于队列的叙述中正确的是(C)A.在队列中只能插入数据 B.在队列中只能删除数据   C.队列是先进先出的线性表            D.队列是先进后出的线性表
23.下列叙述中,正确的是(D)A.线性链表中的各元素在存储空间中的位置必须是连续的
B.线性链表中的表头元素一定存储在其他元素的前面 C.线性链表中的各元素在存储空间中的位置不一定是连续的,但表头元素一定存储在其他元素的前面 D.线性链表中的各元素在存储空间中的位置不一定是连续的,且各元素的存储顺序也是任意的
24.下列叙述中正确的是(A)A.线性表是线性结构      B.栈与队列是非线性结构
C.线性链表是非线性结构                                D.二叉树是线性结构
25. 线性表L=(a1,a2,a3,……ai,……an),下列说法正确的是(D)
A.每个元素都有一个直接前件和直接后件      B.线性表中至少要有一个元素
C.表中诸元素的排列顺序必须是由小到大或由大到小D.除第一个元素和最后一个元素外,其余每个元素都有一个且只有一个直接前件和直接后件
26.线性表若采用链式存储结构时,要求内存中可用存储单元的地址(连续不连续都可以)     
27. 链表不具有的特点是(B)A.不必事先估计存储空间            B.可随机访问任一元素
C.插入删除不需要移动元素           D.所需空间与线性表长度成正比
28. 非空的循环单链表head的尾结点(由p所指向),满足(p->next=head)
29.与单向链表相比,双向链表的优点之一是(更容易访问相邻结点)     
30. 在(D)中,只要指出表中任何一个结点的位置,就可以从它出发依次访问到表中其他所有结点。A.线性单链表           B.双向链表           C.线性链表           D.循环链表
31. 以下数据结构属于非线性数据结构的是(C)A.队列      B.线性表C.二叉树      D.栈
32.树是结点的集合,它的根结点数目是(有且只有1)
33.具有3个结点的二叉树有(5种形态)     
34. 在一棵二叉树上第8层的结点数最多是(128)注:2K-1
35. 在深度为5的满二叉树中,叶子结点的个数为(16)注:2n-1
36. 在深度为5的满二叉树中,共有(31)个结点。注:2n-1
37.设一棵完全二叉树共有699个结点,则在该二叉树中的叶子结点数为(350)
说明:完全二叉树总结点数为N,若N为奇数,则叶子结点数为(N+1)/2;若N为偶数,则叶子结点数为N/2。
38. 设有下列二叉树,对此二叉树中序遍历的结果是(B)
A.ABCDEF     
B.DBEAFC
C.ABDECF     
D.DEBFCA
39.已知二叉树后序遍历序列是dabec,中序遍历序列debac,它的前序遍历序列是(cedba)     
40. 已知一棵二叉树前序遍历和中序遍历分别为ABDEGCFH和DBGEACHF,则该二叉树的后序遍历为(DGEBHFCA)

41.若某二叉树的前序遍历访问顺序是abdgcefh,中序遍历访问顺序是dgbaechf,则其后序遍历的结点访问顺序是(gdbehfca)
42. 串的长度是(串中所含字符的个数)     
43.设有两个串p和q,求q在p中首次出现位置的运算称做(模式匹配)
44. N个顶点的连通图中边的条数至少为(N-1)
45.N个顶点的强连通图的边数至少有(N)
46.对长度为n的线性表进行顺序查找,在最坏情况下所需要的比较次数为(N)
47. 最简单的交换排序方法是(冒泡排序)     
48.假设线性表的长度为n,则在最坏情况下,冒泡排序需要的比较次数为(n(n-1)/2)     
49. 在待排序的元素序列基本有序的前提下,效率最高的排序方法是(冒泡排序)
50. 在最坏情况下,下列顺序方法中时间复杂度最小的是(堆排序)     
51. 希尔排序法属于(插入类排序)
52. 堆排序法属于(选择类排序)
53. 在下列几种排序方法中,要求内存量最大的是(归并排序)     
54. 已知数据表A中每个元素距其最终位置不远,为节省时间,应采用(直接插入排序)
55. 算法的基本特征是可行性、确定性、有穷性   和拥有足够的情报。

1.一个算法通常由两种基本要素组成:一是对数据对象的运算和操作,二是算法的控制结构。
1. 算法的复杂度主要包括时间复杂度和空间复杂度。
2. 实现算法所需的存储单元多少和算法的工作量大小分别称为算法的空间复杂度和时间复杂度。
3.所谓数据处理是指对数据集合中的各元素以各种方式进行运算,包括插入、删除、查找、更改等运算,也包括对数据元素进行分析。
4.数据结构是指相互有关联的数据元素的集合。
5.数据结构分为逻辑结构与存储结构,线性链表属于存储结构。
6.数据结构包括数据的逻辑结构和数据的存储结构。
7. 数据结构包括数据的逻辑结构、数据的存储结构以及对数据的操作运算。
8.数据元素之间的任何关系都可以用前趋和后继关系来描述。
9.数据的逻辑结构有线性结构和非线性结构两大类。
10.常用的存储结构有顺序、链接、索引等存储结构。
11. 顺序存储方法是把逻辑上相邻的结点存储在物理位置   相邻的存储单元中。
12. 栈的基本运算有三种:入栈、退栈与读栈顶元素。
13. 队列主要有两种基本运算:入队运算与退队运算。
14. 在实际应用中,带链的栈可以用来收集计算机存储空间中所有空闲的存储结点,这种带链的栈称为可利用栈。
15.栈和队列通常采用的存储结构是链式存储和顺序存储   。
16.当线性表采用顺序存储结构实现存储时,其主要特点是逻辑结构中相邻的结点在存储结构中仍相邻。
17. 循环队列主要有两种基本运算:入队运算与退队运算。每进行一次入队运算,队尾指针就进1 。
18.当循环队列非空且队尾指针等于对头指针时,说明循环队列已满,不能进行入队运算。这种情况称为上溢  。
19.当循环队列为空时,不能进行退队运算,这种情况称为下溢。
20. 在一个容量为25的循环队列中,若头指针front=16,尾指针rear=9,则该循环队列中共有 18 个元素。注:当rear<front时,元素个数=总容量-(front-rear);
当rear>front时,元素个数=rear-front。

1.判断链表是否存在环型链表问题:判断一个链表是否存在环,例如下面这个链表就存在一个环:
例如N1->N2->N3->N4->N5->N2就是一个有环的链表,环的开始结点是N5这里有一个比较简单的解法。设置两个指针p1,p2。每次循环p1向前走一步,p2向前走两步。直到p2碰到NULL指针或者两个指针相等结束循环。如果两个指针相等则说明存在环。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

struct link

{

 int data;

  link* next;

};

bool IsLoop(link* head)

{

  link* p1=head, *p2 = head;

  if (head ==NULL || head->next ==NULL)

  {

   return false;

  }

  do{

  p1= p1->next;

  p2 = p2->next->next;

  } while(p2 && p2->next && p1!=p2); 

  if(p1 == p2)

   return true;

  else

   return false;

}

2,链表反转单向链表的反转是一个经常被问到的一个面试题,也是一个非常基础的问题。比如一个链表是这样的: 1->2->3->4->5 通过反转后成为5->4->3->2->1。最容易想到的方法遍历一遍链表,利用一个辅助指针,存储遍历过程中当前指针指向的下一个元素,然后将当前节点元素的指针反转后,利用已经存储的指针往后面继续遍历。源代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

struct linka {

  int data;

  linka* next;

};

void reverse(linka*& head)

{

  if(head ==NULL)

  return;

  linka*pre, *cur, *ne;

  pre=head;

  cur=head->next;

  while(cur)

  {

  ne = cur->next;

  cur->next = pre;

  pre = cur;

  cur = ne;

  }

  head->next = NULL;

  head = pre;

}

还有一种利用递归的方法。这种方法的基本思想是在反转当前节点之前先调用递归函数反转后续节点。源代码如下。不过这个方法有一个缺点,就是在反转后的最后一个结点会形成一个环,所以必须将函数的返回的节点的next域置为NULL。因为要改变head指针,所以我用了引用。算法的源代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

linka* reverse(linka* p,linka*& head)

{

  if(p == NULL || p->next == NULL)

  {

  head=p;

  return p;

  }

  else

  {

  linka* tmp = reverse(p->next,head);

  tmp->next = p;

  return p;

  }

}

3,判断两个数组中是否存在相同的数字给定两个排好序的数组,怎样高效得判断这两个数组中存在相同的数字?
这个问题首先想到的是一个O(nlogn)的算法。就是任意挑选一个数组,遍历这个数组的所有元素,遍历过程中,在另一个数组中对第一个数组中的每个元素进行binary search。用C++实现代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

bool findcommon(int a[],int size1,int b[],int size2)

{

  int i;

  for(i=0;i<size1;i++)

  {

  int start=0,end=size2-1,mid;

  while(start<=end)

  {

   mid=(start+end)/2;

   if(a[i]==b[mid])

    return true;

   else if (a[i]<b[mid])

    end=mid-1;

   else

    start=mid+1;

  }

  }

  return false;

}

后来发现有一个 O(n)算法。因为两个数组都是排好序的。所以只要一次遍历就行了。首先设两个下标,分别初始化为两个数组的起始地址,依次向前推进。推进的规则是比较两个数组中的数字,小的那个数组的下标向前推进一步,直到任何一个数组的下标到达数组末尾时,如果这时还没碰到相同的数字,说明数组中没有相同的数字。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

bool findcommon2(int a[], int size1, int b[], int size2)

{

  int i=0,j=0;

  while(i<size1 && j<size2)

  {

  if(a[i]==b[j])

    return true;

  if(a[i]>b[j])

   j++;

  if(a[i]<b[j])

   i++;

  }

  return false;

}

4,最大子序列问题:
给定一整数序列A1, A2,... An (可能有负数),求A1~An的一个子序列Ai~Aj,使得Ai到Aj的和最大
例如:
整数序列-2, 11, -4, 13, -5, 2, -5, -3, 12, -9的最大子序列的和为21。
对于这个问题,最简单也是最容易想到的那就是穷举所有子序列的方法。利用三重循环,依次求出所有子序列的和然后取最大的那个。当然算法复杂度会达到O(n^3)。显然这种方法不是最优的,下面给出一个算法复杂度为O(n)的线性算法实现,算法的来源于Programming Pearls一书。在给出线性算法之前,先来看一个对穷举算法进行优化的算法,它的算法复杂度为O(n^2)。其实这个算法只是对对穷举算法稍微做了一些修改:其实子序列的和我们并不需要每次都重新计算一遍。假设Sum(i, j)是A[i] ... A[j]的和,那么Sum(i, j+1) = Sum(i, j) + A[j+1]。利用这一个递推,我们就可以得到下面这个算法:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

int max_sub(int a[],int size)

{

  int i,j,v,max=a[0];

  for(i=0;i<size;i++)

  {

  v=0;

  for(j=i;j<size;j++)

  {

   v=v+a[j];//Sum(i, j+1) = Sum(i, j) + A[j+1]

   if(v>max)

    max=v;

  }

  }

  return max;

}

那怎样才能达到线性复杂度呢?这里运用动态规划的思想。先看一下源代码实现:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

int max_sub2(int a[], int size)

{

  int i,max=0,temp_sum=0;

  for(i=0;i<size;i++)

  {

  temp_sum+=a[i];

  if(temp_sum>max)

   max=temp_sum;

  else if(temp_sum<0)

   temp_sum=0;

  }

  return max;

}

6,按单词反转字符串并不是简单的字符串反转,而是按给定字符串里的单词将字符串倒转过来,就是说字符串里面的单词还是保持原来的顺序,这里的每个单词用空格分开。
如果只是简单的将所有字符串翻转的话,可以遍历字符串,将第一个字符和最后一个交换,第二个和倒数第二个交换,依次循环。其实按照单词反转的话可以在第一遍遍历的基础上,再遍历一遍字符串,对每一个单词再反转一次。这样每个单词又恢复了原来的顺序。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

char* reverse_word(const char* str)

{

  int len = strlen(str);

  char* restr = new char[len+1];

  strcpy(restr,str);

  int i,j;

  for(i=0,j=len-1;i<j;i++,j--)

  {

  char temp=restr[i];

  restr[i]=restr[j];

  restr[j]=temp;

  }

  int k=0;

  while(k<len)

  {

  i=j=k;

  while(restr[j]!=' ' && restr[j]!='' )

   j++;

  k=j+1;

  j--;

  for(;i<j;i++,j--)

  {

   char temp=restr[i];

   restr[i]=restr[j];

   restr[j]=temp;

  }

  }

  return restr;

}

如果考虑空间和时间的优化的话,当然可以将上面代码里两个字符串交换部分改为异或实现。

例如将

?

1

2

3

char temp=restr[i];

 restr[i]=restr[j];

 restr[j]=temp;

改为

?

1

2

3

4

restr[i]^=restr[j];

  restr[j]^=restr[i];

 restr[i]^=restr[j];

7,字符串反转我没有记错的话是一道MSN的笔试题,网上无意中看到的,拿来做了一下。题目是这样的,给定一个字符串,一个这个字符串的子串,将第一个字符串反转,但保留子串的顺序不变。例如:

输入:第一个字符串: "This is mianwww's Chinese site:http://www.mianwww.com.cn/cn"

子串: "mianwww"

输出: "nc/nc.moc.mianwww.www//:ptth :etis esenihCs'mianwww si sihT"

一般的方法是先扫描一边第一个字符串,然后用stack把它反转,同时记录下子串出现的位置。然后再扫描一遍把记录下来的子串再用stack反转。我用的方法是用一遍扫描数组的方法。扫描中如果发现子串,就将子串倒过来压入堆栈。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

最后再将堆栈里的字符弹出,这样子串又恢复了原来的顺序。源代码如下:

#include <iostream>

#include <cassert>

#include <stack>

using namespace std;

//reverse the string 's1' except the substring 'token'.

const char* reverse(const char* s1, const char* token)

{

  assert(s1 && token);

  stack<char> stack1;

  const char* ptoken = token, *head = s1, *rear = s1;

  while (*head != '')

  {

    while(*head!= '' && *ptoken == *head)

    {

    ptoken++;

    head++;

    }

    if(*ptoken == '')//contain the token

    {

    const char* p;

    for(p=head-1;p>=rear;p--)

      stack1.push(*p);

 

    ptoken = token;

    rear = head;

    }

    else

    {

    stack1.push(*rear);

     head=++rear;

    ptoken = token;

    }

  }

  char * return_v = new char[strlen(s1)+1];

  int i=0;

  while(!stack1.empty())

  {

    return_v[i++] = stack1.top();

    stack1.pop();

  }

  return_v[i]='';

  return return_v;

}

int main(int argc, char* argv[])

{cout<<"This is mianwww 's Chinese site: http://www.mianwww.com.cn/cn

";

  cout<<reverse("This is mianwww's Chinese site: http://www. mianwww.com.cn/cn"," mianwww ");

  return 0;

}

8, 删除数组中重复的数字问题:一个动态长度可变的数字序列,以数字0为结束标志,要求将重复的数字用一个数字代替,例如:

将数组 1,1,1,2,2,2,2,2,7,7,1,5,5,5,0 转变成1,2,7,1,5,0 问题比较简单,要注意的是这个数组是动态的。所以避免麻烦我还是用了STL的vector。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

#include <iostream>

#include <vector>

using namespace std;

//remove the duplicated numbers in an intger array, the array was end with 0;

//e.g. 1,1,1,2,2,5,4,4,4,4,1,0 --->1,2,5,4,1,0

void static remove_duplicated(int a[], vector<int>& _st)

{

  _st.push_back(a[0]);

  for(int i=1;_st[_st.size()-1]!=0;i++)

  {

    if(a[i-1]!=a[i])

    _st.push_back(a[i]);

  }

}

当然如果可以改变原来的数组的话,可以不用STL,仅需要指针操作就可以了。下面这个程序将修改原来数组的内容。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

void static remove_duplicated2(int a[])

{

  if(a[0]==0 || a==NULL)

    return;

  int insert=1,current=1;

  while(a[current]!=0)

  {

    if(a[current]!=a[current-1])

    {

    a[insert]=a[current];

    insert++;

    current++;

    }

    else

    current++;

  }

  a[insert]=0;

}

9,如何判断一棵二叉树是否是平衡二叉树问题:判断一个二叉排序树是否是平衡二叉树解决方案:
根据平衡二叉树的定义,如果任意节点的左右子树的深度相差不超过1,那这棵树就是平衡二叉树。
首先编写一个计算二叉树深度的函数,利用递归实现。

?

1

2

3

4

5

6

7

8

9

10

11

12

template<typename T>

static int Depth(BSTreeNode<T>* pbs)

{

  if (pbs==NULL)

    return 0;

  else

  {

    int ld = Depth(pbs->left);

    int rd = Depth(pbs->right);

    return 1 + (ld >rd ? ld : rd);

  }

}

下面是利用递归判断左右子树的深度是否相差1来判断是否是平衡二叉树的函数:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

template<typename T>

static bool isBalance(BSTreeNode<T>* pbs)

{

  if (pbs==NULL)

    return true;

  int dis = Depth(pbs->left) - Depth(pbs->right);

  if (dis>1 || dis<-1 )

    return false;

  else

    return isBalance(pbs->left) && isBalance(pbs->right);

4.abstract class Something {

 private abstract String doSomething ();

 }

该段代码有错吗?
答案: 错。abstract的methods不能以private修饰。abstract的methods就是让子类implement(实现)具体细节的,怎么可以用private把abstractmethod封锁起来呢? (同理,abstractmethod前不能加final)。
5.看看下面的代码段错在哪里?

?

1

2

3

4

5

6

public class Something {

void doSomething () {

private String s = “”;

int l = s.length();

}

}

答案: 错。局部变量前不能放置任何访问修饰符 (private,public,和protected)。final可以用来修饰局部变量
(final如同abstract和strictfp,都是非访问修饰符,strictfp只能修饰class和method而非variable)。
6. 下面该段代码是否有错,若是有错错在哪里?

?

1

2

3

4

abstract class Name {

private String name;

public abstract boolean isStupidName(String name) {}

}

答案: 错。abstract method必须以分号结尾,且不带花括号。

 

java数据结构与算法003相关推荐

  1. Java 数据结构与算法系列之冒泡排序

    一.前言 相信大部分同学都已经学过数据结构与算法这门课了,并且我们可能都会发现一个现象就是我们所学过的数据结构与算法类的书籍基本都是使用 C 语言来写的,好像没见过使用 Java 写的数据结构与算法. ...

  2. Java数据结构与算法——树(基本概念,很重要)

    声明:码字不易,转载请注明出处,欢迎文章下方讨论交流. 有网友私信我,期待我的下一篇数据结构.非常荣幸文章被认可,也非常感谢你们的监督. 前言:Java数据结构与算法专题会不定时更新,欢迎各位读者监督 ...

  3. Java数据结构与算法——插入排序

    声明:码字不易,转载请注明出处,欢迎文章下方讨论交流. 前言:Java数据结构与算法专题会不定时更新,欢迎各位读者监督.本篇文章介绍排序算法中插入排序算法,包括插入排序的思路,适用场景,性能分析,ja ...

  4. Java数据结构和算法(六)——前缀、中缀、后缀表达式

    前面我们介绍了三种数据结构,第一种数组主要用作数据存储,但是后面的两种栈和队列我们说主要作为程序功能实现的辅助工具,其中在介绍栈时我们知道栈可以用来做单词逆序,匹配关键字符等等,那它还有别的什么功能吗 ...

  5. Java数据结构和算法(一)——简介

    本系列博客我们将学习数据结构和算法,为什么要学习数据结构和算法,这里我举个简单的例子. 编程好比是一辆汽车,而数据结构和算法是汽车内部的变速箱.一个开车的人不懂变速箱的原理也是能开车的,同理一个不懂数 ...

  6. JAVA数据结构与算法【简单介绍】

    前几天去面一个大厂,面试官特别好,面试官说到,我们的学习不能本末倒置,数据结构和算法是程序的基础,如果数据结构你没有学好,你真正意义上不算会写代码.你的代码是各处粘贴,杂乱无章的. 由于现在大多用JA ...

  7. java算法概述,Java数据结构与算法基础(一)概述与线性结构

    Java数据结构与算法基础(二)递归算法 Java数据结构与算法基础(一)概述与线性结构 学习目的:为了能更顺畅的读很多底层API代码和拓宽解决问题的思路 一.数据结构概述 1.数据结构是什么?数据与 ...

  8. 【笔记】Java数据结构与算法

    [笔记]Java数据结构与算法 文章目录 [笔记]Java数据结构与算法 1.八大排序应用场景 2.未完待续-- 1.八大排序应用场景 冒泡排序:优化后的冒泡排序可用于当数据已经基本有序,且数据量较小 ...

  9. 二叉树 BinaryTree (先序、中序、后序遍历 节点查找、插入、删除 完整类) Java数据结构与算法

    二叉树 BinaryTree (先序.中序.后序遍历 节点查找.插入.删除 完整类) Java数据结构与算法 源代码: view plain /** * * @author sunnyykn */ i ...

最新文章

  1. windows使用.NET CORE下创建MVC,发布到linux运行
  2. FreeTextBox的ToolbarButton整理
  3. python stm32-STM32F4系列使用MicroPython开发
  4. 国企营业收入逾17万亿 同比增长24.2%
  5. SQLSERVER语句的执行时间
  6. Ionic2学习笔记
  7. Boost Asio dispatch()与post()的区别
  8. 丰城九中2021高考成绩查询,丰城九中2021届毕业典礼
  9. 南京审计大学计算机考研专业课答案
  10. STM32 驱动液晶LCD12864
  11. Day14-正则表达式
  12. 微软翻译离线简体中文服务器,微软翻译新增离线翻译功能,但使用体验并没有想象中的好...
  13. CSS - 移动Web网页开发(2)- 必掌握知识点 - #博学谷IT学习技术支持#
  14. 基于vue利用openlayers加载天地图的影像图,地形图
  15. 《数据挖掘导论(完整版)》习题答案导航_补档
  16. 巧用clang 的sanitize解决realloc,malloc,calloc失败
  17. 游戏建模在国内的发展前景,3D建模行业真的很缺人吗?
  18. So Cool LINUX 3D桌面
  19. win10重建图标缓存bat
  20. uni-app支付宝微信支付

热门文章

  1. 怒怼七夕甜言蜜语,vivo“引发”情侣和单身狗的对战
  2. 《笨办法学python》第36课——设计和调试
  3. docker镜像/容器的基本命令
  4. 贪婪模式和非贪婪模式
  5. Python爬虫4.4 — selenium高级用法教程
  6. layui分页失效问题
  7. 公共rtsp_常见品牌网络摄像机的端口及RTSP地址
  8. java 致命错误_Java运行时环境检测到致命错误:SIGSEGV(0xb)
  9. 在packagist上发布composer包
  10. 小米9pro textView不设置颜色不显示_小米万象息屏2.0内测开启,新增小组件功能和新息屏...