• 常见排序算法


    索引

    1. 插入排序 
        1.1 直接插入 
        1.2 折半插入 
        1.3 希尔排序 
    2. 交换排序 
        2.1 冒泡排序 
        2.2 快速排序 
    3. 选择排序 
        3.1 直接选择 
        3.2 堆排序 
    4. 归并排序  
        4.1 迭代归并  
    总结

    1. 插入排序

        思想:每步将一个待排序的对象, 按其排序码大小, 插入到前面已经排好序的一组对象的适当位置上, 直到对象全部插入为止。

    1.1 直接插入

        1.1.1 方法: 
            当插入第i (i >= 1) 个对象时, 前面的V[0], V[1], …, V[i-1]已经排好序。这时, 用V[i]的排序码依次与V[i-1], V[i-2], …的排序码顺序进行比较, 找到插入位置即将V[i]插入, 原来位置上的对象向后顺移。 
          具体过程: 
            1. 把n个待排序的元素看成为一个“有序表”和一个“无序表”; 
            2. 开始时“有序表”中只包含1个元素,“无序表”中包含有n-1个元素; 
            3. 排序过程中每次从“无序表”中取出第一个元素,依次与“有序表”元素的关键字进行比较,将该元素插入到“有序表”中的适当位置,有序表个数增加1,直到“有序表”包括所有元素。

        1.1.2 实例图:

    插入排序——直接插入    

    1.1.3 代码:

    /**
     * 直接插入排序:将数组从小到大排序
     */
    #include <iostream>
    using namespace std;
    
    typedef int Index;//下标的别名
    typedef int Type;//待排序的数组的元素类型
    
    /**
     * 直接插入排序
     */
    void direct_insert_sort(Type *array, int length) {
        Index i;
        Index j;//插入的位置
        Type temp;
    
        for(i=1; i<length; i++) {//i=1,即从第二个元素开始(第一个元素下标为0)
            temp = array[i];
            j=i;
            while(j>0 && array[j-1]>temp) {
                array[j] = array[j-1];
                j--;
            }
            //如果i!=j,说明要插入到前面的“已续表”中
            if(i!=j){
                array[j] = temp;
            }
        }
    }
    
    int main(int argc, char **argv) {
        Type array[19] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};//待排序的数组
        shell_sort(array, 19);
    
        //排序后,输出数组
        for(int i=0; i<19; i++) {
            cout<<array[i]<<"  ";
        }
        cout<<endl;
    
        return 0;
    }

         1.1.4 分析: 
            时间复杂度:O(n^2); 稳定的。 
            在下面两种情况,直接插入排序的效率较高:①序列中元素很少;②序列中的元素已经基本有序。

    1.2 折半插入

        1.2.1 方法: 
            在“直接插入排序”中,将在“有序表”中查找符合要求的项,利用二分查找完成。

        1.2.2 实例图: 
            和“直接插入排序”排序过程相同。(两者只是“查找插入位置的方法”不同)

        1.2.3 代码: 

    /**
     * 折半插入排序
     */
    void binary_insert_sort(Type *array, int length) {
        Index i;
        Index k;
        Index left, right;//二分查找时记录左右两侧的下标
        Type temp;
        
        for(i=1; i<length; i++) {
            left = 0;
            right = i-1;//查找时,不包括第i个,因为是要将第i个插入到合适的位置
            temp = array[i];
            while(left<=right) {
                Index middle = (left+right)/2;
                if( array[middle]<=temp ){ //注意:当middle==temp时,要是left加1。否则,算法将“不稳定”
                    left = middle+1;
                }else{
                    right = middle-1;
                }
            }
            for(k=i; k>left; k--){
                array[k] = array[k-1];
            }
            array[left] = temp;
        }
    }
    
    int main(int argc, char **argv) {
        Type array[19] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};//待排序的数组
        shell_sort(array, 19);
    
        //排序后,输出数组
        for(int i=0; i<19; i++) {
            cout<<array[i]<<"  ";
        }
        cout<<endl;
    
        return 0;
    }

        1.2.4 分析: 
            时间复杂度:O(N*logN); 稳定的(注意若处于后面“无序表”中的项,若等于前面“有序表”中的项,要将该项插入到“有序表”中对应项的后面)。 
            相对于“直接插入排序”:比较次数比“直接插入排序”的最差情况要好得多,但是比“直接插入排序”的最好情况要差,尤其是当数组已经排好序或者接近有序的时候。也就是说,“折半插入排序”不是在所有情况都优于“直接插入排序”。

    1.3 希尔排序(缩小增量排序)

        1.3.1 方法: 
            因为在“直接插入排序”过程中,若元素已经基本有序,那么“直接插入排序”的效率较高。引出了“希尔排序”的基本思想: 
            1. 设待排序的序列有 n 个对象,首先取一个整数 gap < n 作为间隔, 将下标相差为gap的倍数对象放在一组。 
            2. 在组内作 直接插入排序。 
            3. 然后逐渐缩小间隔 gap, 例如取 gap = gap/2,重复上述的组划分和排序工作。直到最后取 gap == 1, 将所有对象放在同一个组中进行排序为止。

        1.3.2 实例图:

    插入排序——希尔排序

        1.3.3 代码:

    /**
     * 希尔排序:将数组从小到大排序
     */
    #include <iostream>
    using namespace std;
    
    typedef int Index;//下标的别名
    typedef int Type;//待排序的数组的元素类型
    
    /**
     * 希尔排序
     */
    void shell_sort(Type *array, int length) {
        Index i;
        Index j;//插入的位置
        Type temp;
        int gap = length/2;//子序列间隔,这里取长度的一半
    
        while(gap!=0) {
            for(i=gap; i<length; i+=gap) { //1. i从gap开始取值;2. i每次递增gap
                temp = array[i];
                j=i;
                while(j>=gap && array[j-gap]>temp) {
                    array[j] = array[j-gap];
                    j -= gap;
                }
                //如果i!=j,说明要插入到前面的“已续表”中
                if(i!=j) {
                    array[j] = temp;
                }
            }
            gap /= 2;
        }
    }
    
    int main(int argc, char **argv) {
        Type array[19] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};//待排序的数组
        shell_sort(array, 19);
    
        //排序后,输出数组
        for(int i=0; i<19; i++) {
            cout<<array[i]<<"  ";
        }
        cout<<endl;
    
        return 0;
    }

        1.3.4 分析: 
             时间复杂度:n^1.25 ~ 1.6*n^1.25之间(统计资料),是不稳定的;gap的取值会影响希尔排序的效率。

    2. 交换排序

        思想:两两比较待排序对象的排序码,如果发生逆序,则进行交换。直到所有对象都排好序为止。

    2.1 冒泡排序

        2.1.1 方法: 
            1. 对待排序序列从前向后(从下标较大的元素开始)依次比较相邻元素的关键字,若发现逆序则交换; 
            2. 使较小的元素逐渐前移(或者较大的元素逐渐后移);(假定按照“从小到大”排序)  
          改进措施:  
              如果一趟比较下来没有进行过交换,就说明序列已经有序;可以通过设置一个标志exchange记录一趟遍历中是否进行了交换。

        2.1.2 实例图: 

    交换排序——冒泡排序

        2.1.3 代码:

    /**
     * 冒泡排序:将数组从小到大排序
     */
    #include <iostream>
    using namespace std;
    
    typedef int Index;//下标的别名
    typedef int Type;//待排序的数组的元素类型
    
    /*
     * 方法一:每次将最小的元素推至前端
     */
    void bubble_sort1(Type *array, int length) {
        Index i, j;
        bool exchange;//标志一次遍历中,是否进行了交换
    
        for(i=0; i<length-1; i++) {
            exchange = false;//每次循环开始时,值为false
            for(j=length-1; j>i; j--) {
                if(array[j]<array[j-1]) { //两两比较,当两者相等时,不交换,这样才能使该排序稳定,即原来在前面的排序后也在前面
                    exchange = true;
    
                    Type temp = array[j-1];
                    array[j-1] = array[j];
                    array[j] = temp;
                }
            }
            //如果本次循环没有交换,说明数组已经排序完毕
            if(!exchange) {
                break;
            }
        }
    }
    /*
     * 方法二:每次将最大的元素推至末尾
     */
    void bubble_sort2(Type *array, int length) {
        Index i, j;
        bool exchange;//标志一次遍历中,是否进行了交换
    
        for(i=0; i<length-1; i++) {
            exchange = false;//每次循环开始时,值为false
            for(j=0; j<length-i-1; j++) {
                if(array[j]>array[j+1]) { //两两比较,和“将较小元素推至前端”相同,当两者相等时,不交换,这样才能使该排序稳定,即原来在后面的排序后也在后面
                    exchange = true;
    
                    Type temp = array[j+1];
                    array[j+1] = array[j];
                    array[j] = temp;
                }
            }
            //如果本次循环没有交换,说明数组已经排序完毕
            if(!exchange) {
                break;
            }
        }
    }
    
    int main(int argc, char **argv) {
        Type array[19] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};//待排序的数组
        bubble_sort1(array, 19);
    
        //排序后,输出数组
        for(int i=0; i<19; i++) {
            cout<<array[i]<<"  ";
        }
        cout<<endl;
    
        return 0;
    }


        2.1.4 分析: 
            时间复杂度:O(n^2); 稳定的。

    2.2 快速排序

        2.2.1 方法:  
            “冒泡排序”中,元素的比较是从一端到另一端进行,每次移动一个位置;如果要是能“从两端到中间”进行,比“基准元素”大的一次就能交换到后面单元,比“基准元素”小的一次就能交换到前面单元,每次移动的距离较远,从而总的比较次数和移动次数都会减少。 
            步骤:(利用分治的算法思想) 
                1. 任取待排序序列中的某个元素作为基准(一般取第一个元素); 
                2. 通过一趟排序,将待排元素分为左右两个子序列: 
                      左子序列元素的关键字均小于或等于基准元素的关键字; 
                      右子序列的关键字则大于基准元素的关键字; 
                3. 分别对两个子序列继续进行排序,重复以上步骤,直至整个序列有序。(是一个递归的过程)

        2.2.2 实例图: 交换排序——快速排序

             当 i 为1时,执行过程为:

    交换排序——快速排序(当i为1的执行过程) 
        2.2.3 代码:   

    /**
     * 快速排序:将数组从小到大排序
     */
    #include <iostream>
    using namespace std;
    
    typedef int Index;//下标的别名
    typedef int Type;//待排序的数组的元素类型
    
    void quick_sort_recursion(Type *array, Index left, Index right);
    int partition(Type *array, Index left, Index right);
    
    /**
     * 快速排序,接口,内部调用“递归实现函数”quick_sort_recursion
     */
    void quick_sort(Type *array, int length) {
        quick_sort_recursion(array, 0, length-1);
    }
    /**
     * 快速排序,递归实现函数
     */
    void quick_sort_recursion(Type *array, Index left, Index right) {
        if(left<right) {
            int pivot_index = partition(array, left, right);//获得基准位置
            quick_sort_recursion(array, left, pivot_index-1);//递归调用,将子序列排序,注意不包括pivot_index位置的元素
            quick_sort_recursion(array, pivot_index+1, right);
        }
        /*
         调用这里的left和right都是有效的下标。如果类似C++ STL中,right是末尾的下一位,应写为下面的形式:
          原则:①调用partition函数的参数都是有效的;②调用自身quick_sort_recursion的参数right是末尾的下一个;  
         if(left<right) {
            int pivot_index = partition(array, left, right-1);//使用right-1
            quick_sort_recursion(array, left, pivot_index);//使用pivot_index
            quick_sort_recursion(array, pivot_index+1, right);
        }
         相应的,在quick_sort函数中的调用形式应修改为:quick_sort_recursion(array, 0, length);
         */
    }
    /**
     * 利用第一元素作为基准元素,将整个序列划分为两个部分。
     * 步骤:
     *   1. 比基准元素小的都移动到左侧,比基准元素大的都移动到右侧,变量pivot_position始终记录着比基准元素小的元素的最后一个元素。
     *   2. 最后将基准元素(第一个)与pivot_position位置上的元素交换,就可以达到目的。
     * 这时基准元素所在的位置也就是最终排序完成后,应该在的位置
     * 注意:这个实现中,参数中的left和right都是有效的,特别注意的是,这里的right是序列最后一个元素的下标,不是最后一个元素的下一个。
     */
    int partition(Type *array, Index left, Index right) {
        Type pivot = array[left];//基准元素值
        Index pivot_index = left; //记录已比较的元素中,比基准元素小的元素的最后一个位置;也是最后要返回的位置
    
        Index i;
        for(i=left+1; i<=right; i++) {//这里 i 能够等于right,就要求传给partition的参数中的right参数,一定要是有效的
            if(array[i]<pivot) {
                pivot_index++;
                //交换array[pivot_index]和array[i]
                if(i!=pivot_index) {
                    Type temp = array[i];
                    array[i] = array[pivot_index];
                    array[pivot_index] = temp;
                }
            }
        }
        //交换基准元素(第一个元素)和array[pivot_index]
        array[left] = array[pivot_index];
        array[pivot_index] = pivot;
        return pivot_index;
    }
    
    #define ARRAY_LENGTH 18
    
    int main(int argc, char **argv) {
        Type array[ARRAY_LENGTH] = { 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 0};//待排序的数组
        //调用接口函数
        quick_sort(array, ARRAY_LENGTH);
    
        //排序后,输出数组
        for(int i=0; i<ARRAY_LENGTH; i++) {
            cout<<array[i]<<"  ";
        }
        cout<<endl;
    
        //调用递归实现函数
        quick_sort_recursion(array, 0,ARRAY_LENGTH);
        //排序后,输出数组
        for(int i=0; i<ARRAY_LENGTH; i++) {
            cout<<array[i]<<"  ";
        }
        cout<<endl;
    
        return 0;
    }

        2.2.4 分析: 
            快速排序是一个“递归算法”,快速排序的趟数等于递归树的高度。         时间复杂度:最好 O(N*logN);最差 O(N^2); 
            空间复杂度:最好 O(logN); 最差 O(N); 
            是一种不稳定的算法。但是当序列元素数量较多时,快速排序的效率一般很好,所以经常采用;但是当元素较少时,其比一般方法可能要慢(因为要递归)。

    3. 选择排序

        思想:每一趟遍历,都从序列中找到最小的元素,将其放到队列的开始位置。或者找到序列的最大元素,放到队列的末尾。

    3.1 直接选择排序

        3.1.1 方法: 
            1. 在一组对象 V[i]~V[n-1] 中选择最小的对象; 
            2. 将它与这组对象中的第一个对象对调; 
            3. 在剩下的对象V[i+1]~V[n-1]中重复执行第①、②步, 直到剩余对象只有一个为止。 

        3.1.2 实例图:     

    选择排序——直接选择排序

    /**
     * 直接选择排序:将数组从小到大排序
     */
    #include <iostream>
    using namespace std;
    
    typedef int Index;//下标的别名
    typedef int Type;//待排序的数组的元素类型
    
    /**
     * 直接选择排序,每次选取最小的替换到开头
     */
    void direct_select_sort(Type *array, int length) {
        Index i, j;
        Index min_index;//每趟循环中,最小元素的下标
        Type temp;
        for(i=0; i<length-1; i++) {// i 的取值范围是从0到length-2,不包括最后一个元素(下标为 length-1),因为只剩一个元素时不需要判断
            min_index = i;
            for(j=i+1; j<length; j++) {//从i+1开始
                if(array[j]<array[min_index]) {
                    min_index = j;
                }
            }
            temp = array[i];
            array[i] = array[min_index];
            array[min_index] = temp;
        }
    }
    
    #define ARRAY_LENGTH 18
    
    int main(int argc, char **argv) {
        Type array[ARRAY_LENGTH] = { 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 0};//待排序的数组
        direct_select_sort(array, ARRAY_LENGTH);
    
        //排序后,输出数组
        for(int i=0; i<ARRAY_LENGTH; i++) {
            cout<<array[i]<<"  ";
        }
        cout<<endl;
    
        return 0;
    }

        3.1.4 分析: 
            时间复杂度:O(n^2)。不稳定。 
            总的比较次数固定为:n*(n-1)/2次,即O(n^2)级别。

    3.2 堆排序

        3.2.1 方法:  
            1. 根据初始输入数据,利用堆的“下滤调整算法”形成初始最大堆; 
            2. 将最大元素换到最后一个元素,对前面的元素构建最大堆。 
            3. 重复执行以上两步,直到所有元素排序完成。

        3.2.2 实例图: 

    选择排序——堆排序    

    3.2.3 代码:

    /**
     * 堆排序:将数组从小到大排序
     *  方法:依次建立最大堆,将堆顶元素(最大元素)与最后一个元素交换;
     */
    #include <iostream>
    using namespace std;
    
    typedef int Index;//下标的别名
    typedef int Type;//待排序的数组的元素类型
    
    void filter_down(Type *heap, Index pos, int length);
    void build_heap(Type *array, int length);
    void heap_sort(Type *array, int length);
    
    /**
     * 堆的下滤操作
     */
    void filter_down(Type *heap, Index pos, int length) {
        Index current=pos;//记录下滤过程中的当前节点
        Index child = 2*pos+1;//当前节点的子节点,当下标从0开始时,找到左孩子的下标
        
        Type temp = heap[pos];
        
        while(child<length) {
            //若左右孩子都存在,找到左右孩子中最大的那个
            if(child+1<length && heap[child]<heap[child+1]) {
                ++child;
            }
            //如果当前节点小于 其 孩子,将“子节点的值”赋给“当前节点”
            if(temp<heap[child]) {
                heap[current] = heap[child];
            } else {
                break;
            }
            current = child;
            child = 2*child + 1;
        }
        heap[current] = temp;
    }
    /**
     * 建立 堆
     */
    void build_heap(Type *array, int length) {
        Index i;
        for(i=(length-2)/2; i>=0; i--) {
            filter_down(array, i, length);
        }
    }
    
    /**
     * 堆排序
     */
    void heap_sort(Type *array, int length) {
        //建立堆
        build_heap(array, length);
        
        Index i;
        Type temp;
        for(i=length-1; i>0; i--) {// i 的范围从length-1 到 1,共length-2次循环(不包括i=0),若只剩一个元素,则说明整个序列已经有序了。
            //交换
            temp = array[0];
            array[0] = array[i];
            array[i] = temp;
            //下滤
            filter_down(array, 0, i);
        }
    }
    
    #define ARRAY_LENGTH 18
    
    int main(int argc, char **argv) {
        Type array[ARRAY_LENGTH] = { 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 0};//待排序的数组
    
        heap_sort(array, ARRAY_LENGTH);
    
        //排序后,输出数组
        for(int i=0; i<ARRAY_LENGTH; i++) {
            cout<<array[i]<<"  ";
        }
        cout<<endl;
    
        return 0;
    }

       3.2.4 分析: 
            时间复杂度:O(n*logn); 
            空间复杂度:O(1); 
            不稳定

    4. 归并排序  

        思想:将两个或两个以上的“有序表”合并成一个新的“有序表”。

    4.1迭代归并

       4.1.1 方法:  
            利用“两路归并”过程进行,步骤如下: 
                1. 把序列看成是 n 个长度为 1 的有序子序列 (归并项),先做两两归并,得到 n / 2 个长度为 2 的归并项 (如果 n 为奇数,则最后一个有序子序列的长度为1); 
                2. 然后看成长度为2, 4, 8……的有序子序列,两两归并,直到得到长度为n的有序序列; 
          注意: 
             
    如果n不是2len的整数倍, 则一趟归并到最后,可能遇到两种情形: 
                 1. 剩下一个长度为len的归并项和另一个长度不足len的归并项, 可用merge算法将它们归并成一个长度小于 2len 的归并项; 
                 2. 只剩下一个归并项,其长度小于或等于 len, 将它直接抄到目标序列中。

        4.1.2 实例图: 

    归并排序算法(迭代)     

    4.1.3 代码: 

    /**
     * 归并排序(迭代实现):将数组从小到大排序
     */
    #include <iostream>
    using namespace std;
    
    typedef int Index;//下标的别名
    typedef int Type;//待排序的数组的元素类型
    
    void merge(Type *src, Type *dest, Index left, Index middle, Index right);
    void merge_pass(Type *src, Type *dest, int section, int length);
    void merge_sort(Type *array, int length);
    
    /**
     * 合并两个列表
     * 将src中[left, middle]位置,和src中[middle+1, right]开始位置的元素合并到dest中
     *  注意:上面的区间是闭区间
     */
    void merge(Type *src, Type *dest, Index left, Index middle, Index right) {
        Index i = left; //在src的[left, middle]中前进
        Index j = middle+1; //在src的[middle+1, right]中前进
        Index k = left; //在dest中前进
        while(i<=middle && j<=right) {
            if(src[i]<=src[j]) { //注意:因为src[i]在前面,为了是算法稳定,当src[i]==src[j]时,应先添加stc[i]
                dest[k++] = src[i++];
            } else {
                dest[k++] = src[j++];
            }
        }
        //[left, middle]还没有走完
        while(i<=middle) {
            dest[k++] = src[i++];
        }
        //[middle+1, right]还没有走完
        while(j<=right) {
            dest[k++] = src[j++];
        }
    }
    /**
     * 归并排序的具体执行函数
     */
    void merge_pass(Type *src, Type *dest, int section, int length) {
        Index i=0;
        //1. 合并开始部分
        while(i+2*section <= length) {//因为序列下标从0开始,有等号
            merge(src, dest, i, i+section-1, i+2*section-1);
            i += 2*section;
        }
        //2.剩余部分有两块
        if(i+section<length) {
            merge(src, dest, i, i+section-1, length-1);
        } else {
            //3. 只剩一部分,直接添加到末尾
            while(i<length) {//这里没有等号
                dest[i] = src[i];
                i++;
            }
        }
    }
    
    /**
     * 归并排序
     */
    void merge_sort(Type *array, int length) {
        int section = 1;//小部分的长度
        Type *help_array = new Type[length];//辅助数组
        while(section<length) { //section不需要等于length
            //从array归并到help_array
            merge_pass(array, help_array, section, length);
            section *= 2;
            //从help_array归并到array
            merge_pass(help_array, array, section, length);
            section *= 2;
        }
        delete []help_array;
    }
    
    #define ARRAY_LENGTH 18
    
    int main(int argc, char **argv) {
        Type array[ARRAY_LENGTH] = { 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 0};//待排序的数组
        merge_sort(array, ARRAY_LENGTH);
    
        //排序后,输出数组
        for(int i=0; i<ARRAY_LENGTH; i++) {
            cout<<array[i]<<"  ";
        }
        cout<<endl;
    
        return 0;
    }

        4.1.4 分析: 
            时间复杂度:O(N*logN); 
            空间复杂度:一个和原来数组一样大小的数组,O(N); 
            该算法是稳定的

    5. 总结

    排 序 方 法

     比较次数

     移动次数

    稳定性 

    附加存储

    最好

    最差

    最好

    最差

    最好

    最差

    直接插入排序

    n

    n^2

     0

    n^2

     Ö

      1

    折半插入排序

    n logn

     0

    n^2

     Ö

      1

    起泡排序

    n

    n^2

     0

    n^2

     Ö

      1

    快速排序

    nlogn

    n^2

    < nlogn

    n^2

     ´

    logn

    n

    直接选择排序

    n^2

     0

    n

     ´

      1

    堆排序

    n logn

    n logn

     ´

      1

    归并排序

    n logn

    n logn

     Ö

      n

  • 相关阅读:
    vs2015驱动开发中使用RtlStringCchPrintfW()报错
    windbg双机调试配置
    修改Windows默认调试器
    kong配置service和route实现简单API代理
    konga的初步使用
    Kong Admin API — 核心对象
    Kong的API管理方式
    kong的管理UI选择-konga
    Kong 安装
    关于kong | API Gateway
  • 原文地址:https://www.cnblogs.com/wangfengju/p/6173006.html
Copyright © 2020-2023  润新知