数据结构与算法分析二 (排序与查找)

排序
时间频度T(n)
时间复杂度O(f(n))

排序方法 平均时间复杂度 最差时间复杂度
冒泡排序 O(n^2) O(n^2)
选择排序 centered $12
zebra stripes are neat $1

冒泡排序

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = new int[80000];
        for(int i =0; i < 80000;i++) {
            arr[i] = (int)(Math.random() * 8000000); //生成一个[0, 8000000) 数
        }
        
        Date data1 = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date1Str = simpleDateFormat.format(data1);
        System.out.println("排序前的时间是=" + date1Str);
        
        //测试冒泡排序
        sort2(arr);
        
        Date data2 = new Date();
        String date2Str = simpleDateFormat.format(data2);
        System.out.println("排序后的时间是=" + date2Str);
        // 测试时间 8秒
    }
    
    //普通版冒泡排序
    public static  void sort1(int [] arr) {
        int length = arr.length;
        for(int i=0 ; i<length-1 ; i++) {  //进行length -1 轮循环, 第一趟排出最大的数 ,第二趟排出第二大的数  依次。。。
            for(int j =0;j<(length-1)- i;j++) {//第一轮需要比较4次,第二轮需要比较3次 第n轮需要比较(length-1)-i次
                if(arr[j]>arr[j+1]) {
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
                System.out.println("排序后的结果"+Arrays.toString(arr));
            }
            System.out.println("第"+(i+1)+"趟排序后的结果"+Arrays.toString(arr));
        }
    }
    
    /**
     * 优化版冒泡排序  
     * 上面普通版的排序 在第1轮排序后得到的结果是[-10, 3, 7, 8, 22]
     * 其实,这就是最终结果,后面的排序是不必要的。因此我们可以将排序结束
     */
    public static  void sort2(int [] arr) {
        int length = arr.length;
        for(int i=0 ; i<length-1 ; i++) {  //进行length -1 轮循环, 第一趟排出最大的数 ,第二趟排出第二大的数  依次。。。
            boolean flag = false;
            for(int j =0;j<(length-1)- i;j++) {//第一轮需要比较4次,第二轮需要比较3次 第n轮需要比较(length-1)-i次
                if(arr[j]>arr[j+1]) {
                    flag = true;
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
            
            if(!flag) {
                break;
            }else {
                flag = false;//重置flag
            }
        }
    }
}

选择排序

选择排序思想, 以升序排序为例子,选择排序是第一次 找出最小的数,这个数与arr[0]进行交换,第二次选出除arr[0]之外的最小数 这个数与arr[1]交换,第三轮 选出除arr[0],arr[1]之外的最小数 这个数与arr[2]进行交换,依次类推。。。 这就是选择排序,其时间复杂度为:

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
//选择排序
public class SelectSort {

    public static void main(String[] args) {
//      int [] arr = {101, 34, 119, 1, -1, 90, 123};
//      selectSort(arr);
//      
        //创建要给80000个的随机的数组
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
        }
        
        System.out.println("排序前");
        
        Date data1 = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date1Str = simpleDateFormat.format(data1);
        System.out.println("排序前的时间是=" + date1Str);
        
        selectSort(arr);
        
        Date data2 = new Date();
        String date2Str = simpleDateFormat.format(data2);
        System.out.println("排序后的时间是=" + date2Str);
        //测试时间 1秒
    }
    
    //选择排序
    public static void selectSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            int minIndex = i;//记录最小数的索引
            int min = arr[i];// 记录最小数
            
            /**
             * 随着j的增加 min的值越来越小 ,导致在后面的比较中,碰到比min更小数的概率越来越低 ,
             * 因此减少了交换次数 这是选择排序比冒泡排序快的原因  ,然而其时间复杂度依然是 O(n^2), 
             * 因为其减少的是交换次数 ,而不是比较次数
             */
            for (int j = i + 1; j < arr.length; j++) { 
                if (min > arr[j]) { // 说明假定的最小值,并不是最小
                    min = arr[j]; // 重置min
                    minIndex = j; // 重置minIndex
                }
            }

            // 将最小值,放在arr[0], 即交换
            if (minIndex != i) {
                arr[minIndex] = arr[i];
                arr[i] = min;
            }
//
//          System.out.println("第"+(i+1)+"轮后~~");
//          System.out.println(Arrays.toString(arr));// 1, 34, 119, 101
        }
    }
}

插入排序

