图解排序算法之3种简单排序(选择,冒泡,直接插入)

排序是数据处理中十分常见且核心的操作,虽说实际项目开发中很小几率会需要我们手动实现,毕竟每种语言的类库中都有n多种关于排序算法的实现。但是了解这些精妙的思想对我们还是大有裨益的。本文简单温习下最基础的三类算法:选择,冒泡,插入。

先定义个交换数组元素的函数,供排序时调用

简单选择排序

  简单选择排序是最简单直观的一种算法,基本思想为将序列分为有序序列和待排的无序序列,每一趟从后面n-i个待排数据元素中选择最小的一个元素作为有序序列的第i个元素,直到所有元素排完为止。

  在算法实现时,每一趟确定最小元素的时候会通过不断地比较交换来使得首位置为当前最小,交换是个比较耗时的操作。其实我们很容易发现,在还未完全确定当前最小元素之前,这些交换都是无意义的。我们可以通过设置一个变量k,每一次比较仅存储较小元素的数组下标,当轮循环结束之后,那这个变量存储的就是当前最小元素的下标,此时再执行交换操作即可。代码实现很简单,一起来看下。

例如对无序表{56,12,80,91,20}采用简单选择排序算法进行排序,具体过程为:

  • 第一次遍历时,从下标为 1 的位置即 56 开始,找出关键字值最小的记录 12,同下标为 0 的关键字 56 交换位置:



     

  • 第二次遍历时,从下标为 2 的位置即 56 开始,找出最小值 20,同下标为 2 的关键字 56 互换位置:



     

  • 第三次遍历时,从下标为 3 的位置即 80 开始,找出最小值 56,同下标为 3 的关键字 80 互换位置:



     

  • 第四次遍历时,从下标为 4 的位置即 91 开始,找出最小是 80,同下标为 4 的关键字 91 互换位置:



     

  • 到此简单选择排序算法完成,无序表变为有序表。

  代码实现

void println(int array[], int len)
{
    int i = 0;
    
    for(i=0; i<len; i++)
    {
        printf("%d ", array[i]);
    }
    
    printf("\n");
}

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

void SelectionSort(int array[], int len) // O(n*n)
{
    int i = 0;
    int j = 0;
    int k = -1;
    
    for(i=0; i<len; i++)
    {
        k = i; 

        //循环遍历求出最小值,对应下标赋值为k
        for(j=i; j<len; j++)
        {
            if( array[j] < array[k] )
            {
                k = j;
            }
        } 

        //交换a[i]与a[k]的值
        swap(array, i, k);
    }
}

int main()
{
    int array[] = {56,12,80,91,20};
    int len = sizeof(array) / sizeof(*array); 
    println(array, len); 
    SelectionSort(array, len);
    println(array, len);
    return 0;
}

  简单选择排序通过上面优化之后,无论数组原始排列如何,比较次数是不变的;对于交换操作,在最好情况下也就是数组完全有序的时候,无需任何交换移动,在最差情况下,也就是数组倒序的时候,交换次数为n-1次。综合下来,时间复杂度为O(n2)

直接插入排序

  直接插入排序基本思想是每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。最开始认为首元素为有序序列,当插入第i个数据元素时候,认为前面的r[0]、r[1],..., r[i-1]已经排好序,用r[i]与r[i-1],r[i-2],...分别比较,

找到插入位置将r[i]插入到指定位置。

  例如采用直接插入排序算法将无序表{3,1,7,5,2,4,9,6}进行升序排序的过程为:

  • 首先考虑记录 3 ,由于插入排序刚开始,有序表中没有任何记录,所以 3 可以直接添加到有序表中,则有序表和无序表可以如图 1 所示:



    图 1 直接插入排序(1)
     

  • 向有序表中插入记录 1 时,同有序表中记录 3 进行比较,1<3,所以插入到记录 3 的左侧,如图 2 所示:



    图 2 直接插入排序(2)
     

  • 向有序表插入记录 7 时,同有序表中记录 3 进行比较,3<7,所以插入到记录 3 的右侧,如图 3 所示:



    图 3 直接插入排序(3)
     

  • 向有序表中插入记录 5 时,同有序表中记录 7 进行比较,5<7,同时 5>3,所以插入到 3 和 7 中间,如图 4 所示:



    图 4 直接插入排序(4)
     

  • 向有序表插入记录 2 时,同有序表中记录 7进行比较,2<7,再同 5,3,1分别进行比较,最终确定 2 位于 1 和 3 中间,如图 5 所示:



    图 5 直接插入排序(5)
     

  • 照此规律,依次将无序表中的记录 4,9 和 6插入到有序表中,如图 6 所示:



    图 6 依次插入记录4,9和6


直接插入排序的具体代码实现为: 

代码实现 

