排序算法

215 阅读6分钟

二分查找法

二分查找法

二分查找法的前提是这个数据必须是有序的

二分查找思路

1、数据中间的值V与想查找的元素比较,如果没有查找到,分成两个区间,然后选择一部分查找。

image.png

2、将标定点与查找元素比较。

image.png

二分查找法(递归实现)

public class BinarySearch{
    public BinarySearch() {
    }
    public static <E extends  Comparable<E>> int search(E[] data,E target){
        return search(data,0,data.length-1,target);
    }

    private  static <E extends  Comparable<E>> int search(E[] data,int l,int r,E target){
        if(l>r) return -1;
        int mid = l +(r-l)/2 ;
        if(data[mid].compareTo(target)==0)
            return mid;
        if(data[mid].compareTo(target)<0)
            return  search(data,mid+1,r,target);
        return  search(data,l,mid-1,target);
    }
}

二分查找非递归

public class BinarySearch2 {
    public BinarySearch2() {
    }
    public static <E extends  Comparable<E>> int search(E[] data,E target){
        int l = 0, r=data.length-1;
        //在data[l,r]的范围查找target
        while (l<=r){
            int mid = l+(r-l)/2;
            if(data[mid].compareTo(target)==0)
                return mid;
            if(data[mid].compareTo(target)<0)
                l = mid +1;
            else
                r = mid - 1;
        }
        return -1;
    }
}

不同的边界条件

public static boolean exist(int[] sortedArr, int num) {
   if (sortedArr == null || sortedArr.length == 0) {
      return false;
   }
   int L = 0;
   int R = sortedArr.length - 1;
   int mid = 0;
   // L..R
   while (L < R) { // L..R 至少两个数的时候
      mid = L + ((R - L) >> 1);//=mid=(l+r)/2,数组长度到达20亿,会超标。
      //R-L/2 = L+(R-L)/2
      if (sortedArr[mid] == num) {
         return true;
      } else if (sortedArr[mid] > num) {
         R = mid - 1;
      } else {
         L = mid + 1;
      }
   }
   //只有一个元素的时候比较是否等于num.
   return sortedArr[L] == num;
}

 查找target的(最大或最小值)

例如查找比target大的最小的值

1、取mid为72,此时不能将72过滤,可能存在比target大的最小值。

image.png

2、将mid=r

image.png 3、再取mid =65 mid = r

image.png 4、当前mid=65,l = mid +1 ,当l=r 时,找到了解。

image.png

Ceil

如果数组中存在元素,返回最大索引

如果数组中不存在元素,返回upper

Lower

查找小于target 的最大值。
搜索范围[l,r]
当target远大于数组的最大值,那查找值就是数组的最后一个值。
当target远小于数组的最小值,那没有查找的值。

局部最小值问题

题目描述

给定一个不包含相同元素的整数数组,求一个局部最小值

image.png

题解

1)数组第一个元素比第二个元素小,即为局部最小值。

2)数组最后一个元素比它前一个元素小,即为局部最小值。

3)若不满足,那么局部最小值必可在数组首尾两元素之间的某个位置取得。此时可以采用二分法思想,看中间位置是否符合条件,不符合就分成两部分,从不符合的那一边继续操作。

image.png

两种情况

image.png 值只会有一个。

image.png 这种情况取决于mid所在度区间,求最值。

public class LocalMinimum {

