常用的排序算法(java版)

24 篇文章 0 订阅
11 篇文章 0 订阅

排序算法

1、冒泡排序

主要流程

重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

基本算法原理

1、比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2、对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3、针对所有的元素重复以上的步骤,除了最后一个。
4、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

算法分析

时间复杂度分析
首先最好的情况是需要排序的数组是已经排好了的,因此程序只是需要遍历一次就可以结束了,假设需要比较的次数为C,移动的次数为M, 因此C的min值为n-1,M的min值为0。 冒泡排序最好的时间复杂度为O(n)
其次,假设数组是反序的,这个时候的排序规则是正序,那么程序需要进行n-1趟排序。每趟排序要进行 n-i 次关键字的比较(1≤i≤n-1)
在这里插入图片描述
在这里插入图片描述

冒泡排序最好的情况是O(n),空间复杂度O(1),
冒泡排序的最坏时间复杂度为O(n^2)。
因此冒泡排序总的平均时间复杂度为O(n^2)。

算法稳定性分析
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法

算法实例:

// 测试:1,3,5,2
public int[] BubbleSort(int[] array){
        int M = 0;
        for (int i = 0; i < array.length-1; i++) {
            for (int j = 0; j < array.length-i-1; j++) {
                if(array[j] > array[j+1]){
                    int temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                    M ++;
                }
            }
            System.out.print(M+": ");
            for (int num:array) {
                System.out.print(num+" ");
            }
            System.out.println();
        }
        System.out.println("我交换了"+M+"次");
        return array;
    }

结果:
在这里插入图片描述

2、选择排序

主要流程

选择排序是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

基本算法原理

1、初始状态:无序区为R[0..n-1](共n个元素),有序区为空。
2、设置一个变量i,让i从0至n-2循环的同时,在对比数组中元素i跟元素i+1的大小,如果R[i+1]比R[i]小,则用一个变量k来记住他的位置(即k=i+1)。等到循环结束的时候,我们应该找到了R中最小的那个数的位置了。然后进行判断,如果这个最小元素的不是R的第一个元素,就让第一个元素跟他交换一下值,使R[0…0]和R[1…n-1]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
3、重复上面的循环
4、第i趟排序开始时,当前有序区和无序区分别为R[0..i-1]和R[i..n-1]。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[0…i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
在这里插入图片描述

算法分析

时间复杂度

选择排序的交换操作介于 0 和 (n - 1) 次之间。选择排序的比较操作为 n (n - 1) / 2 次之间。选择排序的赋值操作介于 0 和 3 (n - 1) 次之间。比较次数O(n^2),比较次数与关键字的初始状态无关,总的比较次数N=(n-1)+(n-2)+...+1=n*(n-1)/2。交换次数O(n),最好情况是,已经有序,交换0次;最坏情况交换n-1次,逆序交换n/2次。交换次数比冒泡排序少多了,由于交换所需CPU时间比比较所需的CPU时间多,n值较小时,选择排序比冒泡排序快。

稳定性

选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果一个元素比当前元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。

算法实例

// 测试用例为上图数据
public int[] SelectionSort(int[] array){
        int len = array.length;
        int M = 0;
        for (int i = 0; i < len-1; i++) {
            int min = i;
            for (int j = i+1; j < len; j++) {
                if(array[j] < array[min]){
                    min = j;
                }
            }
            int temp = array[i];
            array[i] = array[min];
            array[min] = temp;
            M ++;
            System.out.print(M+": ");
            for (int num:array) {
                System.out.print(num+" ");
            }
            System.out.println();
        }
        return array;
    }

在这里插入图片描述

3、快速排序

主要流程

1、 首先设定一个分界值,通过该分界值将数组分成左右两部分。
2、 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。
3、 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
4、 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

算法原理

1、 设置两个变量i、j,排序开始的时候:i=0,j=N-1
2、第一个数组元素作为关键数据,赋值给key,即key=A[0]
3、j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]的值交换
4、i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]的值交换;
5、 重复第3、4步,直到 i == j;
6、 3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时循环结束

算法实例

/**
 * 入口函数(递归方法),算法的调用从这里开始。
 */
