10大排序(笔记)

0. 总览

在这里插入图片描述

1. 冒泡

(1)一直比较交换

/**
 * 冒泡排序-无优化
 * 每一次两两进行比较,(大交换位置)最后一个是最大的
 * 相邻之间进行比较
 * [0,len)      len-1 最大
 * [0,len-1)    len-2 最大
 * [0,len-2)    len-3 最大
 */
public class BubbleSort1<T extends Comparable<T>> extends Sort<T> {
    @Override
    protected void sort() {
        for (int end = array.length-1; end > 0; end--) {
            for (int begin = 1; begin <= end; begin++) {
                if (cmp(begin,begin - 1) < 0){//array[i] - array[i-1]
                    swap(begin,begin - 1);       // 交换两个数字
                }
            }
        }
    }
}

(2)在交换过程中判断是否出现 完全有序

/**
 * 冒泡排序-优化
 * 当在一次比较中,出现完全有序情况,结束,(即没有交换过)
 */
public class BubbleSort2<T extends Comparable<T>> extends Sort<T> {
    @Override
    protected void sort() {
        for (int end = array.length-1; end > 0; end--) {
            boolean sorted = true;
            for (int begin = 1; begin <= end; begin++) {
                if (cmp(begin,begin - 1) < 0){//array[i] - array[i-1]
                    swap(begin,begin - 1);       // 交换两个数字
                    sorted = false;
                }
            }
            if (sorted){
                break;
            }
        }
    }
}

(3)在交换中判断,最后一次交换的位置,下一次的结束点

/**
 * 冒泡排序-优化2
 * 如果序列尾部已经局部有序,可以记录最后1次交换的位置,减少比较次数
 */
public class BubbleSort3<T extends Comparable<T>> extends Sort<T> {
    @Override
    protected void sort() {
        for (int end = array.length-1; end > 0; end--) {
            int sortedIndex = 1;
            for (int begin = 1; begin <= end; begin++) {
                if (cmp(begin,begin - 1) < 0){//array[i] - array[i-1]
                    swap(begin,begin - 1);       // 交换两个数字
                    sortedIndex = begin;
                }
            }
            end = sortedIndex;
        }
    }
}

2. 选择

/**
 * [a,a,a,a] 从前4个里找到 最大的下标,与最后一个 (第4个) 交换
 * [a,a,a,a] 从前3个里找到 最大的下标,与最后一个 (第3个) 交换
 * [2,2,1]
 */
public class SelectionSort <T extends Comparable<T>> extends Sort<T> {

    @Override
    protected void sort() {
        for (int end = array.length-1; end > 0 ; end--) {
            int maxIndex = 0;  // 初始下标
            for (int begin = 1; begin <= end; begin++) {
                if (cmp(maxIndex,begin) <= 0){ // arr[maxIndex]-arr[begin]
                    maxIndex = begin;
                }
            }
            swap(maxIndex,end); // 最大值下标 与 最后一个下标交换位置
        }

    }
}

3. 插入

(1)冒泡交换

/**
 *  插入排序
 *  与之前的 已经排好序的 进行 冒泡交换
 */
public class InsertionSort1 <T extends Comparable<T>> extends Sort<T> {
    @Override
    protected void sort() {
        for (int begin = 1; begin < array.length; begin++) {
            int cur = begin;
            while (cur > 0 && cmp(cur,cur-1) < 0) {
                swap(cur,cur-1);
                cur--;
            }
        }
    }
}

(2)后移插入

/**
 *  插入排序
 *  将 交换 改为 移动
 *  最后将  元素直接 放到最终位置
 */
public class InsertionSort2<T extends Comparable<T>> extends Sort<T> {
    @Override
    protected void sort() {
        for (int begin = 1; begin < array.length; begin++) {
            int cur = begin;
            T t = array[begin];  // 备份元素
            while (cur > 0 && cmp(t,array[cur-1]) < 0) {
                array[cur] = array[cur-1];  // 往后挪动
                cur--;
            }
            array[cur] = t;
        }
    }
}

(3)二分后移插入

/**
 * 插入排序-优化2
 * 二分搜索进行优化
 */