    public static int findLM(int[] arr) {
        if (arr == null || arr.length == 0) { //数组为空或数据长度为0
            return -1;
        }
        if (arr.length == 1) { //数组只有一个元素即为局部最小值
            return 0;
        }
        if (arr[0] < arr[1]) { //数组第一个元素比第二个元素小,即为局部最小值
            return 0;
        }
        if (arr[arr.length - 1] < arr[arr.length - 2]) {//数组最后一个元素比它前一个元素小,即为局部最小值
            return arr.length - 1;                     
        }
        //上述条件均不满足,局部最小值必在数组内部取得
        //用二分法思想求解
        int left = 0, right = arr.length - 1;
        int mid;
        while (left < right) {
            mid = left + (right - left) / 2;
            if (arr[mid] > arr[mid - 1]) {
                right = mid;
            } else if (arr[mid] > arr[mid + 1]) {
                left = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        //测试
        int[] arr = {4,3,6,8,2,1,5,7};
        int res = findLM(arr);
        if(res < 0) {
          System.out.println("最小值不存在");
        } else {
          System.out.println(arr[res]);
        }
    }
}

时间复杂度

image.png 比如:总共有n个元素,每次查找的区间大小就是n,n/2,n/4,…,n/2^k(接下来操作元素的剩余个数)其中k就是循环的次数。

在最坏情况下是在排除到只剩下最后一个值之后得到结果,所以为

 n/(2^m)=1; 最坏的情况下是为1 即查找到了最后一个元素。

2^m=n;

所以时间复杂度为:log2(n) = logn+ log2

二分查找的模板

image.png

冒泡排序

冒泡排序

冒泡排序(Bubble Sort)一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始) ,依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒

算法实现

外层循环:要循环的次数,n个元素的循环次数为n-1
内层循环:元素要比较的次数,i层循环的比较次数为n-1-j

public static void main(String[] args) {
    int arr[] = {3, 9, -1, 10, -2};
    int temp = 0;
    for (int i = 0; i < arr.length - 1; i++) {
        for (int j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    System.out.println(Arrays.toString(arr));
}

冒泡优化

Flag:增加falg标志位判断是否经历过排序,没有交换过值,直接返回。

public static void main(String[] args) {
    int arr[] = {3, 9, -1, 10, -2};
    int temp = 0;
    //增加标志位,校验是否做过排序
    boolean flag = false;
    for (int i = 0; i < arr.length - 1; i++) {
        for (int j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                flag = true;
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        //没有做过排序
        if(!flag){
            break;
        } else {
            flag = false;
        }
    }
    System.out.println(Arrays.toString(arr));
}

冒泡排序的时间复杂度

分析一下它的时间复杂度。当最好的情况,也就是要排序的表本身就是有序的,那么我们比较次数,根据最后改进的代码,可以推断出就是n-1次的比较,没有数据交换,时间复杂度为0(n)。当最坏的情况,即待排序表是逆序的情况,此时需要比较.

image.png

并作等数量级的记录移动。因此,总的时间复杂度为0(n2)。

选择排序算法

选择排序

选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。

简单选择排序法(Simple Selection Sort)就是通过n-i次关键字间的比较,从n-1+1个记录中选出关键字最小的记录,并和第i (1<isn)个记录交换之。

选择排序的思路

image.png

算法实现

外层循环:获取最小值的次数,n个元素要循环n-1次.
内存循环:要比较最小值得次数,为i+1->n次

public class Selection {
    public static void main(String[] args) {
        int[] arr = {1,4,2,3,6,5};
        selection(arr);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "   ");
        }
    }
    private static void  selection(int[] arr){
        for (int i = 0; i < arr.length; i++) {//length
            int maxIndex = i;
            for (int j = i; j < arr.length ; j++) {
                if(arr[j]>arr[maxIndex])//lending-i
                    //大到小,直接修改为<
                    maxIndex = j;
            }
            swap(arr,i,maxIndex);
        }
    }
    private static void swap(int[] arr, int i, int j) {
        int t= arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

算法实现(升级版)

public class Selection2 {
    public static void main(String[] args) {
        Student[] students = {
                new Student("alice",123),
                new Student("bob",1223),
                new Student("jack",142)
        };
        selection(students);
        for (int i = 0; i < students.length; i++) {
            System.out.println(students[i]);
        }
    }
    
    private static <E extends Comparable<E>> void selection(E[] arr) {
        for (int i = 0; i < arr.length; i++) {//length
            //索引值当前循环的最大或最小索引,是由compareTo方法决定的
            int index = i;
            for (int j = i; j < arr.length; j++) {
                if (arr[j].compareTo(arr[index]) < 0)//lending-i
                    index = j;
            }
            swap(arr, i, index);
        }
    }
    private static <E extends Comparable<E>> void swap(E[] arr, int i, int j) {
        E t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

插入排序

插入排序

插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。

插入排序思路

插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。

arr[0,i) 已经排好序, arr(i,n]未排好

插入流程

image.png

代码

public class InsertSort {
    public static void main(String[] args) {
        int[] dataSize = {10000, 100000};
        for (int n : dataSize) {
            Integer[] integers = ArrayGenerator.generateRandomArray(n, n);
            SortingHelper.sortTest("InsertSort", integers);
        }
        //        InsertSort,n=10000:0.104666 s
        //        InsertSort,n=100000:13.700619 s
    }
    public static <E extends Comparable<E>> void sort(E[] arr) {
        //一层循环控制排序号的数,[0,i)排序好的数,i代表需要进行向前排序的数。
        for (int i = 0; i < arr.length; i++) {
            //j为排序号最后一位数,
            //j>=0  i=0的情况下,j为i左边的第一个数据,小于0,不满足条件,说明已经已经有序。
            //比较j和j+1 进行交换。依次比较完。
            for (int j = i; j - 1 >= 0; j--) {
                if (arr[j].compareTo(arr[j - 1]) < 0) {
                    swap(arr, j, j - 1);
                } else {
                    break;
                }
            }
        }
        
    }
    private static <E extends Comparable<E>> void swap(E[] arr, int i, int j) {
        E t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

插入排序的优化

减少swap的次数
1、记录当前元素的值。
2、如果当前元素小于比较的元素,将比较的元素向后移位。
3、直到找到适合插入的位置.

public class InsertSort2 {
    public static void main(String[] args) {
        int[] dataSize = {10000, 100000};
        for (int n : dataSize) {
            Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
            Integer[] arr1 = Arrays.copyOf(arr, arr.length);
            SortingHelper.sortTest("InsertSort", arr);
            SortingHelper.sortTest("InsertSort2", arr1);
            //            InsertSort,n=100000:14.144661 s
            //            InsertSort2,n=100000:8.994257 s
        }
    }
    public static <E extends Comparable<E>> void sort(E[] arr) {
        for (int i = 0; i < arr.length; i++) {
            E t = arr[i];
            int j;
            for (j = i; j - 1 >= 0 && t.compareTo(arr[j - 1]) < 0; j--) {
                arr[j] = arr[j - 1];
            }
            arr[j] = t;
        }
        
    }
    private static <E extends Comparable<E>> void swap(E[] arr, int i, int j) {
        E t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

复杂度分析

由于嵌套循环的每一个都花费N次迭代,因此插入排序为O(N2)

Sum = 1+2+3+4+5+…+n = n(n+1)/2 = O(N2)

如果输入数据已预线排序,name运行时间为O(N),因此,对应近乎有序的数据,适合插入排序。 要比较的元素>有序数组的元素 此时跳出有序数组比较,此时已经数组已经有序