void InertionSort(int array[], int len) // O(n*n)
{
    int i = 0;
    int j = 0;
    int k = -1;
    int temp = -1;
    
    for(i=1; i<len; i++)
    {
        k = i;
        temp = array[k];
        
        for(j=i-1;  (j>=0) && (array[j]>temp);  j--)
        {
            array[j+1] = array[j]; //后移空出插入位置
            k = j;
        }
        
        array[k] = temp; //将待插入记录数据元素插入到已排好序的有序序列中
    }
}

  简单插入排序在最好情况下,需要比较n-1次,无需交换元素,时间复杂度为O(n);在最坏情况下,时间复杂度依然为O(n2)。但是在数组元素随机排列的情况下,插入排序还是要优于上面两种排序的。

冒泡排序 

  冒泡排序的基本思想是,对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最大或者最小的元素“浮”到顶端,最终达到完全有序。下面的例子每次是将最小元素浮到顶端,在第i趟中从后往前,j=len-1, len-2, ..., i+1两两比较array[j] 与array[j-1] ,如果发生逆序,则交换array[j] 与array[j-1]。

  

  代码实现

    在冒泡排序的过程中,如果某一趟执行完毕,没有做任何一次交换操作,比如数组[5,4,1,2,3],执行了两次冒泡,也就是两次外循环之后,分别将5和4调整到最终位置[1,2,3,4,5]。此时,再执行第三次循环后,一次交换都没有做,这就说明剩下的序列已经是有序的,排序操作也就可以完成了,来看下代码 

void BubbleSort(int array[], int len) // O(n*n)
{
    int i = 0;
    int j = 0;
    int exchange = 1;
    
    for(i=0; (i<len) && exchange; i++)
    {
        exchange = 0;
        
        for(j=len-1; j>i; j--)
        {
            if( array[j] < array[j-1] )
            {
                swap(array, j, j-1);
                
                exchange = 1;
            }
        }
    }
}

  根据上面这种冒泡实现,若原数组本身就是有序的(这是最好情况),仅需n-1次比较就可完成;若是倒序,比较次数为 n-1+n-2+...+1=n(n-1)/2,交换次数和比较次数等值。所以,其时间复杂度依然为O(n2)。综合来看,冒泡排序性能还还是稍差于上面那种选择排序的。

总结

  本文列举了排序算法中最基本的三种算法(简单选择,冒泡,插入),这三种排序算法的时间复杂度均为O(n2) 

### 快速排序算法图解 快速排序是一种基于分治策略的高效排序方法[^2]。该算法通过递归方式将数组划分为较小子数组,分别对这些子数组进行排序,最终得到有序序列。 #### 基本流程描述 1. **选取基准值**:从待排序列中挑选一个元素作为基准(pivot)。通常可以选择第一个元素、最后一个元素或者中间位置的元素。 2. **分区操作**:重新排列列表中的其他元素,使得所有小于基准值的元素位于其左边,大于等于基准值的元素位于右边;此时基准处于已排好序的位置上。 3. **递归处理**:对于划分后的两个部分重复上述过程直到不能再分割为止。 4. **合并结果**:由于每次都是原地修改数组,则无需额外空间来存储临时数据结构,在完成最后一次迭代后即获得完整的升序/降序排列。 #### 图形化展示 假设有一个无序整型数组 `[9, 7, 5, 11, 12, 2, 14, 3, 10, 6]` 需要按照从小到大顺序排列: - 初始状态: |索引| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |--|--|--|--|----|----|--|-----|--|-----|--| |数值| 9 | 7 | 5 | 11 | 12 | 2 | 14 | 3 | 10 | 6 | - 设定 `low=0`, `high=length-1`; 选定最右侧元素 (`arr[high]=6`) 为本次循环的基准值; - 开始遍历比较并交换满足条件的数据项直至指针交错相遇停止,形成如下所示的新布局: |索引| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |--|--|--|--|----|----|--|-----|--|-----|--| |数值| 5 | 7 | 3 | 2 | 12 | 9 | 14 | 6 | 10 | 11 | - 继续针对左右两侧未排序区间执行相同逻辑... ```python def quicksort(arr): if len(arr) <= 1: return arr else: pivot = arr[-1] less_than_pivot = [x for x in arr[:-1] if x < pivot] greater_or_equal_to_pivot = [x for x in arr[:-1] if x >= pivot] return quicksort(less_than_pivot) + [pivot] + quicksort(greater_or_equal_to_pivot) unsorted_list = [9, 7, 5, 11, 12, 2, 14, 3, 10, 6] print(quicksort(unsorted_list)) ``` 在最优情况下,时间复杂度可以达到 \(O(n \log n)\),这是因为树的高度大约为 \(\log_2n\) 层,并且每一层都要访问几乎所有的节点一次[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值