public class InsertionSort3 <T extends Comparable<T>> extends Sort<T> {
    @Override
    protected void sort() {
        for(int begin = 1; begin < array.length; begin++){
            // 将遍历到的元素插入到前面已经排好序的序列中
            insert(begin, search(begin)); //search() 查找到要插入的位置
        }
    }
    /**
     * 将source位置的元素插入到dest位置
     */
    private void insert(int source, int dest){
        T v = array[source]; // 备份要插入的元素
        // 将 [insertIndex, begin)范围内的元素往右边挪动一个单位
        for(int i = source; i > dest; i--){
            array[i] = array[i - 1];
        }
        array[dest] = v;
    }

    /**
     * 利用二分搜索找到index位置元素的待插入位置
     * 已经排好序数组的区间范围是[0,index)
     *  存在多个,找到最右边的下标,的下一个
     * [begin,end)
     * begin == 0             最小的
     * begin == end           最大的
     * begin == (0,end-1)     中间
     */
    private int search(int index){
        int begin = 0;
        int end = index;
        while(begin < end){
            int mid = (begin + end) >> 1;
            if(cmp(array[index], array[mid]) < 0){
                end = mid;
            }else if(cmp(array[index], array[mid]) == 0){  // 满足,往右找
                begin = mid + 1;
            }else{   //  > 0
                begin = mid + 1;
            }
        }
        return begin;
    }
}

4. 堆排序

是对选择的提升

/**
 * 堆排序
 * 1. 原地建堆
 * 2.  交换堆顶元素与堆尾元素(把最大值放到最后面)
 *     堆的元素数量减 1(不管最后已经放到最后的最大值)
 *     对 0 位置进行 1 次 heapity 操作
 *     
 *
 * 关于堆的叶子结点,与非叶子节点,以数组为例:[0,n) ,数组长度n
 *  根节点: i ; 左孩子:i*2 +1 ; 右孩子:i*2 +2 ;
 *  假设 2 种情况: 最后一个非叶子结点:i,--->  a. 只有左孩子 || b. 左右孩子都有
 * a.  2* i + 1 = n-1       i = n/2-1        (n为偶数)
 * b.  2* i + 2 = n-1       i = n/2-1.5      (n为奇数)
 *     b 的情况根据 java的:整数除不尽时向下取整 ,等价为 n/2 -1
 * 所以最后一个非叶子节点的下标为 n/2 - 1 ;
 *
 *
 * 自上而下的上滤时间复杂度:O(n log n)
 * 自下而上的下滤时间复杂度:O(n log k)  继续计算的话会是 O(n)
 */


public class HeapSort<T extends Comparable<T>> extends Sort<T> {
    private int heapSize; // 堆大小(里面有多少元素)
    @Override
    protected void sort() {
        heapSize = array.length;
        //原地建堆
        buildHeap();
        // 堆中只有一个元素,结束
        while (heapSize > 1) {
            // 交换堆顶元素和尾部元素
            swap(0,--heapSize);  // 下标与堆大小的关系
            // 对新的堆顶:0,进行堆化
            heapity(0);
        }
    }
    // 对 0 - n/2-1 的非叶子节点进行自下向上进行堆化,构建大顶堆
    // 自下而上的下滤  O(n log k)
    public void buildHeap() {
        for (int i = (heapSize)/2-1; i >= 0 ; i--) {
            heapity(i);
        }
    }
    // 堆化 以满足大顶堆 条件
    private void heapity(int index) {
        while (true) {
            int maxValueIndex = index;
            // 下面2个if得到 根,左,右 3节点的最大节点下标
            if (2 * index +1 < heapSize && cmp(maxValueIndex,2*index+1) < 0){
                maxValueIndex = 2 * index + 1;  // 左节点比父节点大
            }
            if (2 * index +2 < heapSize && cmp(maxValueIndex,2*index+2) < 0){
                maxValueIndex = 2 * index + 2;  // 右节点比父节点,或左节点大
            }
            if (maxValueIndex == index){
                break; // 说明当前节点值为最大值,无需往下迭代
            }
            swap(index,maxValueIndex);
            index = maxValueIndex;
        }
    }
}