插入排序思想: 以升序为例子, 插入排序是这样的 将一个无序数组看作两个部分,一部分有序 一部分无序,最开始的时候arr[0] 为有序部分 并且这个有序部分就这一个元素,其余的arr[1] ~ arr[length-1]作为无序部分,这是最初始化的状态 ,然后我们从
1 到(length-1)遍历,依次将arr[1],arr[2]... 加入到有序部分中去,加入的时候我们需要找到待加入的数在有序部分的位置,具体步骤是这样的:我们将待加入的数与有序部分的最后一个数(arr[insertIndex])比较,如果arr[insertIndex]比待加入的数大,那么将arr[insertIndex]后移,然后待插入的数与arr[insertIndex-1]比较 依次比较即可找到待插入数据的位置的前一个数arr[target],我们将待插入的数放到arr[target+1]上去 即可

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class InsertSort {
    public static void main(String[] args) {
//      int[] arr = {101, 34, 119, 1, -1,101,34, 89}; 
//      insertSort(arr) ;
//      System.out.println(Arrays.toString(arr));
        
        // 创建要给80000个的随机的数组
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
        }

        System.out.println("插入排序前");
        Date data1 = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date1Str = simpleDateFormat.format(data1);
        System.out.println("排序前的时间是=" + date1Str);
        
        insertSort(arr); //调用插入排序算法
        
        Date data2 = new Date();
        String date2Str = simpleDateFormat.format(data2);
        System.out.println("排序前的时间是=" + date2Str);
        //测试时间  2秒
    }
    
    //插入排序
    public static void insertSort(int[] arr) {
        int insertVal = 0;
        int insertIndex = 0;
        for(int i = 1; i < arr.length; i++) {
            
            insertVal = arr[i];//定义待插入的数
            insertIndex = i - 1; // 即待插入数据的前一个数据
    
            // 给insertVal 找到插入的位置
            // 说明
            // 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界
            // 2. insertVal < arr[insertIndex] 待插入的数,说明还没有找到插入位置
            // 3. 就需要将 arr[insertIndex]数据 后移
            while (insertIndex >= 0 && insertVal < arr[insertIndex]) { //这里数据交换的次数明显比选择排序多,选择排序是每一轮只需要交换一次 
                arr[insertIndex + 1] = arr[insertIndex];//将被比较的数后移
                insertIndex--;
            }
            // 当退出while循环时,说明插入的位置找到, insertIndex + 1
            // 举例:理解不了,我们一会 debug
            //这里我们判断是否需要赋值
            if(insertIndex + 1 != i) {
                arr[insertIndex + 1] = insertVal;
            }
    
            //System.out.println("第"+i+"轮插入");
            //System.out.println(Arrays.toString(arr));
        }
    } 
}

我们来看插入排序存在的问题 对于数组{2,6,8,9,5,1},我们在处理1的时候,需要移动5,9,8,6,2。当插入的数据是比较小的数的时候,后移的次数明显增多,这对效率有明显影响。

希尔排序:希尔排序是将记录按照下标的一定增量分组,对每一组进行插入排序算法排序,随着增量的逐渐减少,当增量减小为1时,每个整个数组恰好被分为一组,算法结束


image.png

image.png
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class ShellSort {

    public static void main(String[] args) {
//      int[] arr = { 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 };
//      
//      shellSort(arr);//移位方式
        
        // 创建要给80000个的随机的数组
        int[] arr = new int[80000];
        for (int i = 0; i < 80000; i++) {
            arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
        }

        System.out.println("排序前");
        Date data1 = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date1Str = simpleDateFormat.format(data1);
        System.out.println("排序前的时间是=" + date1Str);
        
//      shellSort(arr); //交换式    测试时间  6秒
        shellSort2(arr);//移位方式 测试时间 几乎0秒
        
        Date data2 = new Date();
        String date2Str = simpleDateFormat.format(data2);
        System.out.println("排序前的时间是=" + date2Str);
        
        //System.out.println(Arrays.toString(arr));
    }

    // 使用逐步推导的方式来编写希尔排序
    // 希尔排序时, 对有序序列在插入时采用交换法, 
    // 思路(算法) ===> 代码
    public static void shellSort(int[] arr) {
        int temp = 0;
        // 根据前面的逐步分析,使用循环处理
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < arr.length; i++) {
                // 遍历各组中所有的元素(共gap组,每组有个元素), 步长gap
                for (int j = i - gap; j >= 0; j -= gap) {
                    // 如果当前元素大于加上步长后的那个元素,说明交换
                    if (arr[j] > arr[j + gap]) {
                        temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
//          System.out.println("希尔排序第" + (++count) + "轮 =" + Arrays.toString(arr));
        }
    }
    
    //对交换式的希尔排序进行优化->移位法
    public static void shellSort2(int[] arr) {
        
        // 增量gap, 并逐步的缩小增量
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            // 从第gap个元素,逐个对其所在的组进行直接插入排序
            for (int i = gap; i < arr.length; i++) {
                int j = i;
                int temp = arr[j];
                if (arr[j] < arr[j - gap]) {
                    while (j - gap >= 0 && temp < arr[j - gap]) {
                        //移动
                        arr[j] = arr[j-gap];
                        j -= gap;
                    }
                    //当退出while后,就给temp找到插入的位置
                    arr[j] = temp;
                }
            }
        }
    }
}

