【恋上数据结构】插入排序及二分搜索优化

简介: 【恋上数据结构】插入排序及二分搜索优化

我的《恋上数据结构》源码(第1季 + 第2季):https://github.com/szluyu99/Data_Structure_Note

经典的十大排序算法!
在这里插入图片描述

前言

请==务必==看一下这个:排序算法前置知识+代码环境准备

当上面的内容都准备好以后,那就开始插入排序吧!

插入排序

插入排序非常类似于扑克牌的排序,将后面的牌一张张插入到前面,使得前面有序的牌逐渐变多,直到完全有序。
在这里插入图片描述

在这里插入图片描述
执行流程

  • ① 在执行过程中,插入排序会将序列分为 2 部分

头部是已经排好序的,尾部待排序

  • ② 从头开始扫描每一个元素

每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数据依然保持有序

插入排序实现

/**
 * 插入排序
 */
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--;
            }
        }
    }

}

生成 20000 个取值在[1, 10000] 的随机数进行排序:
在这里插入图片描述

插入排序-逆序对

什么是逆序对?

  • 数组 <2, 3, 8, 6, 1> 的逆序对为:

< 2,1 > < 3,1> <8,1> <8,6> <6,1>,共5个逆序对

插入排序的时间复杂度与逆序对的数量成正比关系

  • 逆序对的数量越多,插入排序的时间复杂度越高

很明显,下图从上至下,插入排序的效率是逐渐提高的
在这里插入图片描述

插入排序 – 优化

思路是将【交换】转为【挪动】

执行流程:

  • ① 先将待插入的元素备份
  • ② 头部有序数据中比待插入元素大的,都朝尾部方向挪动1个位置
  • ③ 将待插入元素放到最终的合适位置

在这里插入图片描述

/**
 * 插入排序-优化
 * 交换  -> 挪动
 */
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 v = array[begin]; // 将待插入元素备份
            while(cur > 0 && cmp(v, array[cur-1]) < 0){
                // 头部有序数据中比待插入元素大的,都朝尾部方向挪动1个位置
                array[cur] = array[cur - 1]; 
                cur--;
            }
            array[cur] = v; // 将待插入元素放到最终的合适位置
        }
    }

}    

生成 20000 个取值在[1, 10000] 的随机数进行排序:可以发现,交换次数变为了0
在这里插入图片描述

复杂度与稳定性

  • 最坏、平均时间复杂度:O(n^2^)
  • 最好时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 属于稳定排序
  • 当逆序对的数量极少时,插入排序的效率特别高
    甚至速度比 O(nlog^n^) 级别的快速排序还要快

数据量不是特别大的时候,插入排序的效率也是非常好的。

二分搜索

如何确定一个元素在数组中的位置?(假设数组里面全都是整数)

  • 如果是无序数组,从第 0 个位置开始遍历搜索,平均时间复杂度:O(n)
    在这里插入图片描述
  • 如果是有序数组,可以使用二分搜索,最坏时间复杂度:O(log^n^)
    在这里插入图片描述

二分搜索-思路

  • 假设在 [begin, end) 范围内搜索某个元素 v,mid == (begin + end) / 2
  • 如果 v < m,去 [begin, mid) 范围内二分搜索
  • 如果 v > m,去 [mid + 1, end) 范围内二分搜索
  • 如果 v == m,直接返回 mid

在这里插入图片描述
在这里插入图片描述

二分搜索-实现

/**
 * 二分搜索
 */
public static int search(int[] array, int v){
    if(array == null || array.length < 1) return -1;
    int begin = 0;
    int end = array.length;
    while(begin < end){
        int mid = (begin + end) >> 1;
        if(v < array[mid]){
            end = mid;
        }else if(v > array[mid]){
            begin = mid + 1;
        }else{
            return mid;
        }
    }
    return -1;
}

如果存在多个重复的值,返回是哪一个?

  • 不确定

插入排序-二分搜索优化

在元素 v 的插入过程中,可以先二分搜索出合适的插入位置再将元素 v 插入

假设有如下序列:
在这里插入图片描述
要求二分搜索返回的插入位置:第1个大于 v 的元素位置

  • 如果 v 是 5,返回 2
  • 如果 v 是 1,返回 0
  • 如果 v 是 15,返回 7
  • 如果 v 是 8,返回 5

插入排序-二分搜索优化-思路

  • 假设在 [begin, end) 范围内搜索某个元素 v,mid == (begin + end) / 2
  • 如果 v < m,去 [begin, mid) 范围内二分搜索
  • 如果 v ≥ m,去 [mid + 1, end) 范围内二分搜索

