几种常见排序算法

一、综述

(一)、排序概念

​ 排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。

(二)、分类

  • 稳定排序:假设在待排序的文件中,存在两个或两个以上的记录具有相同的关键字,在用某种排序法排序后,若这些相同关键字的元素的相对次序仍然不变,则这种排序方法是稳定的。其中冒泡,插入,基数,归并属于稳定排序,选择,快速,希尔,归属于不稳定排序。
  • 就地排序:若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间为O(1),则称为就地排序。

(三)、总览

说明:排序算法众多,这里只介绍几种常见排序算法
在这里插入图片描述

二、插入排序

(一)、思路

用 bound 下标将数组分为两个部分,[0,bound) 和 [bound,size) 两个部分,前者是已排序区间,后者是未排序区间,每次从未排序区间把 bound 位置的元素拿出来插入在已排序区间的正确位置。

​ 注:需要循环 (array.length -1)次 来为未排序区间的每一个元素在已排序区间找位置

(二)、代码

public static void insertSort(int[] array) {
    // 1.找位置
    for (int bound = 1; bound < array.length; bound++) {
        int v = array[bound];   // 准备要插入的元素
        int cur = bound - 1;   // 在已排序元素序列中为待排序元素找要插入的位置
        for (; cur >= 0; cur--) {
            if (array[cur] > v) {
                array[cur + 1] = array[cur];
            } else {
                // 说明此时已经找到了合适的位置
                break;
            }
        }
        // 将bound位置的元素插入进去
        // 始终有一个空位置,就是下标 cur + 1 ,初始的空位置是 bound = cur + 1;cur 在不断变
        array[cur + 1] = v;
    }
}

(三)、算法复杂度

时间复杂度空间复杂度
最好平均最坏O(1)
O(n)O(n^2)O(n^2)

插入排序,初始数据越接近有序,时间效率越高

三、希尔排序

(一)、思路

​ 将无序序列分为 gap 组,每一组进行插入排序,这样不断地减少组数来达到排序效果。从结果来看,gap 每减小一次,数组地有序性就会得到增强,直至完全有序。

(二)、代码

public static void shellSort(int[] array) {
    // gap 怎么设计?
    int gap = array.length / 2;
    // 1.当gap > 1 地时候,就在每一小组内进行组内排序
    while (gap > 1) {
        insertSortGap(array, gap);
        gap /= 2;
    }
    // 2.最后在全部序列上进行一次插入排序
    insertSortGap(array, 1);
}

private static void insertSortGap(int[] array, int gap) {
    // 循环处理
    for (int bound = 1; bound < array.length; bound++) {
        int val = array[bound]; // 要处理的一个数字
        int cur = bound - gap;
        for (; cur >= 0; cur = cur - gap) {
            if (array[cur] > val) {
                array[cur + gap] = array[cur];
            } else {
                // 已经找到 v 要插入的地方
                break;
            }
        }
        array[cur + gap] = val;
    }
}

(三)、算法复杂度

时间复杂度空间复杂度
最好平均最坏O(1)
O(n)O(n^1.3)O(n^2)

三、选择排序

(一)、思路

我们将无序序列分为有序(让前面一部分有序)和无序两部分,不断在无序数组中找到最小的元素放在有序序列的最后一个的后面(此时无序序列的开头),不断在无序数组中最小直至最后一个元素。

(二)、代码

// 每次在数组中找到最小的元素放在待排序部分的开头
public static void selectSort(int[] array) {
    // 循环 length 次,找到每一个位置的需要插入的元素
    for (int bound = 0; bound < array.length; bound++) {
        // 从 bound + 1;开始找最小的元素,然后放在 bound 位置
        for (int cur = bound + 1; cur < array.length; cur++) {
            // 挑选出来的最小元素,需要放在下标为 bound 的位置
            if (array[cur] < array[bound]) {
                int tmp = array[cur];
                array[cur] = array[bound];
                array[bound] = tmp;
            }
        }
    }
}

(三)、算法复杂度

时间复杂度空间复杂度
O(n)O(1)
数据不敏感数据不敏感

四、堆排序

(一)、思路

