排序算法

排序算法

冒泡排序

实现逻辑:相邻两个元素两两相比较,按照排序规则(升序大的数放后面,降序小的数放后面)进行排序

时间复杂度O(n2)

空间复杂度O(1)

稳定排序

优化思路:通过加入布尔变量,判断内层循环是否发生交换来优化冒泡排序,最好情况为数组有序,复杂度降低为O(n),最坏情况为完全倒序,复杂度为O(n2)。

public static void sort(int[] arr) {
    boolean change = false;
    int temp;
    for (int i = 0; i < arr.length - 1; i++) {
        for (int j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {	//后者大于前者则交换位置
                change = true;
                temp = arr[j + 1];
                arr[j + 1] = arr[j];
                arr[j] = temp;
            }
        }
        if (!change) {	//判断是否发生交换
            break;
        }
    }
}

选择排序

实现逻辑:将数组分为已排序和未排序,从未排序列表中选取最小值和未排序的第一个元素交换位置。

时间复杂度为O(n2)

空间复杂度为O(1)

稳定排序

相对于冒泡排序效率更高,因为内循环少了许多赋值语句,一次内循环最多只需进行一次位置交换即可。

public static void sort(int[] arr) {
    int length = arr.length;
    if (length == 1) {
        return;
    }
    for (int i = 0; i < length; i++) {
        int min = arr[i];
        int index = i;
        for (int j = i + 1; j < length; j++) {
            if (arr[j] < min) {
                min = arr[j];
                index = j;
            }
        }
        if (index != i) {
            int tmp = arr[index];
            arr[index] = arr[i];
            arr[i] = tmp;
        }
    }
}

插入排序

实现逻辑:在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。

时间复杂度为O(n2)

空间复杂度为O(1)

稳定排序

优化思路:插入排序分为交换法和复制法,采用复制法可以减少交换法中大量的赋值语句从而能够更快地完成排序。

  • 交换法:内循环采用相邻两元素比较,如果满足排序条件就交换,直到不满足时退出循环。
  • 复制法:为当前元素创建副本,从已排序列表中倒序选择元素进行比较,如果满足排序条件,元素后移,直到不满足,将当前元素副本插入到空位上。
public static void sort(int[] arr) {
    int length = arr.length;
    for (int i = 1; i < length; i++) {
        int curNum = arr[i];
        int targetIndex = i;  //该索引指向最终要插入的位置
        for (int j = i - 1; j >= 0; j--) {
            if (arr[j]>curNum){
                arr[j+1] = arr[j];  //arr[j]后移一位
                targetIndex = j;
            }else{
                break;	//否则退出内循环
            }
        }
        arr[targetIndex] = curNum;
    }
}

希尔排序

实现逻辑:在要排序的一组数中,根据某一增量分为若干子序列,并对子序列分别进行插入排序。然后逐渐将增量减小,并重复上述过程。直至增量为1,此时数据序列基本有序,最后进行插入排序。

时间复杂度O(n^(1.5))

空间复杂度

非稳定排序

public static void sort(int[] arr) {
    int temp = 0;
    int length = arr.length;
    int incre = length;
    while (true) {
        incre = incre / 2;
        for (int k = 0; k < incre; k++) {    //根据增量分为若干子序列
            for (int i = k + incre; i < length; i += incre) {
                for (int j = i; j > k; j -= incre) {
                    if (arr[j] < arr[j - incre]) {
                        temp = arr[j - incre];
                        arr[j - incre] = arr[j];
                        arr[j] = temp;
                    } else {
                        break;
                    }
                }
            }
        }
        if (incre == 1) {
            break;
        }
    }
}

归并排序

实现逻辑:利用分治法的思想,将数组从中间分成前后两个部分,然后分别排序,最后将排序后的结果合并。

时间复杂度为O(nlogn)

空间复杂度O(n)

稳定排序

代码实现上用到了递归算法,核心的方法为分解与合并。

  • 分解方法:将一个数组从中间分隔开。假设一个数组为arr[p…r],p为第一个元素的索引,r为最后一个元素的索引。则中间索引为q=(p+r)/2向下取整,从而将arr[p…r]分解成了arr1[p…q]以及arr2[q+1…r]这两个数组。
  • 合并方法:合并两个有序数组。为两个数组各声明一个整型变量,用来指向各数组当前的对比项。合并时创建出一个长度为arr1.lenght+arr2.length的数组,并声明一个整型变量指向待插入位置的索引。对比时,小的元素先插入,相同的元素
public static int[] sort(int[] arr) {
    return mergeSortChild(arr, 0, arr.length - 1);
}

private static int[] mergeSortChild(int[] arr, int p, int r) {
    if (p >= r) {
        return new int[]{arr[r]};
    }
    int q = (p + r) / 2;
    int[] arr1 = mergeSortChild(arr, p, q);
    int[] arr2 = mergeSortChild(arr, q + 1, r);
    return merge(arr1, arr2);
}

private static int[] merge(int[] arr1, int[] arr2) {
    int n = arr1.length + arr2.length;
    int[] res = new int[n];
    int i = 0;
    int j = 0;
    for (int k = 0; k < n; k++) {
        if (i == arr1.length) {
            res[k] = arr2[j++];
        } else if (j == arr2.length) {
            res[k] = arr1[i++];
        } else {
            if (arr1[i] <= arr2[j]) {
                res[k] = arr1[i++];
            } else {
                res[k] = arr2[j++];
            }
        }
    }
    return res;
}

快速排序

