选择排序小结

至此,排序算法总结告一段落,然数据结构与算法知识庞大,围绕其展开的范围更是无限广大。

简单选择排序

思路:

简单选择排序和直接插入排序在我看来有些像,我们对比起来说。
直接插入排序在上上片文章(插入排序)中已经提过,它每次从未排序数组部分选第一个,然后拿到已排序数组部分一一比较,确定要插入的位置,插入。
而简单选择排序则是在未排序数组部分先选出当前最小的数,放到已排序数组部分的最后位置。

代码

public static void selectSort(int[] nums){
    for(int i=0;i<nums.length;i++){
        int point=i;
        int min=nums[i];
        for(int j=i;j<nums.length;j++){//每次从当前位置向后找最小的数据,将位置用point记录下来,最后交换最小数据point和当前位置i的值
            if(min>nums[j]){
                point=j;
                min=nums[j];
            }
        }
        swap(nums,i,point);
    }
}

堆排序

堆排序使用最大堆这种结构,将选择最大元素这个操作的时间复杂度降到O(nlog2(n)的程度
这里就要先说一下堆这种数据结构了,堆是一种特殊的二叉树,这里以最大堆为例。规定子树的值必然小于本节点的值(最大堆),但不要求子树间满足大小关系,这样堆顶就是整个数最大的节点,整个堆是一个完全二叉树,可以用数组表示。每次取出堆顶节点后,通过从顶向下调整(ShiftDown),将下一次最大节点调整至堆顶。ShiftDown操作的复杂度是O(log(n))。
此外,堆还有一个建堆的操作,一组数据如何把她们构建成一棵最大堆呢,这里有一个叫做“筛选法”的方法(首先先把数组假想成一个完全二叉树,然后从最底层非叶子节点开始ShiftDown,直到最高层),建堆的复杂度是O(n)。

思路

堆排序借助堆,,将待排数组构建成一个基于数组的最大堆,每次取出堆顶最大元素,堆的规模减小,而将取出的数据插入到数组的最后。
这样,借助于堆的优良性能,堆排序的的复杂度是O(nlog2(n)。

关键点:

  • 堆的建立:筛选法,关键是多次的ShiftDown
  • 堆取出堆顶元素后的调整:对堆顶的ShiftDown

代码:

/**
  *最大堆 向下调整的方法
    *@param nums 待排数组,也是储存堆这种抽象数据结构的基础
    *@param n 当前需要由上向下调整的根节点,为数组的下标
    *@param length 当前堆的大小,因为待排数组和堆公用一个数组,有序的部分需要填充在数组后段,
    如果不设置堆的边界length,那么每一次shiftDown整个数组都会再次成堆
*/
private void shiftDown(int[] nums,int n,int length){
    int leftChild=n*2+1,rightChild=leftChild+1;//堆是完全二叉树,用数组储存,所以当前节点和左右子节点的下标有这样的对应关系
    int root=n;
    //有左右子节点,则比较左右子节点是否比当前节点大
    if(leftChild<length && nums[root]<nums[leftChild]){
        root=leftChild;
    }
    if(rightChild<length && nums[root]<nums[rightChild]){
        root=rightChild;
    }
    //左右子节点比当前节点大,交换当前节点与较大的那个子节点的值
    //对交换后的子节点递归调用shiftDown继续向下调整
    if(root!=n){
        int temp=nums[n];
        nums[n]=nums[root];
        nums[root]=temp;
        shiftDown(nums,root,length);
    }
}
/**
 * 建最大堆的方法,使用了筛选法
*/
private void buildHeap(int[] nums){
    int index=nums.length/2-1;//获得完全二叉树的最下一层最后一个有子节点的节点下标
    for(int i=index;i>=0;i--){//由下向上ShiftDown构建最大堆
        shiftDown(nums,i,nums.length);
    }
}
public void heapSort(int[] nums){
    buildHeap(nums);//首先,建队,这时整个数组就是一个最大堆
    //循环取得最大堆的堆顶元素,插入到数组后段,同时缩小堆的大小
    for(int i=nums.length-1;i>0;i--){
        int max=nums[0];
        nums[0]=nums[i];//将i位置的元素换上来,然后通过shiftDown调整最大堆
        nums[i]=max;//大的元素交换到了数组后段,并且循环过程中完成了排序
        shiftDown(nums,0,i);
    }
}

归并排序

归并排序适合快速排序相当量级的排序算法,一般来说快速排序更适合于小数据集的排序,而归并排序因为其特点,在大量数据排序方面也是很好的。

思路

归并排序大方向上思想和快排一致,也是分治思想,关键就是如何分治了。
归并排序选择将待排数组分块,每一块分别使用归并排序,然后将块之间再次排序的方式排序。
这里的关键是如何在块之间再次排序,因为“块”最小的时候就是单个数据,无需排序。再次排序时,归并选择将两个块(在原数组中)复制一份,比较两个数组中的元素,将两个块中当前最小的元素放到原数组中,因为两个块都是有序的,所以在两个块中寻找最小的元素,就是比较遍历两个块的指针位置的数的大小(比较一次)。

关键点:

  • 如何分治:这里又选择了递归的方式,递归的最底层是单个数的排序(本身有序,不用排),返回到上层就是两个数再次排序,然后是再上层……

代码:

public void mergeSort(int[] nums,int start,int end){
    if(start<end){
        int mid=(end+start)/2;
        mergeSort(nums, start, mid);
        mergeSort(nums, mid+1, end);//这里注意mid+1,没有的话可能引起无限的递归调用,进而栈溢出,eg:start=0 && end=1
        merge(nums,start,mid,end);
    }
}
private void merge(int[] nums,int start,int mid,int end){
    int length1=mid-start+1;
    int length2=end-mid;
    int[] left=new int[length1];
    int[] right=new int[length2];
    //这里将原数组的两个块复制出来,重新排序
    //相当于清空了原数组的这部分,从小到大往回塞
    //使用Arrays.copyOfRang()是不是更简洁一点
    for(int i=0;i<length1;i++){
        left[i]=nums[start+i];
    }
    for(int i=0;i<length2;i++){
        right[i]=nums[mid+i+1];
    }
    int i=0,j=0,k=start;
    for(;k<end;k++){
        if(i==length1||j==length2){
            break;
        }
        if(left[i]<right[j]){
            nums[k]=left[i++];
        }else{
            nums[k]=right[j++];
        }
    }
    //如果复制出来的数组还没有全放进去,则全放回后面
    while(i<length1){
        nums[k++]=left[i++];
    }
    while(j<length2){
        nums[k++]=right[j++];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值