在传进来数组上直接搞一个大堆,然后将堆的第一个和当前堆最后一个交换。(注意,堆在不断缩小,有序性在不断增加)这样不断重复,就能够从后到前使数组有序。

注意:升序建立大堆,降序建立小堆

(二)、代码

public static void heapSort(int[] array) {
    // 1.用传进来的数组建立一个大堆
    createHeap(array);
    // 循环 array.length - 1 次,最后一次它本身,不用交换
    for (int i = 0; i < array.length - 1; i++) {
        // 2.将第一个和最后一个(相对)元素交换
        //    相对位置的最后一个下标始终向前移动一个位置
        // 堆中的元素个数
        int heapSize = array.length - i;
        // 交换元素,使得后边的序列有序
        swap(array, 0, heapSize - 1);
        // 交换完成使堆中的元素减 1 
        heapSize--;
        // 3.将剩余部分重新向下调整使保持堆结构
        shiftDown(array, heapSize, 0);
    }
}

// 用传进来的数组构建一个大堆
private static void createHeap(int[] array) {
    // 从后往前对每一个元素进行向下调整
    for (int index = (array.length - 1 - 1) / 2; index >= 0; index--) {
        shiftDown(array, array.length, index);
    }
}

// 交换数组中两个元素
private static void swap(int[] array, int a, int b) {
    int tmp = array[a];
    array[a] = array[b];
    array[b] = tmp;
}

// 向下调整
public static void shiftDown(int[] array, int size, int index) { // 大堆
    // 将需要调整的位置初始化为parent
    int parent = index;
    int child = parent * 2 + 1;
    while (child < size) { // 保证 child 不能越界
        // 使 child 一定是左右孩子中最大的那个
        if (child + 1 < size && array[child + 1] > array[child]) {
            child = child + 1;
        }
        // 然后用最大的那个 child 和 parent 比较
        if (array[parent] < array[child]) {
            int tmp = array[parent];
            array[parent] = array[child];
            array[child] = tmp;
        } else {
            // 结束
            break;
        }
        parent = child;
        child = 2 * parent + 1;
    }
}

(三)、算法复杂度

时间复杂度空间复杂度
O(n * log(n))O(1)
数据不敏感

五、冒泡排序

(一)、思路

在无序序列中循环比较相邻两个元素的的值,这里,我们从后往前比较,将小的值不断交换到前边有序序列中

(二)、代码

public static void bubbleSort(int[] array) {
    // 按照每次找最小的方式来进行排序. (从后往前比较交换)
    for (int bound = 0; bound < array.length; bound++) {
        // [0,bound) 已经排序区间
        // [bound,size) 待排序算法
        
        for (int cur = array.length - 1; cur > bound; cur--) {
            if (array[cur] < array[cur - 1]) {
                int tmp = array[cur];
                array[cur] = array[cur - 1];
                array[cur - 1] = tmp;
            }
        }
    }
}

(三)、算法复杂度

时间复杂度空间复杂度
最好平均最坏O(1)
O(n)O(n^2)O(n^2)

六、快速排序

(一)、思路

快速排序:借助递归来完成
    1.在待排序区间中找一个基准值(一般为区间的第一个或者最后一个元素)
    2.以基准值为中心,将整个区间整理成三个部分:左侧元素都小于基准值,右侧元素都大于基准值
    3.再次针对左侧和右侧区间,重复以上步骤进行递归

(二)、代码

1,递归写法

public static void quickSort(int[] array) {
    // 辅助完成递归过程,这里使用的闭区间
    quickSortHelper(array, 0, array.length - 1);
}

// 辅助函数 : [left , right]
private static void quickSortHelper(int[] array, int left, int right) {
    if (left >= right) {
        // 此时,区间有 0 或者 1 个元素,不用排序
        return;
    }
    int index = partition(array, left, right);
    quickSortHelper(array, left, index - 1);
    quickSortHelper(array, index + 1, right);
}

/*
    partition 方法做:
        取出一个基准值,让小于基准值的放在基准值左边,大于基准值的放在右边
        并且返回基准值在这样一次调整中的位置
 */
