常见的排序算法


学习来源: 博客leetcode912数据结构学习可视化

1、时间复杂度

在这里插入图片描述

2、快速排序

  1. 快速排序的思想:通过一趟排序将数组分割成两个独立的部分,其中一部分记录的值都比另一部分的值小,然后再将这两个部分继续进行排序,从而达到整个数组有序。
class Solution {
    public int[] sortArray(int[] nums){
        quickSort(nums, 0, nums.length - 1);
        return nums;
    }
    public void quickSort(int[] nums, int low, int high){
        if(low < high){
        	//找到一个基准,以这个在这个基准的左边都是比它小的
        	//在这个基准右边都是比它大的
            int mid = partition(nums, low, high);
            //两个部分分别再进行排序,从而使得整个数组有序
            quickSort(nums, low, mid - 1);
            quickSort(nums, mid + 1, high);
        }
    }
    public int partition(int[] nums, int low, int high){
        int pivot = nums[low];
        while(low < high){
        	//high指向的值都比基准值大
            while(low < high && nums[high] >= pivot){
                high--;
            }
            //找到一个比基准还小的,进行交换
            if(low < high){
                nums[low] = nums[high];
            }
            //low指向的值都比基准小
            while(low < high && nums[low] <= pivot){
                low++;
            }
            //找到一个比基准还大的,进行交换
            if(low < high){
                nums[high] = nums[low];
            }
        }
        //最后基准值停在low指向的位置
        nums[low] = pivot;
        //此时low已经进行了初次的分割
        return low;
    }
}

3、归并排序

  1. 归并排序的基本思想:采用分治的思想。是将已经有序的子序列进行合并,得到完全有序的序列。
class Solution {
    public int[] sortArray(int[] nums){
        mergeSort(nums, 0, nums.length - 1);
        return nums;
    }
    public void mergeSort(int[] nums, int low, int high){
        if(low < high){
        	//找到归并的中心位置,以该位置进行划分。
            int mid = low + ((high - low) >> 1);
            //不断的缩小问题的规模
            mergeSort(nums, low, mid);
            mergeSort(nums, mid + 1, high);
            //将缩小后的问题一步一步的进行合并,使之有序
            merge(nums, low, mid, high);
        }
    }
    public void merge(int[] nums, int low, int mid, int high){
    	//临时数组的大小为high - low + 1,
    	//不断分割之后,最开始是两个数进行比较合并的
    	//以5,3为例,此时合并的结果就是3,5,临时数组的大小自然是2,而不是1.
        int[] temp = new int[high - low + 1];
        //i永远指向值小的,而j永远大的序列的第一个位置,即:mid + 1
        int i = low, j = mid + 1;
        //进行合并计数的
        int idx = 0;
        //小序列的范围是[low, mid],大序列的范围是[mid + 1, high]
        while(i <= mid && j <= high){
        //值的比较
            if(nums[i] <= nums[j]){
                temp[idx++] = nums[i++];
            }else {
                temp[idx++] = nums[j++];
            }
        }
        //没有比较完全的
        while(i <= mid){
            temp[idx++] = nums[i++];
        }
        while(j <= high){
            temp[idx++] = nums[j++];
        }
        //返回到nums数组
        for(int count = 0; count < idx; count++){
            nums[low + count] = temp[count];
        }
    }
}

4、堆排序

  1. 堆的性质:子结点的值总是小于或者大于父结点。
  2. 堆排序的基本思想:
    1. 将初始数组构建成大顶堆,此时堆是无序的。
    2. 将堆顶元素和最后一个元素进行交换,此时无序区间就是(R1,…,Rn-1),有序区间是(Rn)。
    3. 交换过后堆顶可能不满足堆的性质,那么要对无序区间调整为新堆,然后再将R1与最后一个元素进行交换。
    4. 不断重复上述过程直到有序区间的元素个数为n-1,排序过程完成。
class Solution {
    public int[] sortArray(int[] nums){
        //先进行大顶堆的构建
        buildMaxHeap(nums);
        for(int i = nums.length - 1; i > 0; i--){
            swap(nums, 0, i);//将堆顶元素和最后一个叶子结点交换,最大值放在数组末尾。
            heapify(nums, 0, i);//对前i个元素构建新的大顶堆
        }
        return nums;
    }
    
    //从第一个非叶子节点,从右向左,从下到上
    public void buildMaxHeap(int[] nums){
        for(int i = nums.length / 2 - 1; i >= 0; i--){
            heapify(nums, i, nums.length);
        }
    }
    //以i为根的堆进行调整
    public void heapify(int[] nums, int i, int length){
        //由数组的下标特性决定的,i为根的话,左右子结点就是下方形式
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        int largest = i;//认为根节点i是最大值
        //左子结点存在并且大于根节点的值
        if(left < length && nums[left] > num[largest]){
            largest = left;
        }
        //右子结点存在并且大于根的值
        if(right < length && nums[right] > nums[largest]){
            largest = right;
        }
        //现在最大值已经不是当时认为的i
        if(largest != i){
            //进行交换
            swap(nums, i, largest);
            //在重构它的子结点的,也调整为大顶堆的形式
            heapify(nums, largest, length);
        }
        
    }
    
    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

5、总结

  1. 只有归并排序是稳定的,只有堆排序的空间复杂度为O(1),快排为O(nlogn),归并排序为O(n),时间复杂度上,快排最差为 O ( n 2 ) O(n^2) O(n2),其他的都是O(nlogn).
  2. 快排:首先是去找一个基准pivot,随后对这个pivot的两端进行排序。找pivot的时候,默认是nums[low],low指针找比pivot大的,high指针找比pivot小的,一旦找到,进行交换。最后pivot放在low位置。
  3. 归并排序:采用了分治的思想,其实就是在不断的进行二分。所以在最小的粒度,是一个一个单独的元素。此时就需要将元素进行合并。合并的时候,需要每次合并较小段的开始、结尾以及较大段的开始、结尾(这里,mid就充当了较小段的结尾,以及mid+1就是较大段的开头)。然后就是不断的进行比较合并,注意,此时需要使用一个临时的数组,最后才转移到给定的数组中。
  4. 堆排序:先进行大顶堆的构建,然后每次将最顶端的节点和当前节点进行交换,交换之后还要进行对的重构。在大顶堆构建的时候,是nums.length / 2 - 1进行逆向构建的,每次进行调整堆的调整。对调整是当前节点和长度,记录最节点(2*i+1)和右节点(2*i+2),假定当前结点是最大的,如果左右节点都比最大节点值大,那么就交换,一旦最大的节点不是当前结点,进行交换,然后重构。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值