高频考察的七大排序算法(个人总结)

    声明: 1. 本文为我的个人复习总结, 并那种从零基础开始普及知识 内容详细全面, 言辞官方的文章
              2. 由于是个人总结, 所以用最精简的话语来写文章
              3. 若有错误不当之处, 请指出

总览:

名称最坏时间复杂度平均时间复杂度空间复杂度
冒泡O(N^2)O(N^2)O(1)
选择O(N^2)O(N^2)O(1)
插入O(N^2)O(N^2)O(1)
希尔O(N^2)O(N*logN)O(1)
快排O(N^2)O(N*logN)O(logN)
归并O(N*logN)O(N*logN)O(n)
O(N+K)O(N+K)O(N+K)
O(N*logN)O(N*logN)O(1)

不稳定排序: 快选希堆

交换排序: 冒泡 & 快排

比较排序: 其他

bubbleSort03 & 插入排序 都很适合于有序度高的集合

选择排序的交换次数<冒泡排序

1. 冒泡:

public void bubbleSort01(int[] arr) {
    int len = arr.length;
    for (int i = 0; i < len; i++) { // 循环次数
        for (int j = 0; j < len - 1 - i; j++) { // i轮循环后,最后i位就确定已经是最大的了,无需再比较
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}


public void bubbleSort02(int[] arr) {
    int len = arr.length;
    for (int i = 0; i < len; i++) {
        boolean flag = true;
        for (int j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                flag = false;
            }
        }
        // 优化: 如果一轮比较下来,flag始终为被置为false,说明已经全局有序,可以提前提出
        if (flag) {
            return;
        }
    }
}


public void bubbleSort03(int[] arr) {
    int len = arr.length;
    //优化:  recentMaxNumIdx, 上一轮交换的最后一次idx,可以当作下一轮比较目的地的临界值
    int recentMaxNumIdx = len - 1;
    int last = recentMaxNumIdx; // 随便给个值就行,若经历循环则last必被赋值,否则flag为true就return就结束了
    for (int i = 0; i < len; i++) {
        boolean flag = true;
        for (int j = 0; j < recentMaxNumIdx; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                flag = false;
            }
            last = j;
        }
        recentMaxNumIdx = last;
        if (flag) {
            return;
        }
    }
}

2. 选择:

public void selectSort(int[] arr) {
    int len = arr.length;
    // 思路:分为两个区[已排序的区 + 未排序的区],
    //       每次从未排序的区里挑选minNum,和未排序分区的首个idx进行swap,这样导致了 已排序的区++,未排序的区--
    //       最开始的时候 已排序的区中没数据

    // i就是未排序分区的首个idx
    // 因为 j(即i+1) < len, 所以i < len - 1;
    // 或者这样理解: i是len-1时,只有最后一个数字没进行排序,其他都从最小数字开始按升序排好了,
    // 言外之意此数字就是最大的了,没必要再进行排序
    for (int i = 0; i < len - 1; i++) {
        int minNumIdx = i;

        for (int j = i + 1; j < len; j++) {
            if (arr[j] < arr[minNumIdx]) {
                minNumIdx = j;
            }
        }
        int temp = arr[i];
        arr[i] = arr[minNumIdx];
        arr[minNumIdx] = temp;
    }
}

3. 插入

public void insertSort(int[] arr) {
    int len = arr.length;
    // 思路: 分为两个区[已排序的区 + 未排序的区],
    // 每次从未排序的区里挑选首个数字,然后往前遍历,在已排序分区中寻求一个位置,然后导致了 已排序的区++,未排序的区--
    // 最开始的时候 已排序的区中只有arr[0]

    // i是没排序分区中的首个元素idx
    for (int i = 1; i < len; i++) {
        int willInsertIdx = i;
        int willInsertNum = arr[i]; // 待插入的元素值

        while (willInsertIdx >= 1) {
            if (arr[willInsertIdx - 1] > willInsertNum) {
                arr[willInsertIdx] = arr[willInsertIdx - 1];
                willInsertIdx--;
            } else {
                break;
            }
        }
        arr[willInsertIdx] = willInsertNum;
    }
}

4. 希尔

// 插入排序在逆序度高时,移动元素次数太多
// 插入排序相当于gap间隙为一的希尔排序
// 希尔排序先gap=1,再=2,再=1
// 这样在即使逆序度高时,也不必移动元素太多次
public void shellSort(int[] arr) {
    int len = arr.length;
    for (int gap = len / 2; gap >= 1; gap /= 2) {
        for (int i = gap; i < len; i++) {
            int willInsertIdx = i;
            int willInsertNum = arr[i];
            while (willInsertIdx >= gap) {
                if (arr[willInsertIdx - gap] > willInsertNum) {
                    arr[willInsertIdx] = arr[willInsertIdx - gap];
                    willInsertIdx -= gap;
                } else {
                    break;
                }
            }
            arr[willInsertIdx] = willInsertNum;
        }
    }
}

5. 快排

public void quickSort(int[] arr, int lp, int rp) {
    if (lp >= rp) {
        return;
    }
    int base = partition(arr, lp, rp);
    quickSort(arr, lp, base - 1);
    quickSort(arr, base + 1, rp);
}

