算法-递归

76 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

\

选择排序:直接插入排序,简单选择排序,堆排序

插入排序:希尔排序

交换排序:冒泡排序,快速排序

归并排序

基数排序

冒泡排序

时间复杂度:O(n^2)

从前往后多次扫描,每次扫描一遍就把待排序序列中的最大值排到最后,像冒气泡一样

 //冒泡排序
    static void f2(int[] arr){
        int temp;
        for(int j = arr.length-1; j > 0; j--)
        for(int i = 0; i < j; i++){
            if(arr[i] > arr[i+1]){
                temp = arr[i];
                arr[i] = arr[i+1];
                arr[i+1] = temp;
            }
        }
    }

插入排序

时间复杂度:O(n^2)

将一个无序的数插入到一个有序的序列中

//插入排序
    static void f3(int[] arr){
        for(int j = 1; j < arr.length; j++){
            int target = arr[j];
            int index = j-1;
            while (index > -1 && target < arr[index]){
                arr[index+1] = arr[index];
                index -= 1;
            }
            arr[index+1] = target;
        }
    }

希尔排序

时间复杂度:O(n^1.3-2)

将原本的数据进行分组,对每个组进行排序。

再将数据进行分组,但是每个组的数据量增多------由增量确定

对每一组进行插入排序。

    //希尔排序
    static void f1(int[] arr){
        //第一个循环将间隔化为总长度的一半,如9个元素就是以间隔为4分组  坐标分别为【0,4,8】,【1,5】,【2,6】,【3,7】
        //第二个循环将间隔再缩一半,为【0,2,4,6,8】,【1,3,5,7】
        for(int interval = arr.length/2; interval > 0; interval = interval/2){
            //第一轮循环
            //第一个循环4开始,先比较4,0
            //第二个循环5开始,比较5,1
            //第三个循环6开始,比较6,2
            //第四个循环7开始,比较7,3
            //第五个循环8开始,比较8,4,0
            //第二轮循环从2开始,先比较2,0
            //3,1->4,2,0->5,3,1->6,4,2,0->7,5,3,1->8,6,4,2,0
            for(int i = interval; i < arr.length; i++){
                int target = arr[i];
                int j = i - interval;
                while (j > -1 && target < arr[j]){
                    arr[j + interval] = arr[j];
                    j -= interval;
                }
                arr[j+interval] = target;
            }
        }
    }

希尔排序的性能分析

最坏的情况

希尔排序是分组的插入排序,如果最后一次分组所有数据仍然不是有序的,那么就跟直接插入排序没什么区别了,复杂度就是O(n^2)

如数据1 9 2 10 3 11 4 12 5 13 6 14  7  15 8 16

从增量4变化到增量为2,各组都是有序的

但是增量为1时,发现完全无序,排序效果跟直接插入排序相同,反而徒增了前面分组的时间

选择排序

时间复杂度:O(n^2)

每次选择最大或者最小的放到最前面或最后面,只记录下标,找到最大的那个下标,然后将该位置的数与放到最前面或者最后面

    //选择排序  倒序  将排好的放到最前面
    static void f4(int[] arr){
        int i,j,max,temp;
        for(i = 0; i < arr.length; i++){
            max = i;
            for(j = i + 1; j < arr.length; j++){
                if(arr[j] > arr[max]){
                    max = j;
                }
            }
            temp = arr[max];
            arr[max] = arr[i];
            arr[i] = temp;
        }
    }

选择排序和冒泡排序的区别就是 冒泡排序每次比较发现不相等就交换,而选择排序每次比较只记录下标,最后再交换。

快速排序

时间复杂度:nlogn

1.选一个中心轴pivot

2.将大于Pivot的数字放到轴右边

3.将小于pivot的数字放到轴左边

4.递归调用两侧子序列,重新前三个步骤

//快速排序
    static void f5(int[] arr,int L,int R){
        //如果某侧只剩一个元素了,L就会等于R,退出排序
        if(L >= R){
            return;
        }
        int left = L,right = R;
        //选择最左侧当中心轴
        int pivot = arr[left];
        while (left < right){
            //如果右侧的比轴大,就移动比较下一个
            while (left < right && arr[right] >= pivot){
                right--;
            }
            //如果比轴小,就换到左侧
            if(left < right){
                arr[left] = arr[right];
            }
            //再去比较右侧,如果左侧小就移动比较下一个
            while (left < right && arr[left] <= pivot){
                left++;
            }
            //比轴大就换到右侧
            if(left < right){
                arr[right] = arr[left];
            }
            //最后将轴放到指针交汇处位置
            if(left >= right){
                arr[left] = pivot;
            }
        }
        //递归排序轴左侧的序列和轴右侧的序列
        f5(arr,L,right-1);
        f5(arr,right+1,R);
    }

堆排序

时间复杂度:O(nlogn)

堆符合的要求

  • 1.是一颗完全二叉树(叶子节点都往左靠)
  • 2.所有父节点的值都大于(小于)子节点

分类

  • 大顶堆:每个结点的值都大于等于子节点
  • 小顶堆:每个结点的值都小于等于子节点

步骤

先构建堆,建好堆之后,堆顶是最大的数据或是最小的数,每次取堆顶的数(与数组末尾交换),取完之后,再重新建堆,直到取完所有的数,就得到了有序的序列。

将树的逻辑结构存到数组中,下标之间的关系为:

​编辑

public class HeapSort {
    //堆排序入口
    static void heap_sort(int elem[],int n){
        //建堆
        int i;
        for(i = n/2 - 1; i >= 0; i--){ //从最后一个有孩子的节点开始
            heapify(elem,n,i);
        }
        //排序
        for(i = n-1; i > 0; i--){
            swap(elem,i,0); //将数组第一个元素和最后一个元素交换位置
            heapify(elem,i,0); //数组第一个元素位置被交换后,从开头重新维护堆的性质
        }
    }
    //维护堆的性质     arr 数组,n 数组长度, i,待维护节点的下标
    static void heapify(int elem[],int n,int i){
        int largest = i; //最大节点的下标,假设是根节点
        int lson = i*2+1; //左孩子下标
        int rson = i*2+2; //右孩子下标
        if(lson < n && elem[largest] < elem[lson]){ //根小于左孩子
            largest = lson; //最大节点下标为左孩子
        }
        if(rson < n && elem[largest] < elem[rson]){
            largest = rson; //最大节点下标为右孩子
        }
        if(largest != i){ //最大值不是根节点,则需要进行交换
            swap(elem,largest,i);//交换
            heapify(elem,n,largest); //下标largest的值被换为了elem[i],递归判断是否遵循堆的性质
        }
    }

    static void swap(int elem[],int largest,int i){
        int temp;
        temp = elem[largest];
        elem[largest] = elem[i];
        elem[i] = temp;
    }
    public static void main(String[] args) {
        int elem[] = {5,9,4,2,1,7,3,9,6};
        heap_sort(elem,elem.length);
        for (int i : elem) {
            System.out.print(i);
        }
    }
}

计数排序

 ​编辑

基数排序

多关键字排序

例如多个百位数排序,先按照个位数排,将个位相同的放到一个桶中,再按照十位数排,十位相同的放到一个桶中,最后按照百位排,百位数相同的放到一个桶中

归并排序