在这里插入图片描述

插入排序-二分搜索优化-实例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

插入排序-二分搜索优化-实现

/**
 * 插入排序-优化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)
     */
    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{
                begin = mid + 1;
            }
        }
        return begin;
    }
    
}

生成 20000 个取值在[1, 10000] 的随机数进行排序:
在这里插入图片描述

复杂度与稳定性

使用了二分搜索后,只是减少了比较次数,但插入排序的平均时间复杂度依然是 O(n^2^)

  • 最坏、平均时间复杂度:O(n^2^)
  • 最好时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 属于稳定排序
相关文章
|
3月前
|
存储 缓存 算法
Python中常用的数据结构与算法优化技巧指南
Python是一种强大而灵活的编程语言,它提供了丰富的数据结构和算法库,但是在处理大规模数据或者需要高效运行的情况下,需要考虑一些优化技巧。本文将介绍一些Python中常用的数据结构与算法优化技巧,并附带代码实例,帮助你更好地理解和运用。
|
4月前
|
存储 监控 NoSQL
Redis处理大量数据主要依赖于其内存存储结构、高效的数据结构和算法,以及一系列的优化策略
【5月更文挑战第15天】Redis处理大量数据依赖内存存储、高效数据结构和优化策略。选择合适的数据结构、利用批量操作减少网络开销、控制批量大小、使用Redis Cluster进行分布式存储、优化内存使用及监控调优是关键。通过这些方法,Redis能有效处理大量数据并保持高性能。
76 1
|
28天前
|
JSON NoSQL MongoDB
MongoDB Schema设计实战指南:优化数据结构,提升查询性能与数据一致性
【8月更文挑战第24天】MongoDB是一款领先的NoSQL数据库,其灵活的文档模型突破了传统关系型数据库的限制。它允许自定义数据结构,适应多样化的数据需求。设计MongoDB的Schema时需考虑数据访问模式、一致性需求及性能因素。设计原则强调简洁性、查询优化与合理使用索引。例如,在构建博客系统时,可以通过精心设计文章和用户的集合结构来提高查询效率并确保数据一致性。正确设计能够充分发挥MongoDB的优势,实现高效的数据管理。
37 3
|
2月前
|
搜索推荐 测试技术
【数据结构常见七大排序(一)】—插入排序篇【直接插入排序】And【希尔排序】
【数据结构常见七大排序(一)】—插入排序篇【直接插入排序】And【希尔排序】
|
2月前
|
存储 算法 调度
惊呆了!Python高级数据结构堆与优先队列,竟然能这样优化你的程序性能!
【7月更文挑战第10天】Python的heapq模块实现了堆和优先队列,提供heappush和heappop等函数,支持O(log n)时间复杂度的操作。优先队列常用于任务调度和图算法,优化性能。例如,Dijkstra算法利用最小堆加速路径查找。堆通过列表存储,内存效率高。示例展示了添加、弹出和自定义优先级元素。使用堆优化程序,提升效率。
45 2
|
2月前
|
缓存 算法 安全
Java中的数据结构与算法优化策略
Java中的数据结构与算法优化策略
|
3月前
|
存储 算法 搜索推荐
Java数据结构与算法优化
Java数据结构与算法优化
|
3月前
|
算法 C++
算法与数据结构高手养成:朴素的贪心法(上)最优化策略
算法与数据结构高手养成:朴素的贪心法(上)最优化策略
25 1
算法与数据结构高手养成:朴素的贪心法(上)最优化策略
|
3月前
|
机器学习/深度学习 算法 搜索推荐
数据结构算法--2 冒泡排序,选择排序,插入排序
**基础排序算法包括冒泡排序、选择排序和插入排序。冒泡排序通过相邻元素比较交换,逐步将最大值“冒”到末尾,平均时间复杂度为O(n^2)。选择排序每次找到剩余部分的最小值与未排序部分的第一个元素交换,同样具有O(n^2)的时间复杂度。插入排序则类似玩牌,将新元素插入到已排序部分的正确位置,也是O(n^2)复杂度。这些算法适用于小规模或部分有序的数据。**
|
3月前
|
C++
【洛谷 P1044】[NOIP2003 普及组] 栈 题解(递归+记忆化搜索)
**NOIP2003普及组栈问题**:给定操作数序列1到n,仅允许push(进栈)和pop(出栈)操作。目标是计算所有可能的输出序列总数。输入包含一个整数n(1≤n≤18)。示例输入3,输出5。当队列空时返回1,栈空则只能入栈,栈非空时可入栈或出栈。AC C++代码利用记忆化搜索求解。
37 1