// 1,5,9,2,7,3
// 1,5,9,2,7,3
// 1,2,9,5,7,3
// 1,2,3,5,7,9
// 基准元素应该放到哪个idx,并且把小于基准元素的放在idx左边,剩下右边的就是大于基准元素的了
private int partition(int[] arr, int lp, int rp) {
    // baseNum随便选的,这里统一选成最右边的元素值
    int baseNum = arr[rp];
    int baseIdx = lp;
    for (int i = lp; i < rp; i++) {
        if (arr[i] < baseNum) {
            swap(arr, i, baseIdx);
            baseIdx++;
        }
    }
    swap(arr, rp, baseIdx);
    return baseIdx;
}

private void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

6. 归并

数组时 递归时需要传递temp[], 使其都是同一个数组; 链表时, 因为可以传递引用, 便不用传参temp[]

数组:

// 分+合
public static void mergeSort(int[] arr, int lp, int rp, int[] temp) {
    if (lp < rp) {
        int mid = (lp + rp) / 2;
        // 向左递归分解
        mergeSort(arr, lp, mid, temp);
        // 向右递归分解
        mergeSort(arr, mid + 1, rp, temp);
        // 合并
        merge(arr, lp, mid, rp, temp);
    }
}

// 合并 两个升序数组
// arr[lp~mid]是一个升序数组,arr[mid+1~rp]是一个升序数组
public static void merge(int[] arr, int lp, int mid, int rp, int[] temp) {

    int lp1 = lp; // 左边有序序列的初始索引
    int rp1 = mid;
    int lp2 = mid + 1; // 右边有序序列的初始索引
    int rp2 = rp;

    int cnt = 0;

    while (lp1 <= rp1 && lp2 <= rp2) {
        if (arr[lp1] <= arr[lp2]) {
            temp[cnt++] = arr[lp1++];
        } else {
            temp[cnt++] = arr[lp2++];
        }
    }

    // 把有剩余数据的数组余留元素 填充到temp
    while (lp1 <= mid) {
        temp[cnt++] = arr[lp1++];
    }

    while (lp2 <= rp) {
        temp[cnt++] = arr[lp2++];
    }

    // 拷贝temp数组到arr
    System.arraycopy(temp, 0, arr, lp, rp - lp + 1);
}

链表:

public ListNode mergeSort(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }

    ListNode fast = head, slow = head;
    ListNode pre = null;
    // 循环至少会发生一次,因为上文 if(head == null || head.next == null)就return了
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        pre = slow;
        slow = slow.next;
    }
    // 找mid结点
    ListNode mid = slow;
    // 截断
    pre.next = null;
    // 递归拆分
    ListNode left = mergeSort(head);
    ListNode right = mergeSort(mid);
    ListNode dummy = new ListNode(0);
    ListNode temp = dummy;
    // 合并
    while (left != null && right != null) {
        if (left.val < right.val) {
            temp.next = left;
            left = left.next;
        } else {
            temp.next = right;
            right = right.next;
        }
        temp = temp.next;
    }
    // 合并有剩余的单个链表
    temp.next = left != null ? left : right;
    return dummy.next;
}

7. 堆

大顶堆是对数组进行升序排序

/*
     堆排序 = 构造大顶堆(需要遍历所有非叶子结点) + 维护大顶堆(只需调整根一个结点) + 选择排序
     规律: i从0开始,arr[i]的左孩子是2*i+1,右孩子是2*i+2
*/
// 堆排序
private void heapSort(int[] arr) {
    int len = arr.length;

    // 构造大顶堆
    buildMaxHeap(arr);

    // 选择排序+维护大顶堆
    // 末尾元素部分,就是已经有序的部分
    // 逐步将每个最大值的根节点与末尾元素交换,并且再调整二叉树,使其成为大顶堆
    for (int i = len - 1; i > 0; i--) {
        swap(arr, 0, i); // swap 根 和 当前未排序部分的最后一个结点
        heapAdjust(arr, 0, i); // 调整根结点使其能符合大顶堆,i后面的都已经排好序了
    }
}

// 构造大顶堆
private void buildMaxHeap(int[] arr) {
    int len = arr.length;
    // 遍历所有的非叶子结点,自底向下调整
    for (int i = len / 2; i >= 0; i--) {
        heapAdjust(arr, i, len);
    }
}

// 维护大顶堆
// 把某个结点 放到合适的位置
// 要调整的结点不仅与左孩子右孩子比较,还要在"下沉"后继续与孙子比较
private void heapAdjust(int[] arr, int targetIdx, int len) {
    int maxChildIdx = -1;
    int i;
    // "下沉"继续比较
    for (i = targetIdx; 2 * i + 1 < len; i = maxChildIdx) {
        maxChildIdx = 2 * i + 1;

        // 如果左子树小于右子树,则需要比较右子树和父节点; 即父亲与最大的孩子进行比较
        if (maxChildIdx + 1 < len && arr[maxChildIdx] < arr[maxChildIdx + 1]) {
            maxChildIdx++;
        }

        // 如果父节点小于孩子结点,则需要交换
        if (arr[i] < arr[maxChildIdx]) {
            swap(arr, i, maxChildIdx);
        } else {
            break; // 大顶堆结构未被破坏,不需要调整
        }
    }
}

private void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值