zoukankan      html  css  js  c++  java
  • 排序算法学习笔记(一)-- 冒泡排序,选择排序,插入排序,希尔排序

    写在前面:

     1.图片来源是从别的博客转来的,后期有空会自己使用java swing实现。

     2.代码实现由于个人习惯使用了C++和python,https://zhuanlan.zhihu.com/p/57088609博主实现了java版本。

     3.这里的代码实现排序顺序都是从左到右的升序。想要实现逆序可以直接颠倒数组。其实所谓的“小”只是相对的,“小”的规则可以通过重载运算符或者传入比较器,比较函数来实现自己想要的标准,来变成“大”

    1.冒泡排序(bubble sort)

    1.1 算法过程:

      比方对于一个从左到右排列的数组。比较相邻的元素。如果左边比第右边大,就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到尾部的最后一对。这样尾部的元素将会是最大的元素。从左到右(除尾端的)每个元素依次作为起点重复上述的步骤。 

           算法名字很形象,每一个元素就是慢慢浮动到自己应该在的位置。借《啊哈!算法》中的一个图来理解

    1.2 可视化展示:

    图源:https://juejin.im/post/5b4d8c47e51d4519105d4ec3

    1.3 代码:

    python版本:

    def bubble_sort(nums):
        length = len(nums)
        if(length==0):
            return nums
        for i in range(length):
            for j in range(i,length):
                if nums[i]>nums[j]:
                    nums[i],nums[j] = nums[j],nums[i]
        return nums

    C++版本:

    1 template<typename T> 
    2 void bubbleSort(T arr[], int length) {
    3     for (int i = 0; i < length - 1; i++){
    4         for (int j = 0; j < length- 1 - i; j++)
    5             if (arr[j] > arr[j + 1])
    6                 swap(arr[j], arr[j + 1]);
    7     }
    8 }  

    1.4 算法改进

      优化1:设立一个“flag”,记录每一次遍历过程中是否发生了交换。若没有发生交换说明数组已经是有序的了,可以提前退出循环。

      优化2:可以记录最后一次发生交换的位置,此位置之后的元素已经是有序的,不需要再进行遍历。

      实现优化1(C++):

     1 template<typename T>
     2 void bubbleSort( T arr[] , int length){
     3 
     4     bool swapped;
     5 
     6     do{
     7         swapped = false;
     8         for (int i = 0; i < length - 1; i++)
     9             for (int j = 0; j < length - 1 - i; j++)
    10                 if (arr[j] > arr[j + 1]){
    11                     swap(arr[j], arr[j + 1]);
    12                     swapped=true;
    13         }
    14 
    15     }while(swapped);
    16 }

      实现优化2(C++):

     1 template<typename T>
     2 void bubbleSort( T arr[] , int n){
     3 
     4     int new_n; 
     5     do{
     6         new_n = 0;
     7         for( int i = 1 ; i < n ; i ++ )
     8             if( arr[i-1] > arr[i] ){
     9                 swap( arr[i-1] , arr[i] );
    10                 new_n = i;
    11             }
    12         n = new_n;
    13     }while(new_n > 0);
    14 }

    1.5 相关性质

    • 时间复杂度: O(n2)  
    • 空间复杂度:O(1)
    • 稳定排序      
    • 原地排序

    2.选择排序(selection sort)

    2.1 算法过程:

      首先在未排序的序列(通常就是整个序列)中找到最小的元素,来放到序列的头部;然后再从剩余的未排序的序列中寻找最小元素,放到有序序列的最尾端。

      如此重复,直到整个序列都是有序的。

    2.2 可视化展示:

    图源:https://juejin.im/post/5b4ef0bbe51d4519575a18d0

    2.3 代码实现:  

     python版本:

     1 def selection_sort(nums):
     2     length = len(nums)
     3     if(length==0):
     4         return nums
     5     for i in range(length):
     6         argmin = i
     7         for j in range(i,length):
     8             if(nums[j]<nums[argmin]):
     9                 argmin = j
    10         nums[i],nums[argmin] = nums[argmin],nums[i]
    11     return nums

     C++版本:

     1 template<typename T>
     2 void selectionSort(T arr[], int n){
     3 
     4     for(int i = 0 ; i < n ; i ++){
     5 
     6         int minIndex = i;
     7         for( int j = i + 1 ; j < n ; j ++ )
     8             if( arr[j] < arr[minIndex] )
     9                 minIndex = j;
    10 
    11         swap( arr[i] , arr[minIndex] );
    12     }
    13 }

    2.4 相关性质:

    • 时间复杂度:O(n2)  (无论什么数据都是,稳)
    • 空间复杂度:O(1)
    • 稳定性取决于具体实现方法
    • 原地排序

    3.插入排序(insertion sort)

    3.1 算法过程:

      将第一个元素看作是一个有序序列,第二个元素到最后一个元素看作是无序的。对于无序序列的每一个元素,放到有序序列的合适位置,变成新的有序序列,如此反复知道整个序列变成有序序列。(如果遇到相等元素的放在该元素的后面)

            通俗理解:类比拿到扑克牌时,拿到第一张扑克牌时,该扑克牌就是一个有序序列。每次取到一张新的扑克牌将其放在合适的位置,如此往复。

    3.2 可视化展示:

       图源:https://juejin.im/post/5b4ef681f265da0f4b7a8d44

     3.3 代码实现:

     python版本:

     1 def insertion_sort(nums):
     2     length = len(nums)
     3     if(length==0):
     4         return nums
     5     for i in range(length):
     6         pre = i-1
     7         cur_num = nums[i]
     8         while pre>=0 and nums[pre]>cur_num:
     9             nums[pre+1] = nums[pre]
    10             pre -= 1
    11         nums[pre+1] = cur_num  # 选择最后再交换这个元素
    12     return nums

    C++版本:

     1 template<typename T>
     2 void insertionSort(T arr[], int n) {
     3     
     4     for (int i = 1; i < n; i++) {
     5         //寻找元素arr[i]合适的插入位置
     6         for (int j = i; j > 0 && arr[j] < arr[j - 1]; j--) {
     7             swap(arr[j], arr[j - 1]);
     8         }
     9     }
    10 }

    3.4 算法改进:

      改进:先不进行swap,而是拷贝一份先前进行比较,找到合适的位置后再做赋值操作

           实现(C++)(上面python代码已经实现了):

    template<typename T>
    void insertionSortImprove(T arr[], int n) {
    
        for (int i = 1; i < n; i++) {
            //寻找元素arr[i]合适的插入位置
            T e = arr[i];
            int j;
            for ( j = i; j > 0 && arr[j-1] > e; j--) {
                arr[j] = arr[j - 1];
            }
            arr[j] = e;
        }
    }

    3.5 相关性质: 

    • 时间复杂度:O(n2)   最好的情况下是O(n),平均比较和移动次数约为 (n^2)/4

      (插入排序法的提前退出循环的特性,使得在面对一个有序性强的数组时,性能非常优异)

      (插入排序法理论上来说可以提前跳出循环,速度应该快于选择排序法,但由于多次交换的赋值操作使得时间变长。这说明算法实际的运行速度还受限于实现方式,例如递归时开辟栈空间的开销,交换操作的开销等等)

    • 空间复杂度:O(1)
    • 稳定排序
    • 原地排序

    4.插入排序的衍生算法--希尔排序

    1959年Shell发明,第一个突破O(n2)的排序算法

    插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位,因此如果一次可以移动多位,也就是有一个间隔,就有了希尔排序的思路。

    4.1 算法过程

      基本思想:先将整个待排序的序列分割成为若干子序列分别进行插入排序,等到整个序列中基本有序时,再对整个序列进行插入排序。

      具体过程:

    1. 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;

    2. 按增量序列个数 k,对序列进行 k 趟排序;

    3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

            来张图帮助理解:

      

      图源:https://www.cnblogs.com/chengxiao/p/6104371.html

    4.2 可视化展示:

     

    图源:https://juejin.im/post/5bf9f2285188256b0f5832a0

    4.3 代码实现:

      由于原始的希尔增量序列不互质,可能会出现增量序列不起作用的情况,故在此不使用希尔增量序列。

     python版本:

     1 import math #使用math.floor()来对增量进行取整
     2 def shell_sort(nums):
     3     length = len(nums)
     4     if(length==0):
     5         return nums
     6     offset = 1
     7     while(offset<length/3):
     8         offset = offset*3 + 1
     9     while offset>0:
    10         for i in range(offset,length):
    11             tmp = nums[i]
    12             j = i-offset
    13             while j>=0 and nums[j] > tmp:
    14                 nums[j+offset] = nums[j]
    15                 j -= offset
    16             nums[j+offset] = tmp
    17         offset = math.floor(offset/3)
    18     return nums

      C++版本:

     1 template<typename T>
     2 void shellSort(T arr[], int n){
     3 
     4     // 计算增量序列: 1, 4, 13, 40, 121, 364, 1093...
     5     int offset = 1;
     6     while( offset < n/3 )
     7         offset = 3 * offset + 1;
     8 
     9     while( offset >= 1 ){
    10 
    11         for( int i = offset ; i < n ; i ++ ){
    12 
    13             // 对 arr[i], arr[i-offset ], arr[i-2*offset ], arr[i-3*offset ]... 使用插入排序
    14             T e = arr[i];
    15             int j;
    16             for( j = i ; j >= offset && e < arr[j-offset ] ; j -= offset )
    17                 arr[j] = arr[j-offset ];
    18             arr[j] = e;
    19         }
    20 
    21         offset /= 3;
    22     }
    23 }

    4.4 希尔排序的增量序列:

    4.5 相关性质:

    • 时间复杂度:希尔排序的复杂度与增量序列的选择有关。具体分析可以参考:

      排序:希尔排序(算法)

    • 空间复杂度: O(1) 
    • 不稳定排序
    • 原地排序

    END

  • 相关阅读:
    高级查询相关练习
    高级查询
    CRUD简单查询
    eclipse 实际运用
    控制台输入与Math Random的基本使用
    调用方法
    jQuery 基础语法
    Java第二课 分支与循环
    Java第一课
    网页项目制作收获2
  • 原文地址:https://www.cnblogs.com/lucas-/p/12411832.html
Copyright © 2011-2022 走看看