5. 归并

/**
 * 归并排序
 */
@SuppressWarnings("unchecked")
public class MergeSort <T extends Comparable<T>> extends Sort<T> {
    private T[] leftArray;

    @Override
    protected void sort() {
        // 准备一段临时的数组空间, 在merge操作中使用
        leftArray = (T[])new Comparable[array.length >> 1];
        sort(0, array.length);
    }
    /**
     * 对 [begin, end) 范围的数据进行归并排序
     */
    private void sort(int begin, int end){
        if(end - begin < 2) {
            return; // 至少要2个元素
        }
        int mid = (begin + end) >> 1;
        sort(begin, mid); // 归并排序左半子序列
        sort(mid, end);	  // 归并排序右半子序列
        merge(begin, mid, end); // 合并整个序列
    }
    /**
     * 将 [begin, mid) 和 [mid, end) 范围的序列合并成一个有序序列
     *  两个数组 : leftArray ,[begin,end) :array的一个范围
     *  最终 [begin,end) 有序;
     */
    private void merge(int begin, int mid, int end){
        int li = 0, le = mid - begin; // 左边数组(基于leftArray)
        int ri = mid, re = end;	// 右边数组(array)
        int ai = begin; // array的索引,范围是[begin,end);

        // 备份左边数组到leftArray
        for(int i = li; i < le; i++){
            leftArray[i] = array[begin + i];
        }
        /**
         * cmp(array[ri], leftArray[li])  : array[ri] - leftArray[li]
         * 稳定性(相同数字不改变相对位置):[L,l] 排完序 : [L,l] (稳定)  ; [l,L] (不稳定)
         *
         * 左边结束(li < le)----->直接结束 (右边本来就在应在位置上)
         * 右边结束(ri < re)----->直接赋值 (leftArray赋值)
         */
        // 如果左边还没有结束
        while(li < le){ // li == le 左边结束, 则直接结束归并
            // 右边合法 && 右边小
            if(ri < re && cmp(array[ri], leftArray[li]) < 0){ // cmp改为<=0会失去稳定性
                array[ai++] = array[ri++]; // 右边<左边, 拷贝右边数组到array
            }else{
                array[ai++] = leftArray[li++]; // 左边<=右边, 拷贝左边数组到array
            }
        }
    }
}

6. 快速

(1)轴点排序法1,整体上是优秀的

开始表格里的复杂度按照第一个排序说明的

/**
 * 快速排序
 * 轴元素
 * 左右指针移动
 */
public class QuickSort<T extends Comparable<T>> extends Sort<T> {
    @Override
    protected void sort() {
        sort(0, array.length);
    }
    /**
     * 对 [begin, end) 范围的元素进行快速排序
     */
    private void sort(int begin, int end){
       if (end - begin < 2){ // 只有一个元素
           return;
       }
       // 确定轴点元素
       int mid = pivotIndex(begin, end);
       // 对子序列进行快速排序
       sort(begin,mid);
       sort(mid+1,end);
    }
    /**
     * 构造出 [begin, end) 范围的轴点元素
     * @return 轴点元素的最终位置
     */
    private int pivotIndex(int begin, int end){
//        不能将begin作为轴点,防止最坏情况(恶意): 7,1,2,3,4,5,6
//        随机位置与轴点 交换顺序
       swap(begin,begin + (int)Math.random()*(end-begin));
       // 备份begin 位置的元素
       T pivot = array[begin];
       // end 指向最后一个元素
       end--;
       while (begin < end) {
           // 1. 两个while 将左右指针的转移,完美
           while (begin < end) {// 从右往左扫描
               //右指针开始
               // 轴点相等的元素,要放到相反位置,不可以 <= , >=
               // 结果是 子序列极度不均匀,出现最坏复杂度O (n^2)
               // 个人感觉--> 左右指针转化次数越多,越不可能出现最坏复杂度
               if (cmp(pivot,array[end]) < 0){  // 轴 < 右边 ,
                   end--;
               }else {                          // 轴 >= 右边,放到另一边
                   array[begin++] = array[end];
                   break;
               }
           }
           // 2.
           while (begin < end) {// 从左往右扫描
               if (cmp(pivot,array[begin]) > 0){// 轴 > 左边 ,
                   begin++;
               }else {
                   array[end--] = array[begin];
                   break;
               }
           }
       }
       // 放入最终位置
       array[begin] = pivot;
       // 返回轴点元素的位置
       return begin;
    }
}