快速排序

快速排序是对冒泡排序的一种改进,基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的数据都比另外一部分的数据小,然后再通过这个方法将这两部分的数据分部进行快速排序,从而到达整个数组有序。示意图如下:


image.png

图中是以最后一个数为基准,我们以中间的数字为基准来写代码

import java.util.Arrays;

public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {-9,-21,-1,-2,0,12,33,-56,-3,44,1,3,5};
        
        quickSort(arr, 0, arr.length-1);
        System.out.println("arr=" + Arrays.toString(arr));
        
//      //测试快排的执行速度
//      // 创建要给80000个的随机的数组
//      int[] arr = new int[80000];
//      for (int i = 0; i < 80000; i++) {
//          arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
//      }
//      
//      System.out.println("排序前");
//      Date data1 = new Date();
//      SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//      String date1Str = simpleDateFormat.format(data1);
//      System.out.println("排序前的时间是=" + date1Str);
//      
//      quickSort(arr, 0, arr.length-1);//测试时间 不到1秒
//      
//      Date data2 = new Date();
//      String date2Str = simpleDateFormat.format(data2);
//      System.out.println("排序前的时间是=" + date2Str);
//      //System.out.println("arr=" + Arrays.toString(arr));
    }
    
    
    public  static void quickSort(int [] arr,int left,int right){
        int temp = 0;//定义一个交换的数
        int l = left;
        int r = right;
        int pivot = arr[(l + r) / 2];
        while(l < r) {
            while(arr[l] < pivot) {//在pivot 左侧找出一个大于或者等于pivot的数
                l = l+1;
            }
            
            while(arr[r] > pivot) {//在pivot 右侧找出一个小于或者等于pivot的数
                r= r-1;
            }
            
            /**
             * 如果左索引都大于有索引了 说明它们的循环查找的时候已经碰头了
             * 这个时候左边的数据都小于等于pivot,右边的数据都大于等于pivot
             */
            if(l >= r) { 
                 break;
            }
            //两个数进行交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
            
            //如果arr[l] 与pivot相等,那么要将索引向前推进一位,否则arr[l]这个数会导致上面的while循环一直退出 形成死循环
            //因为arr[i] 是由arr[r]交换得来的 所以我们将r左进一位
            if(arr[l] == pivot) {
                r = r-1;
            }
            // 同理 我们让i右移一位
            if(arr[r] == pivot) {
                l = l+1;
            }
        }
        if(l ==r) {//如果左索引和右索引重合 我们需要将他们错开,其实重合索引对应的那个数就是privot,而
// 这个privot是已经确定好位置了的 我们不需要在将他放进来比较
            l = l+1;
            r = r-1;
        }
        //将左部分的数据 进行递归
        if(left < r) {
            quickSort(arr, left, r);
        }
        
        if(l<right) {
            quickSort(arr, l, right);
        }
    }
}

归并排序
归并排序采用经典的分治策略,分:将一个问题分成小问题 然后递归求解,治:将将分之后的结果"修补"在一起:思路示意图如下


