面试必考04——数组的排序和查找算法

在这里插入图片描述

1.冒泡排序 O(n2),O(1),稳定

冒泡排序对相邻的两个元素进行比较,看是否满足大小关系。如果不满足就让它俩互换。每轮冒泡操作都会让至少一个元素移动到它应该在的位置,最多重复 n 次,就完成了 n 个元素的排序工作。

    public void bubbleSort(int[] a) {
        int n = a.length;
        if (n <= 1) {
            return;
        }
        for (int i = 0; i < n; ++i) {
            boolean flag = false;
            for (int j = 0; j < n - i - 1; ++j) {
                if (a[j] > a[j + 1]) { // 交换
                    int tmp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = tmp;
                    flag = true; // 表示有数据交换
                }
            }
            if (!flag) {
                break; // 没有数据交换,提前退出
            }
        }
    }

2.快速排序,O(nlogn)、O(1)、不稳定
  • 使用的是分而治之的思想,将大问题分解成小的子问题来解决。首先选择一个基准值,然后遍历数组,将小于基准值的放在左边,大于基准值的放在右边,基准值放在中间,这样数组就被分成了三部分,然后再用递归的思想对小于基准值的部分和大于基准值的部分再进行一次比较划分,每一次可以确定其中一个元素的最终位置。
  • 在理想情况下,每次分区操作都正好把数组分为大小相等的两个子区间,那快排的时间复杂度的求解公式和归并排序的一样,结果也为 O(nlogn)。而在极端情况下,比如数组已经有序时,每次分区都是不均等的,需要进行 n 次分区,这种情况下快排的时间复杂度退化为 O(n2)
public class T02_QuickSort {
    public void qSort(int[] arr, int frist, int last) {
        if (frist >= last || arr == null || arr.length <= 1) {
            return;//递归出口
        }
        int i = frist;
        int j = last;
        int temp = arr[frist];
        while (i != j) {
            while (arr[j] >= temp && j > i) {
                j--;
            }
            while (arr[i] <= temp && i < j) {
                i++;
            }
            if (i < j) {
                int t = arr[i];
                arr[i] = arr[j];
                arr[j] = t;
            }
        }
        if (i == j) {
            arr[frist] = arr[i];
            arr[i] = temp;
        }
        qSort(arr, frist, i - 1);
        qSort(arr, i + 1, last);
    }
}
3.归并排序,O(nlogn)、O(n)),稳定

使用的是分而治之的思想,将大问题分解成小的子问题来解决:首先把数组从中间分成前后两部分,然后对它们分别排序,再将排好序的两部分合并在一起,这样整个数组就有序了。

public class T03_MergeSort {
    public void merge(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        int left = 0;
        int right = arr.length - 1;
        int[] help = new int[arr.length];
        merge2(arr, help, left, right);
    }

    private void merge2(int[] arr, int[] help, int left, int right) {
        if (left >= right) {
            return;
        }
        int mid = left + (right - left) / 2;
        merge2(arr, help, left, mid);
        merge2(arr, help, mid + 1, right);
        sort(arr, help, left, right, mid);
    }

    private void sort(int[] arr, int[] help, int left, int right, int mid) {
        int i = left;
        int j = mid + 1;
        int index = left;
        while (i <= mid && j <= right) {
            help[index++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }
        while (i <= mid) {
            help[index++] = arr[i++];
        }
        while (j <= right) {
            help[index++] = arr[j++];
        }
        for (int m = left; m <= right; m++) {
            arr[m] = help[m];
        }
    }
}
4.选择排序,O(n2),O(1),不稳定

选择排序算法的实现思路类似插入排序:将数组中的数据分为已排序区间和未排序区间,每次从未排序区间中找到最小或者最小的元素,将其放到已排序区间的末尾。

public class T04_SelectionSort {
    public void selectionSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            int maxIndex = 0;
            for (int j = 0; j <= arr.length - 1 - i; j++) {
                maxIndex = arr[j] > arr[maxIndex] ? j : maxIndex;
            }
            if (maxIndex != arr.length - 1 - i) {
                int temp = arr[arr.length - 1 - i];
                arr[arr.length - 1 - i] = arr[maxIndex];
                arr[maxIndex] = temp;
            }
        }
    }
}
5.插入排序,O(n2),O(1),稳定