(2) 对于轴点的另一种排序:整体是很不好的,好记忆

		private int pivotIndex(int begin, int end){
	//        不能将end作为轴点,防止最坏情况(恶意): 7,1,2,3,4,5,6
	//        随机位置与轴点 交换顺序
	       swap(begin,begin + (int)(Math.random()*(end-begin)));
	       // index 保证在它前面的都是 比 begin 轴点小的数字
	       int index = begin+1;
	        for (int i = begin + 1; i < end; i++) {
	            if (cmp(array[i],array[begin]) < 0){
	                swap(i,index);
	                index++;
	            }
	        }
	        swap(begin,index-1);
	       // 返回轴点元素的位置
	       return index-1;
	    }

7. 希尔

/**
 * 希尔排序
 *
 * 希尔排序把序列看作是一个矩阵,分成 𝑚 列,逐列进行排序
 *
 * 𝑚 从某个整数逐渐减为1
 * 当 𝑚 为1时,整个序列将完全有序
 * 因此,希尔排序也被称为递减增量排序(Diminishing Increment Sort)
 *
 * 矩阵的列数取决于步长序列(step sequence):
 *
 * [0,1,2,4]
 * [5,6,7,8]
 */

内部是插入排序


public class ShellSort <T extends Comparable<T>> extends Sort<T> {

    @Override
    protected void sort() {
        // 根据元素数量算出步长序列
        List<Integer> stepSequence = sedgewickStepSequence();
        // 按步长序列划分进行排序
        for (Integer step : stepSequence) {
            sort(step); // 按step进行排序
        }
    }

    /**
     * 分成step列进行排序
     */
    private void sort(int step){
        // 列 column
        for (int col = 0; col < step; col++) {
            // 内部是插入排序,可用最优的二分插入排序,这里用最简单的,明白原理是关键
            for (int begin = col + step; begin < array.length; begin += step) {
                // col+step , col + 2*step , col + 3*step
                int cur = begin;
                // cur > col 或 cur > step-1
                while (cur > col && cmp(cur,cur - step) < 0) {
                    swap(cur,cur - step);
                    cur -= step;
                }
            }
        }
    }
    /**
     * 希尔本人提出的步长序列
     * 1,2,4,8,16,32
     */
    public List<Integer> shellStpSequence(){
        LinkedList<Integer> lists = new LinkedList<>();
        int len = array.length;
        // 每次取一半
        while ((len >>= 1) > 0){
            lists.add(len);
        }
        return lists;
    }
    /**
     * 目前效率最高的步长序列
     * 最坏情况时间复杂度是 O(n4/3) ,1986年由 Robert Sedgewick 提出
     */
    private List<Integer> sedgewickStepSequence() {
        List<Integer> stepSequence = new LinkedList<>();
        int k = 0, step = 0;
        while (true) {
            if (k % 2 == 0) {
                int pow = (int) Math.pow(2, k >> 1);
                step = 1 + 9 * (pow * pow - pow);
            } else {
                int pow1 = (int) Math.pow(2, (k - 1) >> 1);
                int pow2 = (int) Math.pow(2, (k + 1) >> 1);
                step = 1 + 8 * pow1 * pow2 - 6 * pow2;
            }
            if (step >= array.length) {
                break;
            }
            stepSequence.add(0, step);
            k++;
        }
        return stepSequence;
    }
}

8. 计数排序

(1)非负整数

/**
     *  只能对 非 负数 整数 进行排序
     *  及其   浪费内存空间
     *  不稳定
     *  O(n)
     */
    private void test1(){
//        数组里的最大值
        int maxLen  = array[0];
        int len = array.length;
        for (int i = 1; i < len; i++) {
            if (array[i] > maxLen){
                maxLen = array[i];
            }
        }
//        存放数字出现次数 的数组
        int[] counts = new int[maxLen+1];
        for (int i = 0; i < len; i++) {
            counts[array[i]]++;
        }
//        输出
        int index = 0;
        for (int i = 0; i < maxLen+1; i++) {
            while (counts[i]-- > 0) {
            	array[index++] = i;
            }
        }
    }