image.png
image.png
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class MergetSort {

    public static void main(String[] args) {
        //int arr[] = { 8, 4, 5, 7, 1, 3, 6, 2 }; //
        
        //测试快排的执行速度
        // 创建要给80000个的随机的数组
        int[] arr = new int[8000000];
        for (int i = 0; i < 8000000; i++) {
            arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
        }
        System.out.println("排序前");
        Date data1 = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date1Str = simpleDateFormat.format(data1);
        System.out.println("排序前的时间是=" + date1Str);
        
        int temp[] = new int[arr.length]; //归并排序需要一个额外空间
        mergeSort(arr, 0, arr.length - 1, temp);
        
        Date data2 = new Date();
        String date2Str = simpleDateFormat.format(data2);
        System.out.println("排序前的时间是=" + date2Str);
        
        //System.out.println("归并排序后=" + Arrays.toString(arr));
    }
    
    
    //分+合方法
    public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if(left < right) {
            int mid = (left + right) / 2; //中间索引
            //向左递归进行分解
            mergeSort(arr, left, mid, temp);
            //向右递归进行分解
            mergeSort(arr, mid + 1, right, temp);
            //每分解一轮就合并一轮
            merge(arr, left, mid, right, temp);
            
        }
    }
    
    //合并的方法
    /**
     * 
     * @param arr 排序的原始数组
     * @param left 左边有序序列的初始索引
     * @param mid 中间索引
     * @param right 右边索引
     * @param temp 做中转的数组
     */
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        
        int i = left; // 初始化i, 左边有序序列的初始索引
        int j = mid + 1; //初始化j, 右边有序序列的初始索引
        int t = 0; // 指向temp数组的当前索引
        
        //(一)
        //先把左右两边(有序)的数据按照规则填充到temp数组
        //直到左右两边的有序序列,有一边处理完毕为止
        while (i <= mid && j <= right) {//继续
            //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
            //即将左边的当前元素,填充到 temp数组 
            //然后 t++, i++
            if(arr[i] <= arr[j]) {
                temp[t] = arr[i];
                t += 1;
                i += 1;
            } else { //反之,将右边有序序列的当前元素,填充到temp数组
                temp[t] = arr[j];
                t += 1;
                j += 1;
            }
        }
        
        //(二)
        //把有剩余数据的一边的数据依次全部填充到temp
        while( i <= mid) { //左边的有序序列还有剩余的元素,就全部填充到temp
            temp[t] = arr[i];
            t += 1;
            i += 1; 
        }
        
        while( j <= right) { //右边的有序序列还有剩余的元素,就全部填充到temp
            temp[t] = arr[j];
            t += 1;
            j += 1; 
        }
        
        
        //(三)
        //将temp数组的元素拷贝到arr
        //注意,并不是每次都拷贝所有
        t = 0;
        int tempLeft = left; // 
        //第一次合并 tempLeft = 0 , right = 1 //  tempLeft = 2  right = 3 // tL=0 ri=3
        //最后一次 tempLeft = 0  right = 7
        while(tempLeft <= right) { 
            arr[tempLeft] = temp[t];
            t += 1;
            tempLeft += 1;
        }
    }
}

基数排序

基数排序基本思想 :将所有带比较的数值统一为二位同样长度的数字,然后由最低位开始 依次进行进行排序,这样从最低位排序一直到最高位排序完成,数据就变成了一个有序的序列。其实基数排序利用的就是 一个数a如果大于一个数b,那么a的高位数字大于b的高位数字或者二者的高位数字相等 但是a的低位数字肯定比b的低位数字大。


image.png

image.png

基数排序的特性

  • 速度非常快,800万条数据排序只需1秒
  • 基数排序采用的是空间换时间策略,占用内存比较大 ,容易造成OutOfMemoryError
  • 基数排序是稳定的排序,假如 arr[i] = arr[i+m] = k,经过排序之后arr[i] 依然排在arr[i+m]之前
  • 有负数的数组不能采用基数排序,如果需要支持负数 参考https://code.i-harness.com/zh-CN/q/e98fa9
public class RadixSort {

    public static void main(String[] args) {
        int arr[] = { 53, 3, 542, 748, 14, 214};
        
        // 80000000 * 11 * 4 / 1024 / 1024 / 1024 =3.3G 
//      int[] arr = new int[8000000];
//      for (int i = 0; i < 8000000; i++) {
//          arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
//      }
        System.out.println("排序前");
        Date data1 = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date1Str = simpleDateFormat.format(data1);
        System.out.println("排序前的时间是=" + date1Str);
        
        radixSort(arr);
        
        Date data2 = new Date();
        String date2Str = simpleDateFormat.format(data2);
        System.out.println("排序前的时间是=" + date2Str);
        
        System.out.println("基数排序后 " + Arrays.toString(arr));
        
    }