public void quickSort(int[] arr, int startIndex, int endIndex) {
    if (startIndex >= endIndex) {
        return;
    }
 
    // 核心算法部分:分别介绍 双边指针(交换法),双边指针(挖坑法),单边指针
    int pivotIndex = doublePointerSwap(arr, startIndex, endIndex);
    // int pivotIndex = doublePointerHole(arr, startIndex, endIndex);
    // int pivotIndex = singlePointer(arr, startIndex, endIndex);
 
    // 用分界值下标区分出左右区间,进行递归调用
    quickSort(arr, startIndex, pivotIndex - 1);
    quickSort(arr, pivotIndex + 1, endIndex);
}
 
/**
 * 双边指针(交换法)
 * 思路:
 * 记录分界值 pivot,创建左右指针(记录下标)。
 * (分界值选择方式有:首元素,随机选取,三数取中法)
 *
 * 首先从右向左找出比pivot小的数据,
 * 然后从左向右找出比pivot大的数据,
 * 左右指针数据交换,进入下次循环。
 *
 * 结束循环后将当前指针数据与分界值互换,
 * 返回当前指针下标(即分界值下标)
 */
private int doublePointerSwap(int[] arr, int startIndex, int endIndex) {
    int pivot = arr[startIndex];
    int leftPoint = startIndex;
    int rightPoint = endIndex;
 
    while (leftPoint < rightPoint) {
        // 从右向左找出比pivot小的数据
        while (leftPoint < rightPoint
                && arr[rightPoint] > pivot) {
            rightPoint--;
        }
        // 从左向右找出比pivot大的数据
        while (leftPoint < rightPoint
                && arr[leftPoint] <= pivot) {
            leftPoint++;
        }
        // 没有过界则交换
        if (leftPoint < rightPoint) {
            int temp = arr[leftPoint];
            arr[leftPoint] = arr[rightPoint];
            arr[rightPoint] = temp;
        }
    }
    // 最终将分界值与当前指针数据交换
    arr[startIndex] = arr[rightPoint];
    arr[rightPoint] = pivot;
    // 返回分界值所在下标
    return rightPoint;
}
 
/**
 * 双边指针(挖坑法)
 * 思路:
 * 创建左右指针。
 * 记录左指针数据为分界值 pivot,
 * 此时左指针视为"坑",里面的数据可以被覆盖。
 *
 * 首先从右向左找出比pivot小的数据,
 * 找到后立即放入左边坑中,当前位置变为新的"坑",
 * 然后从左向右找出比pivot大的数据,
 * 找到后立即放入右边坑中,当前位置变为新的"坑",
 *
 * 结束循环后将最开始存储的分界值放入当前的"坑"中,
 * 返回当前"坑"下标(即分界值下标)
 */
private int doublePointerHole(int[] arr, int startIndex, int endIndex) {
    int pivot = arr[startIndex];
    int leftPoint = startIndex;
    int rightPoint = endIndex;
 
    while (leftPoint < rightPoint) {
        // 从右向左找出比pivot小的数据,
        while (leftPoint < rightPoint
                && arr[rightPoint] > pivot) {
            rightPoint--;
        }
        // 找到后立即放入左边坑中,当前位置变为新的"坑"
        if (leftPoint < rightPoint) {
            arr[leftPoint] = arr[rightPoint];
            leftPoint++;
        }
        // 从左向右找出比pivot大的数据
        while (leftPoint < rightPoint
                && arr[leftPoint] <= pivot) {
            leftPoint++;
        }
        // 找到后立即放入右边坑中,当前位置变为新的"坑"
        if (leftPoint < rightPoint) {
            arr[rightPoint] = arr[leftPoint];
            rightPoint--;
        }
    }
    // 将最开始存储的分界值放入当前的"坑"中
    arr[rightPoint] = pivot;
    return rightPoint;
}
 
/**
 * 单边指针
 * 思路:
 * 记录首元素为分界值 pivot, 创建标记 mark 指针。
 * 循环遍历与分界值对比。
 * 比分界值小,则 mark++ 后与之互换。
 * 结束循环后,将首元素分界值与当前mark互换。
 * 返回 mark 下标为分界值下标。
 */