private static int partition(int[] array, int left, int right) {
    int beg = left;
    int end = right;
    // 取最右侧元素为基准值
    int base = array[right];
    while (beg < end) {
        // 从左往右找比基准值大的元素
        while (beg < end && array[beg] <= base) {
            beg++;
        }
        // 从右往左找比基准值比基准值小的元素
        while (beg < end && array[end] >= base) {
            end--;
        }
        // 执行完上面的步骤,beg 要么和end重合,要么end就指向一个小于base的值
        // 交换beg 和 end 的值
        swap(array, beg, end);
    }
    swap(array, beg, right);
    return beg;
}

private static void swap(int[] array, int beg, int end) {
    int tmp = array[beg];
    array[beg] = array[end];
    array[end] = tmp;
}

2,非递归写法(借助栈)

public static void quickSortByLoop(int[] array) {
    // 借助栈来实现递归过程
    Stack<Integer> stack = new Stack<>();
    // 初始情况下,先把左右边界进行入栈(先右后左),左右边界仍然构成前闭后开区间
    stack.push(array.length - 1);
    stack.push(0);

    while (!stack.isEmpty()) {
        // 注意和 push 的顺序正好相反
        int left = stack.pop();
        int right = stack.pop();
        if (left > right) {
            // 区间中只有 1个 或者 0 个
            continue;
        }
        // 通过 partition 把区间整理成以基准值问中心的形式
        int index = partition(array, left, right);
        // [index + 1,right] 基准值右侧区间
        stack.push(right);
        stack.push(index + 1);
        // [left ,index] 基准值左侧区间
        stack.push(index-1);
        stack.push(left);
    }
/*
    	partition 方法做:
        取出一个基准值,让小于基准值的放在基准值左边,大于基准值的放在右边
        并且返回基准值在这样一次调整中的位置
 */
private static int partition(int[] array, int left, int right) {
    int beg = left;
    int end = right;
    // 取最右侧元素为基准值
    int base = array[right];
    while (beg < end) {
        // 从左往右找比基准值大的元素
        while (beg < end && array[beg] <= base) {
            beg++;
        }
        // 从右往左找比基准值比基准值小的元素
        while (beg < end && array[end] >= base) {
            end--;
        }
        // 执行完上面的步骤,beg 要么和end重合,要么end就指向一个小于base的值
        // 交换beg 和 end 的值
        swap(array, beg, end);
    }
    swap(array, beg, right);
    return beg;
}

private static void swap(int[] array, int beg, int end) {
    int tmp = array[beg];
    array[beg] = array[end];
    array[end] = tmp;
}
}

(三)、算法复杂度

时间复杂度空间复杂度
最好平均最好平均
O(n)O(n*log(n))O(n)O(log(n))

(四)、优化

1.优化基准值的取法,三个元素取中间元素(最左侧元素,中间位置元素,最右侧元素,取中间值作为基准值,把确认的基准值交换到数组末尾或者开头,为了后面的整理动作做铺垫);

2.当区间已经比较小的时候再去递归其实效率就不高了,不在继续递归,而是直接进行插入排序;

3,如果区间特别大,递归深度也会非常深,当递归深度达到一定程度的时候,把当前区间的排序使用堆排序。

七,归并排序

归并排序可以用于外部排序,也可以适用于链表排序

(一)、思路

基于递归的方式,把待排序区间分成均等的两份,如果两个区间是有序区间,就可以按照把两个有序数组合并成一个有序数组来进行合并,如果当前分出的区间不是有序的,继续往下分割,如果当前区间中只有一个元素就一定是有序了。

(二)、代码

1.递归写法

public static void merge(int[] array, int low, int mid, int high) {
    int[] output = new int[high - low];
    int outputIndex = 0;
    int cur1 = low;
    int cur2 = mid;
    while (cur1 < mid && cur2 < high) {
        // 小的先放在 output 数组中
        if (array[cur1] <= array[cur2]) {
            output[outputIndex] = array[cur1];
            outputIndex++;
            cur1++;
        } else {
            output[outputIndex] = array[cur2];
            outputIndex++;
            cur2++;
        }
    }
    // 当上面循环结束的时候,肯定是 cur1 或者 cur2有一个先达到末尾,另一个还剩下一些内容
    // 把剩下的内容拷贝到 output 中
    while (cur1 < mid) {
        output[outputIndex] = array[cur1];
        outputIndex++;
        cur1++;
    }
    while (cur2 < high) {
        output[outputIndex] = array[cur2];
        outputIndex++;
        cur2++;
    }
    for (int i = 0; i < high - low; i++) {
        array[low + i] = output[i];
    }
}

