面试常问的排序算法分析及代码实现

1. 选择排序

选择排序就是在遍历数组的过程中,每次都选出从当前位置到数组末尾的最小值,并把选出来的最小值和当前位置交换。

	//选择排序
    public static void select_sort(int[] arr){
    	 if (arr == null || arr.length < 2) {
            return;
        }
        int n = arr.length;
        for(int i = 0; i < n; i++){
            int minIndex = i;
            for(int j = i + 1; j < n; j++){
                min = arr[minIndex] > arr[j] ? j : minIndex;
            }
            if(minIndex != i) swap(arr, i, minIndex);
        }
    }
  • 时间复杂度:O(n的平方)
  • 空间复杂度:O(1)
  • 不稳定

2. 冒泡排序

冒泡排序就是在遍历数组的时候,让当前位置元素arr[j] 和后一个元素arr[j+1] 进行比较,将大的元素放在后面,最后遍历完,最后一个元素就是最大值,下一次遍历,只需到上一次最大值的位置(不包括这个位置),这样,每次找到的都是每一段元素中的最大值。

 	//冒泡排序
    public static void bubble_sort(int[] arr){
    	 if (arr == null || arr.length < 2) {
            return;
        }
        int n = arr.length;
        for(int i = n - 1; i >= 0; i--){
        	//注意遍历到i,不包括i,因为i位置是最大值
            for(int j = 0; j < i; j++){
                if(arr[j] > arr[j + 1]) swap(arr, j, j + 1);
            }
        }
    }
  • 时间复杂度:O(n的平方)
  • 空间复杂度:O(1)
  • 可以做到稳定

3. 插入排序

插入排序就是遍历数组的时候,假设当前位置之前的数组都是有序的,如果当前位置的数小于前一个数就交换,如果不小于,就结束插入,继续遍历数组中下一个位置

 	//插入排序
    public static void insert_sort(int[] arr){
    	 if (arr == null || arr.length < 2) {
            return;
        }
        int n = arr.length;
        //注意这里是从1开始的
        for(int i = 1; i < n; i++){
        	//若果arr[j] >= arr[j-1],这个循环就结束了
            for(int j = i; j > 0 && arr[j] < arr[j - 1]; j--){
               swap(arr, j, j - 1);
            }
        }
    }
  • 时间复杂度:O(n的平方)
  • 空间复杂度:O(1)
  • 可以做到稳定

4. 归并排序

归并排序就是利用分治思想,先将数组切成两半,然后合并。利用递归,将数组切成两半,再将每一半切成两半,…,最后分成每一半只有一个元素,然后,一半一半(这两半是同一次拆分成的)的合并,合并的过程中排序。

//归并排序
    public static void merge_sort(int[] arr){
    	 if (arr == null || arr.length < 2) {
            return;
        }
        merge_sort(arr, 0, arr.length - 1);
    }
    private static void merge_sort(int[] arr, int left, int right){
        //切到只有一个元素
        if(left >= right) return;
        int mid = left + (right - left) / 2;
        merge_sort(arr, left, mid);
        merge_sort(arr,mid + 1, right);
        merge(arr, left, mid, right);
    }
	//合并,left为左半部分开始的索引,right为右半部分结束的索引
    private static void merge(int[] arr, int left, int mid, int right){
        //临时数组,暂时存放数据
        int[] help = new int[right - left + 1];
        int count = 0;
       	//i为左半部分的开始,j为右半部分的开始
        int i = left, j = mid + 1;
        //将左半部分和右半部分进行比较,较小的数先放到数组
        while(i <= mid && j <= right){
            help[count++] = arr[i] > arr[j] ? arr[j++]:arr[i++];
        }
        //可能有左半部分或者右半部分的数没有存到help数组
        //判断左半部分是否都已存到help数组,没有的话,就存到数组
        while(i <= mid){
            help[count++] = arr[i++];
        }
        //判断右半部分是否都已存到数组,没有的话,就存到数组
        while(j <= right){
            help[count++] = arr[j++];
        }
        //将排好序的数组放到原数组中
        for(int k = 0; k < help.length; k++){
            arr[left++] = help[k];
        }
    }
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
  • 可以做到稳定