    //基数排序方法
    public static void radixSort(int[] arr) {
        //根据前面的推导过程,我们可以得到最终的基数排序代码
        
        //1. 得到数组中最大的数的位数
        int max = arr[0]; //假设第一数就是最大数
        for(int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        //得到最大数是几位数
        int maxLength = (max + "").length();
        
        
        //定义一个二维数组,表示10个桶, 每个桶就是一个一维数组
        //说明
        //1. 二维数组包含10个一维数组
        //2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
        //3. 名明确,基数排序是使用空间换时间的经典算法 
        int[][] bucket = new int[10][arr.length];
        
        //为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
        //可以这里理解
        //比如:bucketElementCounts[0] , 记录的就是  bucket[0] 桶的放入数据个数
        int[] bucketElementCounts = new int[10];
        
        //这里我们使用循环将代码处理
        
        for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {
            //(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位..
            for(int j = 0; j < arr.length; j++) {
                //取出每个元素的对应位的值
                int digitOfElement = arr[j] / n % 10;
                //放入到对应的桶中
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                bucketElementCounts[digitOfElement]++;
            }
            //按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
            int index = 0;
            //遍历每一桶,并将桶中是数据,放入到原数组
            for(int k = 0; k < bucketElementCounts.length; k++) {
                //如果桶中,有数据,我们才放入到原数组
                if(bucketElementCounts[k] != 0) {
                    //循环该桶即第k个桶(即第k个一维数组), 放入
                    for(int l = 0; l < bucketElementCounts[k]; l++) {
                        //取出元素放入到arr
                        arr[index++] = bucket[k][l];
                    }
                }
                //第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
                bucketElementCounts[k] = 0;
                
            }
            //System.out.println("第"+(i+1)+"轮,对个位的排序处理 arr =" + Arrays.toString(arr));
        }
    }
}
image.png

image.png
查找算法

线性查找

线性查找:一个一个的去查找,不要求数组有序

public class SeqSearch {

    public static void main(String[] args) {
        int arr[] = { 1, 9, 11, -1, 34, 89 };// 没有顺序的数组
        int index = seqSearch(arr, -11);
        if(index == -1) {
            System.out.println("没有找到到");
        } else {
            System.out.println("找到,下标为=" + index);
        }
    }

    //这里我们实现的线性查找是找到一个满足条件的值,就返回
    public static int seqSearch(int[] arr, int value) {
        // 线性查找是逐一比对,发现有相同值,就返回下标
        for (int i = 0; i < arr.length; i++) {
            if(arr[i] == value) {
                return i;
            }
        }
        return -1;
    }
}

二分查找

二分查找:思路


image.png
import java.util.ArrayList;
import java.util.List;

//注意:使用二分查找的前提是 该数组是有序的.
public class BinarySearch {

    public static void main(String[] args) {
        //int arr[] = { 1, 8, 10, 89,1000,1000, 1234 };
        int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10  };
        //
        int resIndex = binarySearch(arr, 0, arr.length - 1, 1000);
        System.out.println("resIndex=" + resIndex);
        
//      List<Integer> resIndexList = binarySearch2(arr, 0, arr.length - 1, 1);
//      System.out.println("resIndexList=" + resIndexList);
    }

    // 二分查找算法
    /**
     * 
     * @param arr 数组
     * @param left 左边的索引
     * @param right 右边的索引
     * @param findVal 要查找的值
     * @return 如果找到就返回下标,如果没有找到,就返回 -1
     */
    public static int binarySearch(int[] arr, int left, int right, int findVal) {
        //在整个过程中 左递归right = mid -1 导致right越来越小,右递归 left = mid+1 导致left越来越大
        // 当 left > right 时,说明递归整个数组,但是没有找到
        if (left > right) {
            return -1;
        }
        int mid = (left + right) / 2;
        int midVal = arr[mid];

        if (findVal > midVal) { // 向 右递归
            return binarySearch(arr, mid + 1, right, findVal);
        } else if (findVal < midVal) { // 向左递归
            return binarySearch(arr, left, mid - 1, findVal);
        } else {
            return mid;
        }
    }
    