(2)整数

/**
     *  最终版
     *  所有整数
     *  取范围
     * 可以稳定
     */
    private void test2(){
//        数组里的最大值,最小值 确定长度
        int maxLen  = array[0];
        int minLen  = array[0];
        for (int i = 1; i < array.length; i++) {  // 不可以if-else min,max可能相同
            if (array[i] > maxLen){
                maxLen = array[i];
            }
            if (array[i] < minLen){
                minLen = array[i];
            }
        }
//        // 开辟内存空间,存储次数
        int[] counts = new int[maxLen - minLen +1];
        for (int i = 0; i < array.length; i++) {
            counts[array[i] - minLen]++; // 数字 与 下标 相对应
        }
//        累加次数
        for (int i = 1; i < counts.length; i++) {
            counts[i] += counts[i-1];
        }
//         从后往前遍历元素,将它放到有序数组中的合适位置
        int[] newArray = new int[array.length];
        for (int i = array.length-1; i >= 0 ; i--) {
            newArray[--counts[array[i] - minLen]] = array[i];
        }
//        将有序数组赋值到array
        for (int i = 0; i < array.length; i++) {
            array[i] = newArray[i];
        }
    }

(3)对象也可以用:

 /**
     *     对 对象的 整数 属性 进行排序
      */
    public static void main(String[] args) {
        Person[] array = new Person[] {
                new Person(20, "A"),
                new Person(-13, "B"),
                new Person(17, "C"),
                new Person(12, "D"),
                new Person(-13, "E"),
                new Person(20, "F")
        };
//        数组里的最大值,最小值 确定长度
        int maxLen  = array[0].age;
        int minLen  = array[0].age;
        for (int i = 1; i < array.length; i++) {  // 不可以if-else min,max可能相同
            if (array[i].age > maxLen){
                maxLen = array[i].age;
            }
            if (array[i].age < minLen){
                minLen = array[i].age;
            }
        }
//        // 开辟内存空间,存储次数
        int[] counts = new int[maxLen - minLen +1];
        for (int i = 0; i < array.length; i++) {
            counts[array[i].age - minLen]++; // 数字 与 下标 相对应
        }
//        累加次数
        for (int i = 1; i < counts.length; i++) {
            counts[i] += counts[i-1];
        }
//         从后往前遍历元素,将它放到有序数组中的合适位置
        Person[] newArray = new Person[array.length];
        for (int i = array.length-1; i >= 0 ; i--) {
            newArray[--counts[array[i].age - minLen]] = array[i];
        }
//        将有序数组赋值到array
        for (int i = 0; i < array.length; i++) {
            array[i] = newArray[i];
        }
//        打印
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
    }
 /**
     *  自定义对象
     */
    private static class Person {
        int age;
        String name;
        Person(int age, String name) {
            this.age = age;
            this.name = name;
        }
        @Override
        public String toString() {
            return "Person [age=" + age
                    + ", name=" + name + "]";
        }
    }

9. 基数排序

/**
 *  基数排序
 *  基数排序非常适合用于整数排序(尤其是非负整数),这里只介绍对非负整数进行基数排序。
 *  https://blog.csdn.net/weixin_43734095/article/details/105170908
 *  个位 ,十位, 百位。。。。
 */
public class RadixSort  extends Sort<Integer> {

    @Override
    protected void sort() {
        int max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (max < array[i]){
                max = array[i];
            }
        }
        // 个位数: array[i] / 1 % 10 = 3
        // 十位数:array[i] / 10 % 10 = 9
        // 百位数:array[i] / 100 % 10 = 5
        // 千位数:array[i] / 1000 % 10 = ...
        // 传值 : 1,10,100,1000.....
        for (int divider = 1; divider <= max; divider *= 10) {
            test2(divider);
        }
    }