实现逻辑:如果要排序的数组中下标从p到r 之间的一组数据,我们选择 p 到 r 之间的任意一个数据作为pivot(分区点)。遍历 p 到 r 之间的数据,将小于 pivot 的放在左边,将大于 pivot 的放到右边,将pivot放到中间。经过这一步骤,数组 p 到 r 之间数据被分成了三个部分,前面 p 到 q-1 之间的都是小于pivot的,中间是pivot,后面的 q+1到 r 之间的是大于pivot的。

时间复杂度为O(nlogn)~O(n2)

​ 如果每次分区操作,都能正好把数组分成大小接近的两个小区,那快排的时间复杂度和归并排序相同,也是O(nlogn)。如果在极端情况下,每次分区,两个小区的大小差别都很悬殊,那么它的时间复杂度会退化为O(n2),具体怎么算,可以用递归树的相关知识来解答。快速排序算法虽然在最坏情况下时间复杂度是O(n2)。但最好情况,平均情况时间复杂度都是O(nlogn)。而且,快速排序算法时间复杂度退化为O(n^2) 的概率非常小,我们可以通过合理地选择pivot来避免这种情况。

空间复杂度O(1)

不稳定排序

public static void sort(int[] arr){
    quickSort(arr, 0, arr.length - 1);
}

private static void quickSort(int[] arr, int p, int r) {
    if (p >= r){
        return;
    }
    int q = partition(arr, p , r);
    quickSort(arr, p, q-1);
    quickSort(arr, q+1, r);
}

private static int partition(int[] arr, int p, int r) {
    int cursor = r;
    boolean findBig = false;
    for (int i = 0; i < r; i++) {
        if (arr[i] > arr[r] && !findBig){
            cursor = i;
            findBig = true;
        }
        if (arr[i] < arr[r] && findBig){
            int tmp = arr[i];
            arr[i] = arr[cursor];
            arr[cursor] = tmp;
            cursor++;
        }
    }
    if (cursor != r){
        int tmp = arr[r];
        arr[r] = arr[cursor];
        arr[cursor] = tmp;
    }
    return cursor;
}

堆排序

实现逻辑:堆满足完全二叉树的性值,分为"最大堆"和"最小堆"。最大堆通常被用来进行"升序"排序,而最小堆通常被用来进行"降序"排序。通过不断地建堆,首尾交换,从而完成排序。

/**
 * 对数组进行堆排序
 * @param arr 待排序列
 */
private static void heapSort(int[] arr) {
    //创建堆
    for (int i = (arr.length - 1) / 2; i >= 0; i--) {
        //从第一个非叶子结点从下至上,从右至左调整结构
        adjustHeap(arr, i, arr.length);
    }

    //调整堆结构+交换堆顶元素与末尾元素
    for (int i = arr.length - 1; i > 0; i--) {
        //将堆顶元素与末尾元素进行交换
        int temp = arr[i];
        arr[i] = arr[0];
        arr[0] = temp;

        //重新对堆进行调整
        adjustHeap(arr, 0, i);
    }
}

/**
 * 调整堆
 * @param arr 待排序列
 * @param parent 父节点
 * @param length 待排序列尾元素索引
 */
private static void adjustHeap(int[] arr, int parent, int length) {
    //将temp作为父节点
    int temp = arr[parent];
    //左孩子
    int lChild = 2 * parent + 1;

    while (lChild < length) {
        //右孩子
        int rChild = lChild + 1;
        // 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
        if (rChild < length && arr[lChild] < arr[rChild]) {
            lChild++;
        }

        // 如果父结点的值已经大于孩子结点的值,则直接结束
        if (temp >= arr[lChild]) {
            break;
        }

        // 把孩子结点的值赋给父结点
        arr[parent] = arr[lChild];

        //选取孩子结点的左孩子结点,继续向下筛选
        parent = lChild;
        lChild = 2 * lChild + 1;
    }
    arr[parent] = temp;
}

基数排序

实现逻辑:基数排序(radix sort)属于"分配式排序"(distribution sort),又称"桶子法"(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些"桶"中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

/**
 * 递归,找出数组最大的值
 *
 * @param arrays 数组
 * @param L      左边界,第一个数
 * @param R      右边界,数组的长度
 * @return
 */
public static int findMax(int[] arrays, int L, int R) {
    //如果该数组只有一个数,那么最大的就是该数组第一个值了
    if (L == R) {
        return arrays[L];
    } else {
        int a = arrays[L];
        int b = findMax(arrays, L + 1, R);//找出整体的最大值
        if (a > b) {
            return a;
        } else {
            return b;
        }
    }
}
public static void sort(int[] arrays) {
    int max = findMax(arrays, 0, arrays.length - 1);
    //需要遍历的次数由数组最大值的位数来决定
    for (int i = 1; max / i > 0; i = i * 10) {
        int[][] buckets = new int[arrays.length][10];
        //获取每一位数字(个、十、百、千位...分配到桶子里)
        for (int j = 0; j < arrays.length; j++) {
            int num = (arrays[j] / i) % 10;
            //将其放入桶子里
            buckets[j][num] = arrays[j];
        }
        //回收桶子里的元素
        int k = 0;
        //有10个桶子
        for (int j = 0; j < 10; j++) {
            //对每个桶子里的元素进行回收
            for (int l = 0; l < arrays.length; l++) {
                //如果桶子里面有元素就回收(数据初始化会为0)
                if (buckets[l][j] != 0) {
                    arrays[k++] = buckets[l][j];
                }
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值