    /*
     *  {1,8, 10, 89, 1000, 1000,1234} 当一个有序数组中,
     * 有多个相同的数值时,如何将所有的数值都查找到,比如这里的 1000
     * 
     * 思路分析
     * 1. 在找到mid 索引值,不要马上返回
     * 2. 向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
     * 3. 向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
     * 4. 将Arraylist返回
     */
    public static List<Integer> binarySearch2(int[] arr, int left, int right, int findVal) {
        System.out.println("hello~");
        // 当 left > right 时,说明递归整个数组,但是没有找到
        if (left > right) {
            return new ArrayList<Integer>();
        }
        int mid = (left + right) / 2;
        int midVal = arr[mid];

        if (findVal > midVal) { // 向 右递归
            return binarySearch2(arr, mid + 1, right, findVal);
        } else if (findVal < midVal) { // 向左递归
            return binarySearch2(arr, left, mid - 1, findVal);
        } else {
//           * 思路分析
//           * 1. 在找到mid 索引值,不要马上返回
//           * 2. 向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
//           * 3. 向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
//           * 4. 将Arraylist返回
            
            List<Integer> resIndexlist = new ArrayList<Integer>();
            //向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
            int temp = mid - 1;
            while(true) {
                if (temp < 0 || arr[temp] != findVal) {//退出
                    break;
                }
                //否则,就temp 放入到 resIndexlist
                resIndexlist.add(temp);
                temp -= 1; //temp左移
            }
            resIndexlist.add(mid);  //
            
            //向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
            temp = mid + 1;
            while(true) {
                if (temp > arr.length - 1 || arr[temp] != findVal) {//退出
                    break;
                }
                //否则,就temp 放入到 resIndexlist
                resIndexlist.add(temp);
                temp += 1; //temp右移
            }
            return resIndexlist;
        }
    }
}

插值查找

插值查找类似于二分查找 ,二分查找每次都是从中间mid开始比较查找,插值查找是从自适应位置mid开始比较查找的


image.png

image.png
public class InsertValueSearch {

    public static void main(String[] args) {
        
//      int [] arr = new int[100];
//      for(int i = 0; i < 100; i++) {
//          arr[i] = i + 1;
//      }
        
        int arr[] = { 1,2,3,4,5,6,7, 8, 10, 89,90,91, 1234,22222,55555,67891,999999 };
        
        int index = insertValueSearch(arr, 0, arr.length - 1, 8);//插值查找需要查找8次
//      int index = binarySearch(arr, 0, arr.length, 1);//二分查找需要查找4次
        System.out.println("index = " + index);
        
        //System.out.println(Arrays.toString(arr));
    }

    //编写插值查找算法
    //说明:插值查找算法,也要求数组是有序的
    /**
     * 
     * @param arr 数组
     * @param left 左边索引
     * @param right 右边索引
     * @param findVal 查找值
     * @return 如果找到,就返回对应的下标,如果没有找到,返回-1
     */
    public static int insertValueSearch(int[] arr, int left, int right, int findVal) { 

        System.out.println("插值查找次数~~");
        
        //注意:findVal < arr[0]  和  findVal > arr[arr.length - 1] 必须需要
        //否则我们得到的 mid 可能越界
        if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
            return -1;
        }

        // 求出mid, 自适应
        int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
        int midVal = arr[mid];
        if (findVal > midVal) { // 说明应该向右边递归
            return insertValueSearch(arr, mid + 1, right, findVal);
        } else if (findVal < midVal) { // 说明向左递归查找
            return insertValueSearch(arr, left, mid - 1, findVal);
        } else {
            return mid;
        }
    }
}

插值查找需要注意的地方是

  • 当数据有序且比较均匀,使用插值查找速度很快。
  • 当数据有序 但是不均匀,插值查找速度不一定比二分查找快。

斐波那契查找: 好难理解

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容

  • 排序算法说明 (1)排序的定义:对一序列对象根据某个关键字进行排序; 输入:n个数:a1,a2,a3,…,an 输...
    code武阅读 658评论 0 0
  • Ba la la la ~ 读者朋友们,你们好啊,又到了冷锋时间,话不多说,发车! 1.冒泡排序(Bub...
    王饱饱阅读 1,794评论 0 7
  • 本文转自公众号 「程序员私房菜 」 一直有写一篇关于排序算法文章的打算,直到我看到了这一篇,我放弃了自己写的打算,...
    KEEPINUP阅读 2,442评论 4 15
  • 引言 排序是算法恒久不变的话题,也是面试的常客,常见的排序方法有冒泡、选择、插入、堆、希尔、归并、计数、基数和快速...
    kakaxicm阅读 285评论 0 0
  • 奔向天际的 走廊。用最妖娆的身段 和山峰跳上一段 梁祝 2017.09.20 慕田峪长城上写
    赵着急_阅读 149评论 0 0