//    计数排序
    private void test2(int divider){
//      开辟内存空间,存储次数 ,只会是 0-9
        int[] counts = new int[10];
        for (int i = 0; i < array.length; i++) {
            counts[array[i] / divider % 10]++; // 数字 与 下标 相对应
        }
//        累加次数
        for (int i = 1; i < counts.length; i++) {
            counts[i] += counts[i-1];
        }
//         从后往前遍历元素,将它放到有序数组中的合适位置
        int[] newArray = new int[array.length];
        for (int i = array.length-1; i >= 0 ; i--) {
            newArray[--counts[array[i] / divider % 10]] = array[i];
        }
//        将有序数组赋值到array
        for (int i = 0; i < array.length; i++) {
            array[i] = newArray[i];
        }
        Integers.println(array);
    }
}

10. 桶排序

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供关于数据结构排序笔记。 内排序是指在排序过程中,所有待排序的数据元素全部存放在内存中进行排序。常见的内排序算法有冒泡排序、插入排序、选择排序快速排序、归并排序等。 1. 冒泡排序 冒泡排序是一种交换排序。它的基本思想是:通过交换相邻两个元素的位置,把大的元素往后面移动,小的元素往前面移动,从而实现排序的目的。 代码实现: ```c void bubble_sort(int arr[], int len) { for (int i = 0; i < len - 1; i++) { for (int j = 0; j < len - i - 1; j++) { if (arr[j] > arr[j+1]) { swap(arr[j], arr[j+1]); } } } } ``` 2. 插入排序 插入排序是一种插入排序。它的基本思想是:将待排序的元素插入到已经排好序的序列中,从而得到一个新的、更大的有序序列。 代码实现: ```c void insertion_sort(int arr[], int len) { int i, j, tmp; for (i = 1; i < len; i++) { tmp = arr[i]; for (j = i; j > 0 && arr[j-1] > tmp; j--) { arr[j] = arr[j-1]; } arr[j] = tmp; } } ``` 3. 选择排序 选择排序是一种选择排序。它的基本思想是:每次从待排序的序列中选择一个最小的元素,放到已经排好序的序列的末尾,直到所有元素都排好序。 代码实现: ```c void selection_sort(int arr[], int len) { int i, j, min_idx; for (i = 0; i < len - 1; i++) { min_idx = i; for (j = i + 1; j < len; j++) { if (arr[j] < arr[min_idx]) { min_idx = j; } } swap(arr[i], arr[min_idx]); } } ``` 4. 快速排序 快速排序是一种分治排序。它的基本思想是:通过一趟排序将待排序序列分割成独立的两部分,其中一部分的所有元素都比另一部分的元素小,然后再对这两部分分别进行排序,直到整个序列排好序为止。 代码实现: ```c void quick_sort(int arr[], int l, int r) { if (l < r) { int i = l, j = r, x = arr[l]; while (i < j) { while (i < j && arr[j] >= x) j--; if (i < j) arr[i++] = arr[j]; while (i < j && arr[i] < x) i++; if (i < j) arr[j--] = arr[i]; } arr[i] = x; quick_sort(arr, l, i-1); quick_sort(arr, i+1, r); } } ``` 5. 归并排序 归并排序是一种分治排序。它的基本思想是:将待排序序列分成若干个子序列,每个子序列都是有序的,然后再将子序列合并成一个有序序列。 代码实现: ```c void merge(int arr[], int l, int m, int r) { int i, j, k; int n1 = m - l + 1; int n2 = r - m; int L[n1], R[n2]; for (i = 0; i < n1; i++) L[i] = arr[l + i]; for (j = 0; j < n2; j++) R[j] = arr[m + 1 + j]; i = 0; j = 0; k = l; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k++] = L[i++]; } else { arr[k++] = R[j++]; } } while (i < n1) { arr[k++] = L[i++]; } while (j < n2) { arr[k++] = R[j++]; } } void merge_sort(int arr[], int l, int r) { if (l < r) { int m = (l + r) / 2; merge_sort(arr, l, m); merge_sort(arr, m+1, r); merge(arr, l, m, r); } } ``` 以上是内排序的几种常见算法。需要注意的是,不同的算法在不同的数据情况下,其效率也会有所不同。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值