private int singlePointer(int[] arr, int startIndex, int endIndex) {
    int pivot = arr[startIndex];
    int markPoint = startIndex;
 
    for (int i = startIndex + 1; i <= endIndex; i++) {
        // 如果比分界值小,则 mark++ 后互换。
        if (arr[i] < pivot) {
            markPoint++;
            int temp = arr[markPoint];
            arr[markPoint] = arr[i];
            arr[i] = temp;
        }
    }
    // 将首元素分界值与当前mark互换
    arr[startIndex] = arr[markPoint];
    arr[markPoint] = pivot;
    return markPoint;
}

结果:
在这里插入图片描述

4、插入排序

主要流程

插入排序法,就是检查第i个数字,如果在它的左边的数字比它大,进行交换,这个动作一直继续下去,直到这个数字的左边数字比它还要小,就可以停止了。插入排序法主要的回圈有两个变数:i和j,每一次执行这个回圈,就会将第i个数字放到左边恰当的位置去。

基本算法原理

伪代码

INSERTION-SORT(A)
for j=2 to A.length:
    key=A[j]
    //将A[j]插入已排序序列A[1..j-1]
    i=j-1
    while i>0 and A[i]>key
        A[i+1]= A[i]
        i=i-1
    A[i+1]=key

实例效果
在这里插入图片描述

实例代码

public int[] InsertionSort(int[] array){
        int len = array.length;
        for (int i = 1; i < len; i++) {
            // 记录选择的位置
            int temp = array[i];
            // 记录选择的位置前一个
            int j = i-1;
            // 在选择位置前一个个找,如果满足则一个个更改
            while (j >=0 && array[j] > temp){
                array[j+1] = array[j];
                j --;
            }
            // 复位
            array[j+1] = temp;
        }
        return array;
    }

结果
在这里插入图片描述

算法分析

时间复杂度
在插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较N- 1次,时间复杂度为O(n)

最坏的情况是待排序数组是逆序的,此时需要比较次数最多,总次数记为:1+2+3+…+N-1,所以,插入排序最坏情况下的时间复杂度为O(n^2)

平均来说,A[1…j-1]中的一半元素小于A[j],一半元素大于A[j]。插入排序在平均情况运行时间与最坏情况运行时间一样,是输入规模的二次函数

空间复杂度
插入排序的空间复杂度为常数阶O(1)

稳定性分析
如果待排序的序列中存在两个或两个以上具有相同关键词的数据,排序后这些数据的相对次序保持不变,即它们的位置保持不变,通俗地讲,就是两个相同的数的相对顺序不会发生改变,则该算法是稳定的;如果排序后,数据的相对次序发生了变化,则该算法是不稳定的。关键词相同的数据元素将保持原有位置不变,所以该算法是稳定的

5、堆排序

基本算法原理

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

二叉树

二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个节点

最小堆

最小堆要求父节点元素小于等于子节点元素

在这里插入图片描述

最大堆

最大堆要求父节点元素大于等于子节点元素
在这里插入图片描述

算法分析

最小堆实例代码

// 堆排序
    public void heapSort(int[] array){
        int len = array.length;
        for (int i = len/2-1; i >= 0 ; i--) {
            adjustHeap(array, i, len);
        }
        for (int i = len - 1; i >= 0 ; i--) {
            swap(array, i, 0);
            adjustHeap(array, 0, i);
        }

    }

    // 调整堆元素
    public void adjustHeap(int[] array, int i, int length){
        //先取出当前元素i
        int temp = array[i];
        //从i结点的左子结点开始,也就是2i+1处开始
        for(int k=i*2+1;k<length;k=k*2+1){
            //如果左子结点小于右子结点,k指向右子结点(k = i*2+1为左节点,k = i*2+1+1为右节点)
            if(k+1<length && array[k]<array[k+1]){
                k++;
            }
            //如果子节点大于父节点,将子节点值赋给父节点(不用进行交换,后续归位)
            if(array[k] > temp){
                array[i] = array[k];
                i = k;
            }else{
                break;
            }
        }
        //将temp值放到最终的位置
        array[i] = temp;
    }