public static void mergeSort(int[] array) {
    mergeSortHelper(array, 0, array.length);
}

// 这里使用的是前闭后开区间
private static void mergeSortHelper(int[] array, int low, int high) {
    if (high - low <= 1) {
        // 两者差值小于等于1表示区间只有 0 个元素或者1个元素
        return;
    }
    int mid = (low + high) / 2;
    mergeSortHelper(array, low, mid);
    mergeSortHelper(array, mid, high);
    merge(array, low, mid, high);
}

2,非递归写法

public static void mergeSortByLoop(int[] array){
    for (int gap = 1; gap <array.length ; gap*=2) {
        for (int i = 0; i < array.length; i+=2*gap) {
            int beg = i;
            int mid = i + gap;
            int end = i + 2*gap;
            if (mid > array.length){
                mid = array.length;
            }
            if (end > array.length){
                end = array.length;
            }
            merge(array,beg,mid,end);
        }
    }
}

public static void merge(int[] array, int low, int mid, int high) {
    int[] output = new int[high - low];
    int outputIndex = 0;
    int cur1 = low;
    int cur2 = mid;
    while (cur1 < mid && cur2 < high) {
        // 小的先放在 output 数组中
        if (array[cur1] <= array[cur2]) {
            output[outputIndex] = array[cur1];
            outputIndex++;
            cur1++;
        } else {
            output[outputIndex] = array[cur2];
            outputIndex++;
            cur2++;
        }
    }
    // 当上面循环结束的时候,肯定是 cur1 或者 cur2有一个先达到末尾,另一个还剩下一些内容
    // 把剩下的内容拷贝到 output 中
    while (cur1 < mid) {
        output[outputIndex] = array[cur1];
        outputIndex++;
        cur1++;
    }
    while (cur2 < high) {
        output[outputIndex] = array[cur2];
        outputIndex++;
        cur2++;
    }
    for (int i = 0; i < high - low; i++) {
        array[low + i] = output[i];
    }
}

(三)、算法复杂度

时间复杂度空间复杂度
O(n*log(n))O(n)

稳定排序

几种常见排序 基于比较的排序算法: 下界是 nlgn 1.1 SelectionSort:每次选出最下的元素,放在当前循环最左边的位置。 1.2 BubbleSort:每次比较相邻的两个数,使得最大的数像气泡一样冒到最右边。 1. 3 InsertionSort:每次拿起一个数,插入到它左边数组的正确位置。 1.4 QuickSort:选择一个数,作为标准,小于它的放在左边,大于它的放在右边。并把它放在中间;递归地对左右子数组进行排序。 实现时:1. 确定递归结束条件,初始化左右游标, 选择标准数; 2. while循环,do while实现两个游标同时向中间移动,置换; 3. 置换标准数和右边游标所指的数; 4. 递归调用,对左右子数组进行排序。 1. 5 HeapSort:用最大堆实现。 实现时:建堆:置换堆顶元素和最后一个元素,堆大小减少,保持新的堆为最大堆; 保持最大堆: 从底向上依次保持最大堆,从第一个父节点到根部。 1.6 MergeSort:拆分数组,递归实现排序,二路归并。用哨兵来阻止游标的越界。 线性时间运行的算法: 1.7 CountingSort: 假设数据分布在0到k之间的。对于每个输入x,确定出小于x的数的个数。假设小于x的数有17个,那么x就应该在第18个输出位置。 1. 8 Radix sort(基数排序):从最低位开始,每位采用稳定的排序算法(如计数排序)。 1.9 Bucket sort:当输入数据比较均匀时采用。 先将数据区间分为n个桶,把数据分放到对应的桶中;对桶内的数据采用插入排序;再把各个桶的排序结果串起来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值