将数组中的数据分为已排序区间和未排序区间。挨个取未排序区间中的元素,在已排序区间中找到合适的位置进行插入,并保证已排序区间中的元素一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。

public class T05_InsertSort {
    public void insertSort(int[] arr) {
        for (int i = 1; i <= arr.length - 1; i++) {
            int temp = arr[i];
            int j = i - 1;
            while (j >= 0 && arr[j] > temp) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = temp;
        }
    }
}
6.堆排序
  • 任何情况下时间复杂度都为 O(nlogn),空间复杂度为 O(1)。不稳定。堆总是一棵完全二叉树。
  • 思想:
  1. 首先将待排序序列构成一个堆,假设构成一个大根堆,大根堆的性质就是每个节点的值都大于或等于它左右孩子节点的值;
  2. 然后将堆顶元素与末尾元素交换,交换后末尾元素就是这个序列的最大值
  3. 然后将剩余 n-1 个元素重新调整为大根堆,再次将堆顶元素与末尾元素交换,交换后末尾元素就是这个序列第二大的值了
  4. 反复执行,最后就得到了一个递增的有序序列。
  • 排序重建堆的过程是每次将堆顶元素与末尾元素交换,然后重新调整剩余的 n - 1 个元素为大根堆,每次调整的时间为 logn,所以排序重建堆的时间复杂度为 nlogn。
  • 建堆时间(初始化堆):每个节点在进行堆化时,需要比较和交换的节点个数,跟这个节点的高度成正比,也就是第一层节点个数为1,高度为h;第二层节点个数为2,高度为 h-1,以此类推,最后一层节点个数为 2 的 h-1 次方,高度为 1。然后将每个节点的高度求和,得到的就是建堆时间,公式为:S = 2^0 * h + 2^1 *(h-1) + 2^2 *(h-2) +…+ 2^(h-1) * 1 ,将公式左右都乘以 2,然后 2S - S,最后可以得到 S = 2^(h-1) - h - 2 。因为二叉树中的高度 h = logn,代入公式,就可以得到 S = 2n - logn - 2,所以建堆的时间复杂度为 O(n)。
  • 综上所述,整体堆排序的时间复杂度为 O(nlogn)
public class T06_HeapSort {
    public void heapSort(int[] arr) {
        //构造初始堆,从第一个非叶子节点开始调整,左右孩子节点中较大的交换到父节点中
        for (int i = arr.length - 1; i >= 0; i--) {
            sort(arr, arr.length, i);
        }
        //排序,将最大的节点放在堆尾,然后从根节点重新调整
        for (int i = arr.length - 1; i >= 1; i--) {
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;
            sort(arr, i, 0);
        }
    }

    private void sort(int[] arr, int len, int i) {
        int k = i;
        int temp = arr[k];
        int index = 2 * k + 1;
        while (index < len) {
            if (index + 1 < len) {
                index = arr[index] > arr[index + 1] ? index : index + 1;
            }
            if (arr[index] > temp) {
                arr[k] = arr[index];
                k = index;
                index = 2 * k + 1;
            } else {
                break;
            }
        }
        arr[k] = temp;
    }
}
7.二分查找
public class T07_BinarySearch {
    public int binarySearch(int[] arr, int value) {
        if (arr == null || arr.length <= 0) {
            return -1;
        }
        return search(arr, 0, arr.length - 1, value);
    }

    //非递归
    private int search(int[] arr, int start, int end, int value) {
        while (start <= end) {
            int mid = start + (end - start) / 2;
            if (arr[mid] == value) {
                return mid;
            } else if (arr[mid] > value) {
                end = mid - 1;
            } else {
                start = mid + 1;
            }
        }
        return -1;
    }
    //递归
//    private int search(int[] arr,int start,int end,int value){
//        if(start>end){
//            return -1;
//        }
//        int mid=start+(end-start)/2;
//        if(arr[mid]==value){
//            return mid;
//        }else if(arr[mid]>value){
//            return search(arr,start,mid-1,value);
//        }else{
//            return search(arr,mid+1,end,value);
//        }
//    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值