测试:

int[] array = new int[]{3,2,3,1,2,4,5,5,5,6};
        heapSort(array);
        for (int num:array) {
            System.out.print(num + " ");
        }

最小堆代码效果
在这里插入图片描述

最大堆实例代码

// 堆排序
    public static void heapSort(int[] array){
        int len = array.length;
        for (int i = len/2-1; i >= 0 ; i--) {
            adjustHeap(array, i, len);
        }
        for (int i = len - 1; i >= 0 ; i--) {
            swap(array, i, 0);
            adjustHeap(array, 0, i);
        }

    }

    // 调整堆元素
    public static void adjustHeap(int[] array, int i, int length){
        //先取出当前元素i
        int temp = array[i];
        //从i结点的左子结点开始,也就是2i+1处开始
        for(int k=i*2+1;k<length;k=k*2+1){
            //如果左子结点小于右子结点,k指向右子结点(k = i*2+1为左节点,k = i*2+1+1为右节点)
            // if(k+1<length && array[k]<array[k+1])(大顶堆)
            if(k+1<length && array[k]>array[k+1]){
                k++;
            }
            //如果子节点大于父节点,将子节点值赋给父节点(不用进行交换,后续归位)
            //  if(array[k] > temp)(大顶堆)
            if(array[k] < temp){
                array[i] = array[k];
                i = k;
            }else{
                break;
            }
        }
        //将temp值放到最终的位置
        array[i] = temp;

    }

最大堆代码效果

在这里插入图片描述

6、希尔排序

主要流程

希尔排序是插入排序的一种又称“缩小增量排序”,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。

算法分析

实例代码:

 // 希尔排序
 // new int[]{73, 22, 93, 43, 55, 14, 28, 65, 39, 81};
    public static int[] hillSort(int[] array){
        int length = array.length;
        int mid = length/2;
        while (mid > 0){
            for (int i = mid; i < length; i++) {
                int temp = array[i];
                int index = i-mid;
                while (index >= 0 && array[index] > temp){
                    array[index+mid] = array[index];
                    index -= mid;
                }
                array[index + mid] = temp;
            }
            mid /= 2;
        }
        return array;
    }

结果:
在这里插入图片描述

7、归并排序

基本算法原理

第一步: 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步: 设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步: 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
第四步: 重复步骤3直到某一指针超出序列尾
第五步: 将另一序列剩下的所有元素直接复制到合并序列尾

算法分析

算法实例:

// new int[]{73, 22, 93, 43, 55, 14, 28, 65, 39, 81};
 public int[] mergeSort(int[] array, int left, int right){

        if(left == right){
            return new int[]{array[left]};
        }

        int mid = (right - left)/2 + left;
        int[] leftArray = mergeSort(array, left, mid);
        int[] rightArray = mergeSort(array, mid+1, right);
        int[] newArray = new int[leftArray.length + rightArray.length];

        int index = 0, i = 0, j = 0;
        while (i < leftArray.length && j < rightArray.length){
            newArray[index++] = leftArray[i] < rightArray[j] ? leftArray[i++] : rightArray[j++];
        }
        while (i < leftArray.length){
            newArray[index++] = leftArray[i++];
        }
        while (j < rightArray.length){
            newArray[index++] = rightArray[j++];
        }
        return newArray;
    }

结果:
在这里插入图片描述

8、计数排序

主要流程

假设输入的线性表L的长度为n,L=L1,L2,…,Ln;线性表的元素属于有限偏序集S,|S|=k且k=O(n),S={S1,S2,…Sk};则计数排序可以描述如下:

1、扫描整个集合S,对每一个Si∈S,找到在线性表L中小于等于Si的元素的个数T(Si);
2、扫描整个线性表L,对L中的每一个元素Li,将Li放在输出线性表的第T(Li)个位置上,并将T(Li)减1。

在这两个条件下,计数排序的复杂性为O(n)。计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。

算法分析

实例代码:

/**
     * 查找出最大值
     * @param array
     * @return
     */
    public static int findMax(int[] array){
        // 查找最大值
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < array.length; i++) {
            max = Math.max(max, array[i]);
        }
        return max;
    }
    /**
     * 查找出最小值
     * @param array
     * @return
     */
    public static int findMin(int[] array){
        // 查找最大值
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < array.length; i++) {
            min = Math.min(min, array[i]);
        }
        return min;
    }
    // 计数排序
    public static int[] countSort(int[] array){
        int max = findMax(array);
        int min = findMin(array);
        int[] result = new int[array.length];
        int temp = max - min + 1;
        int[] count = new int[temp];
        for (int i = 0; i < array.length; i++) {
            count[array[i] - min] += 1;
        }
        for (int i = 1; i < count.length; i++) {
            count[i] += count[i-1];
        }
        for (int i = array.length-1; i >= 0 ; --i) {
            result[--count[array[i]-min]] = array[i];
        }
        return result;
    }

结果:
在这里插入图片描述

9、桶排序

算法原理

桶排序或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间O(n)。但桶排序并不是 比较排序,它不受到 O(nlog n) 下限的影响。

算法分析

实例代码(7月9号更新:貌似有bug):

public static void main(String[] args) {
        int[] array = new int[]{73, 22, 93, 3, 55, 14, 280, 65, 39, 801};
        bucketSort(array);
        for (int num: array) {
            System.out.print(num + " ");
        }
    }
 /**
     * 查找出最大值
     * @param array
     * @return
     */
    public static int findMax(int[] array){
        // 查找最大值
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < array.length; i++) {
            max = Math.max(max, array[i]);
        }
        return max;
    }
    /**
     * 最大值的位数
     * @param number
     * @return
     */
    public static int findNumberBit(int number){
        int count = 0;
        while (number > 0){
            count ++;
            number /= 10;
        }
        return count;
    }
 // 桶排序
    public static void bucketSort(int[] array){
        ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            lists.add(new ArrayList<>());
        }
        int max = findMax(array);
        int num = findNumberBit(max);
        int len = array.length;

        int div = (int)Math.pow(10, num-1);
        // 插入数据并排序
        for (int i = 0; i < len; i++) {
            int temp = (array[i] / div) % 10;
            insertData(lists.get(temp), array[i]);
        }
        int index = 0;
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < lists.get(i).size(); j++) {
                array[index++] = lists.get(i).get(j);
            }
        }
    }

    public static void insertData(ArrayList<Integer> list, int data){
        ListIterator<Integer> iterator = list.listIterator();
        boolean insert = false;
        while (iterator.hasNext()){
            if(data < iterator.next()){
                iterator.previous();
                iterator.add(data);
                insert = true;
                break;
            }

        }
        if(!insert){
            iterator.add(data);
        }
    }

结果:
在这里插入图片描述

10、基数排序

主要流程

基数排序属于“分配式排序”,又称“桶子法”或bin sort基数排序法是属于稳定性的排序,其时间复杂度为O (nlog( r )m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

算法基本原理

MSD算法
最高位优先法,简称MSD法:先按k1排序分组,同一组中记录,关键码(最高位)k1相等,再对各组按(次高位)k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码(最低位)kd对各子组排序后。再将各组连接起来,便得到一个有序序列。

LSD算法
最低位优先法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。
LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好。MSD的方式与LSD相反,是由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个“桶子”中建立“子桶”,将每个桶子中的数值按照下一数位的值分配到“子桶”中。在进行完最低位数的分配后再合并回单一的数组中。

空间复杂度

一趟排序需要的辅助空间为 r (r个队列),但以后的排序中会重复使用这些队列,故基数排序的空间复杂度为O ( r ) 。

时间复杂度

基数排序需要进行 d 趟分配和收集,一趟分配需要O ( n ),一趟搜集需要O ( r ) ,所以基数排序的时间复杂度为 O ( d ( n + r ) ) ,它与序列的初始状态没有关系。

算法代码实例

LSD算法代码实例:

	// new int[]{703, 22, 93, 413, 545, 14, 28, 65, 39, 810}
 	public int[] radixSort(int[] array){
        if(array == null || array.length < 2){
            return array;
        }
        int max = findMax(array);
        int num = findNumberBit(max);
        int len = array.length;
        int mod = 10, div = 1;
        ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            lists.add(new ArrayList<>());
        }
        for (int i = 0; i < num; i++, mod *= 10, div *= 10) {
            for (int j = 0; j < len; j++) {
                int temp = (array[j] % mod)/div;
                lists.get(temp).add(array[j]);
            }
            int n = 0;
            for (int j = 0; j < lists.size(); j++) {
                for (int k = 0; k < lists.get(j).size(); k++) {
                    array[n++] = lists.get(j).get(k);
                }
                lists.get(j).clear();
            }
        }
        return array;
    }

    /**
     * 查找出最大值
     * @param array
     * @return
     */
    public int findMax(int[] array){
        // 查找最大值
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < array.length; i++) {
            max = Math.max(max, array[i]);
        }
        return max;
    }

    /**
     * 最大值的位数
     * @param number
     * @return
     */
    public static int findNumberBit(int number){
        int count = 0;
        while (number > 0){
            count ++;
            number /= 10;
        }
        return count;
    }