5. 快速排序

快速排序就是找出一个参照物,每次将大于参照物的放在右边,小于参照物的放在左边,之后,再分别从参照为的两边找参照物,重复上述过程,整个过程也是递归的。

	//快速排序
    public static void quick_sort(int[] arr){
    	 if (arr == null || arr.length < 2) {
            return;
        }
        quick_sort(arr,0, arr.length - 1);
    }
    private static void quick_sort(int[] arr, int left, int right){
        if(left >= right) return;
        //快速排序的效率跟选的参照物有很大的关系,所以,这里,随机选一个,和数组
        //首元素交换
        swap(arr, left, left + (int)(Math.random() * (right - left + 1)));
        int mid = partition(arr, left, right);
        quick_sort(arr, left, mid - 1);
        quick_sort(arr, mid + 1, right);
    }

    private static int partition(int[] arr, int left, int right){
        int model = arr[left];
        while(left < right){
            //当arr[right]>model的时候,让right一直左移
            //注意这里要判断left<right,有可能在移动的时候,left = right
            while(left < right && arr[right] > model){
                right--;
            }
            //上面循环结束,可能是nums[right]<model,这样的话,就交换left和right的值
            //也可能是left == right,所以判断一下
            if(left < right){
                arr[left++] = arr[right];
            }
            while(left < right && arr[left] < model){
                left++;
            }
            if(left < right) {
                arr[right--] = arr[left];
            }
        }
            //当right=left的时候,这个位置是空,把model放进去
            arr[left] = model;
            return left;
    }
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(logn)
  • 常规实现不稳定

快速排序的时间复杂度主要与选取的参照物有关,可以随机选出一个值作为参照物,下面是快速排序的另一种写法,这种写法和上面哪一种写法的partition过程不一样。

 //快速排序
    public static void quick_sort(int[] arr){
        if (arr == null || arr.length < 2) {
            return;
        }
        quick_sort(arr,0, arr.length - 1);
    }
    private static void quick_sort(int[] arr, int left, int right){
        if(left >= right) return;
        //快速排序的效率跟选的参照物有很大的关系,所以,这里,随机选一个,和数组
        //首元素交换
        //swap(arr, left, left + (int)(Math.random() * (right - left + 1)));
        //随机选一个数,和数组最后一个元素交换,作为参照物
        swap(arr, right, left + (int)(Math.random() * (right - left + 1)));
        //int mid = partition(arr, left, right);
        int mid = partition2(arr, left, right);
        quick_sort(arr, left, mid - 1);
        quick_sort(arr, mid + 1, right);
    }

    private static int partition2(int[] arr, int left, int right) {
        //小于等于target的边界
        int small = left - 1;
        //大于target的边界,注意最后一个数是target,最后交换
        int big = right;
        while(left < big){
            if(arr[left] < arr[right]){
                //注意left++
                swap(arr, ++small, left++);
            }else if(arr[left] > arr[right]){
                swap(arr, --big, left);
            }else{
                left++;
            }

        }
        //最后,将arr[big]和比较的值arr[right]交换
        swap(arr, big, right);
        //最后返回big的索引
        return big;
    }

快速排序的第三种写法

6. 堆排序

数据结构中的堆是一个完全二叉树,一个数组,可以看成是一个完全二叉树,按层次遍历的顺序存放,一个元素(索引为i)的父节点是 (i- 1)/ 2, 左孩子的索引为(2 * i + 1)。如果堆中,每一个根节点(子树中)都是所在子树中的最大值,那么这个堆就是最大堆,堆排序就是利用最大堆进行排序的。大致过程是,将数组构建成一个最大堆,将数组最后一个元素(n -1)和第一个元素交换,再将除最后一个元素之外的数组调整为最大堆,重复上述过程。

	//堆排序
    public static void heap_sort(int[] arr){
        if (arr == null || arr.length < 2) {
            return;
        }
        //构造最大堆
        for(int i = 0; i < arr.length; i++){
            heap_insert(arr, i);
        }

        int size = arr.length;
        //将第一个元素和最后一个元素交换
        swap(arr, 0, --size);
        while(size > 0){
            //调整堆为最大堆
            heapify(arr, 0, size);
            swap(arr, 0, --size);
        }
    }
    //使用插入的方式构建最大堆
    private static void heap_insert(int[] arr, int index){
        //当孩子结点大于父节点的时候交换
        while(arr[index] > arr[(index - 1) / 2]){
            swap(arr, index, (index - 1) / 2);
            //父节点和子节点交换完之后,将父节点的索引赋给index,
            //再去判断父节点和父节点的父节点的大小,所以这里用的是while
            index = (index - 1) / 2;
        }
    }
    //调整堆,注意size是数组个数,不是索引
    private static void heapify(int[] arr, int index,int size){
        int left = index * 2 + 1;
        while(left < size){
            int largest = left;
            //找出左右孩子中最大的一个
            if(left + 1 < size && arr[left + 1] > arr[left]){
                largest++;
            }
            //如果根节点大于左右孩子,那么说明已经是最大堆了,
            //因为左右孩子是左右子树的根节点,是子树中的最大值
            if(arr[index] > arr[largest]){
                break;
            }
            swap(arr, index, largest);
            //将左右子树中最大值的索引赋给index,调整子树为最大堆
            index = largest;
            left = index * 2 + 1;
        }
    }
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
  • 不稳定

另外一种写法

构建堆的过程 ,其实也是调整堆的过程,从最后一个根节点开始,保证所有子树都是一个小根堆,那么整个树就是小根堆了

public static void heapSort(int[] arr) {
        //构建最大堆
        for(int i = arr.length / 2 - 1; i >= 0; i--) {
            adjust(arr, i, arr.length);
        }

        for(int i = arr.length - 1; i >= 0; i--) {
            swap(arr, 0, i);
            adjust(arr, 0, i);
        }
    }

    public static void adjust(int[] arr, int i, int size) {
        int left = 2 * i + 1;
        while(left < size) {
            //temp为i的左右孩子中最大的一个的索引
            int temp = left;
            //如果有右孩子,并且右孩子大于左孩子,就把左孩子索引赋给temp
            if(left + 1 < size && arr[left + 1] > arr[left]) {
                temp = left + 1;
            }
            //判断最小值和根节点哪个大
            if(arr[i] > arr[temp]) {
                break;
            }
            //如果小于,就交换
            swap(arr, i, temp);
            //将temp赋给i,调整子树,因为,子树的根节点变了,所以,也要调整
            i = temp;
            left = temp * 2 + 1;
        }
    }

7. 计数排序

计数排序其实是一种特殊的桶排序,就是先求出数组中的最大值max和最小值min,然后,建max-min +1个桶,假入是数组,数组长度就是max-min+1,bucket数组中存的就是原数组中第i(i为桶的索引,数组元素-min得到)小的数的个数,统计完之后,根据桶(bucket)中的个数,将min+i放到原数组中,对应的桶中记录的有几个就放几个。计数排序和前面六中排序不同的是,计数排序不是基于比较的排序。

//计数排序
    public static void count_sort(int [] arr){
        if(arr == null || arr.length < 2) return;
        int min = arr[0];
        int max =0;
        //找出数组中的最大值和最小值
        for(int i = 0; i < arr.length; i++){
            max = Math.max(max, arr[i]);
            min = Math.min(min, arr[i]);
        }
        //bucket数组中存放的是第i小的个数
        int[] bucket = new int[max - min + 1];
        //统计元素出现的次数
        for(int i = 0; i < arr.length; i++){
            bucket[arr[i] - min]++;
        }
        int j = 0;
        //将bucket中的数写回原数组
        for(int i = 0; i < bucket.length; i++){
            while(bucket[i]-- > 0){
                arr[j++] = min + i;
            }
        }
    }

不稳定的排序:快(快排)些(希尔)选(选择)一堆(堆排)

8. java中的Arrays.sort()

java1.8中,当数组长度小于47的时候,使用的是冒泡排序,当数组长度大于47,小于286的时候使用双轴快速排序,并且当快速排序递归时,如果长度小于47,直接使用插入排序,大于286的时候,使用的是归并排序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值