结果:
在这里插入图片描述
MSD算法代码实例(递归加回溯):(2022/4/20更新:有bug,负数无法进行排序)

// main函数调用实例
 public static void main(String[] args) {
       int[] array = new int[]{73, 22, 93, 43, 55, 14, 28, 65, 39, 81};
       int max = findMax(array);
       int num = findNumberBit(max);
       ArrayList<Integer> list = new ArrayList<>();
       radixSortMSD(array, list, num);
       for (int temp: list) {
            System.out.print(temp + " ");
       }

    }
 public static void radixSortMSD(int[] array, ArrayList<Integer> str, int bit){
        if(array == null || array.length < 2){
            return;
        }
        int max = findMax(array);
        int num = findNumberBit(max);
        int len = array.length;

        int div = (int)Math.pow(10, bit);

        ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            lists.add(new ArrayList<>());
        }

        for (int j = 0; j < len; j++) {
            int temp = (array[j] / div) % 10;
            lists.get(temp).add(array[j]);
        }

        int index = 0;
        for (int j = 0; j < 10; j++) {
            int length = lists.get(j).size();
            if(length == 1){
                str.add(lists.get(j).get(0));
            }else if(length > 1){
                int[] temp = new int[length];
                for (int k = 0; k < length; k++) {
                    temp[index++] = lists.get(j).get(k);
                }
                num --;
                radixSortMSD(temp, str, num);
                num ++;
            }
        }
   }
     // 查找最大值
    public static int findMax(int[] array){
        // 查找最大值
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < array.length; i++) {
            max = Math.max(max, array[i]);
        }
        return max;
    }

   // 查找最大值的位数
    public static int findNumberBit(int number){
        int count = 0;
        while (number > 0){
            count ++;
            number /= 10;
        }
        return count;
    }

结果:
在这里插入图片描述

11、二分查找算法

主要流程

二分查找也称折半查找,它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一个子表,否则进一步查找后一个子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

基本要求

必须是采用顺序存储结构。
必须按关键字大小有序排列

算法分析

算法复杂度
二分查找的基本思想是将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果x<a[n/2],则只要在数组a的左半部分继续搜索x,如果x>a[n/2],则只要在数组a的右半部搜索x。时间复杂度即是while循环的次数。总共有n个元素,渐渐跟下去就是n,n/2,n/4,…n/2^k(接下来操作元素的剩余个数),其中k就是循环的次数,由于 n/2^k 取整,取整后大于等于1即令n/2^k=1可得k=log2n,(是以2为底,n的对数)所以时间复杂度可以表示O(h)=O(log2n)

比较的次数
在这里插入图片描述

当顺序表有n个关键字时:查找失败时,至少比较a次关键字;查找成功时,最多比较关键字次数是b。注意:a,b,n均为正整数。

算法实例

 public int binarySearch(int[] array, int k){
        int end = array.length-1;;
        int start = 0;
        while (start <= end){
            int mid = (start + end) >> 1;
            if(array[mid] == k){
                return mid;
            }else if(array[mid] > k){
                start = mid + 1;
            }else if(array[mid] < k){
                end = mid - 1;
            }
        }
        return -1;
    }
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值