zoukankan      html  css  js  c++  java
  • 【紫书】算法竞赛之排序算法笔记

    一:冒泡排序(O(n^2))

    每组每个位置的数与它后面的数比较,前面数大于后面的数就交换数值,交换n-1组。

    每次每组交换的时候,都会把最大的排到后面去,就类似与在水底泡泡慢慢的向上浮出。

    特性:

    稳定。

    动图演示:

    详细解析代码:

    #include<stdio.h>
    
    int main() {
        int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
        //为了方便大家理解,我将数值设为与动图一样的数值。
    
        int n = 15;
        //冒泡排序
        for(int i=0;i<n-1;i++)//代表的是要做多少组比对,为什么是n-1呢?
            //因为计数从0开始而且第一个数和自身没有比对的必要性。
            for(int j=0;j<n-1-i;j++)
            //代表组里面的序号,因为要与后面数做比较,而最后一个数没有与之比较的(可能会越界)所以n-1
            //为什么还要-i呢?
            //因为每排过一遍,这一组最大值就排到后面去了,也就没必要再去比较了。
                if (a[j] > a[j + 1]) {
                    int temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                }
    
        for (int i = 0;i < n;i++)
            printf("%d ", a[i]);
    
        return 0;
    }

    二、选择排序(O(n^2))

    设置两重循环,第一重循环做为比较的基准,第二重循环找它后面比它小的数,然后与之交换。

    就是选择一个它后面的所有比他小的数中的最小的数进行交换,冒泡排序是固定后面的数,而选择排序是固定前面的数。

    特性:

    移动数据的次数已知(n-1 次)。

    动图演示:

      

    详细解析代码:

    #include<stdio.h>
    
    int main() {
        int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
        //为了方便大家理解,我将数值设为与动图一样的数值。
    
        int n = 15;
        //选择排序
        for (int i = 0;i < n - 1;i++) // 代表的是要做基数数值的下标,为什么是n - 1呢?
            //因为计数从0开始而且最后一个数没得数和它进行比较了。
        {
            int k = i;
            for (int j = i + 1;j < n;j++)//找到比基数小的数中的最小数的下标
                if (a[k] > a[j]) k = j;
    
            if (k != i) {
                int temp = a[k];
                a[k] = a[i];
                a[i] = temp;
            }
        }
    
        for (int i = 0;i < n;i++)
            printf("%d ", a[i]);
    
        return 0;
    }

    三、插入排序(O(n^2))

    从第二个数开始,将数字抽出来与前面的数依次比较,大于它的统统向后移动一格。

    也就是将抽出来的数放到它该在的位置。

    特性:

    在大多数元素已经有序的情况下,插入排序的工作量较小。

    动图演示:

    详细解析代码:

    #include<stdio.h>
    
    int main() {
        int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
        //为了方便大家理解,我将数值设为与动图一样的数值。
    
        int n = 15;
        //选择排序
        for (int i = 1;i < n ;i++) // 代表的是要放回原位数数值的下标
        {
            int front = i- 1;//初始化前一个数的下标
            int sum = a[i];//放回原位数数值
            while (front >= 0 && a[front] > sum)
                //防止数组越界并且前面数比后面的数大才能继续
                a[front + 1] = a[front--];
            //把前面数向后移动,前一个数的下标-1,,想象动图中空格的变化
    
            //上个循环结束了,说明空格不能再变化了,将其赋值回去就好了
            a[++front] = sum;
        }
    
        for (int i = 0;i < n;i++)
            printf("%d ", a[i]);
    
        return 0;
    }

    四、归并排序 O(nlog(2)n)

    特性:

    可顺便处理逆序对的问题。

    动图演示:

    这个算法用动图充分体现了它的思想,放在下面的是辅助数组,将每部分以两段两段的分开进行排序,分开的排好了,再进行总的排序。

    详细解析代码

    #include<stdio.h>
    
    int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
    //为了方便大家理解,我将数值设为与动图一样的数值。
    int t[20];//辅助数组,中转站的意思。
    
    void merge_sort(int l, int r) {
     //如果两个数组下标相邻,就不必要再进行二分了,再分就只有一个了,无法比较了。
        if (r - l > 1) {
            int m = l + (r - l >> 1), i = l, j = m, k = l;
            //m为分开的的这一段的中间下标,i为左边起始点,j为右边起始点,k为辅助数组起始点。
            //将总的一段二分。
            merge_sort(l, m);
            merge_sort(m, r);
    
            //然后再将这两段归并到一起进行排序。
            while (i < m || j < r)//防止下标越界
    
                //先放到辅助数组中的肯定是小的,所以我们只需要将两边较小的放到辅助数组中就好了。
                if (j >= r || (i < m && a[i] <= a[j])) t[k++] = a[i++];
            //注意下标越界问题。
                else t[k++] = a[j++];//这里处理逆序对问题。
    
    
            for (i = l;i < r;i++) a[i] = t[i];
        }
    }
    
    int main() {
        int n = 15;
    
        merge_sort(0, n);
        for (int i = 0;i < n;i++) printf("%d ", a[i]);
    
        return 0;
    }

    逆序对问题    代码:

    #include<stdio.h>
    
    int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
    //为了方便大家理解,我将数值设为与动图一样的数值。
    int t[20];//辅助数组,中转站的意思。
    int ans;//逆序对数量
    
    void merge_sort(int l, int r) {
        //如果两个数组下标相邻,就不必要再进行二分了,再分就只有一个了,无法比较了。
        if (r - l > 1) {
            int m = l + (r - l >> 1), i = l, j = m, k = l;
            //m为分开的的这一段的中间下标,i为左边起始点,j为右边起始点,k为辅助数组起始点。
            //将总的一段二分。
            merge_sort(l, m);
            merge_sort(m, r);
    
            //然后再将这两段归并到一起进行排序。
            while (i < m || j < r)//防止下标越界
    
                //先放到辅助数组中的肯定是小的,所以我们只需要将两边较小的放到辅助数组中就好了。
                if (j >= r || (i < m && a[i] <= a[j])) t[k++] = a[i++];
            //注意下标越界问题。
                else t[k++] = a[j++],ans+=m-i;//这里处理逆序对问题。
    
    
            for (i = l;i < r;i++) a[i] = t[i];
        }
    }
    
    int main() {
        int n = 15;
    
        merge_sort(0, n);
        printf("%d
    ", ans);
    
        return 0;
    }

    只要你把

    else t[k++] = a[j++];成else t[k++]=a[j++],ans+=m-i;就好了,记得声明ans变量。

    逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i < j 且 a[i] > a[j],则其为一个逆序对;否则不是。

    而else恰好符合这个条件只要有一个是从右边的数加进来的,就说明当前(左边)下标的  i到m-1都是大于这个数的。

    五、快速排序 O(nlog(2)n)

    以中间数作为基数,同时从左右边开始比较,因为基数是在中间,所以左边的数要比基数小,右边的数要比基数大,否则就交换数值,分支递归反复便得出正确的顺序。

    特性:

    可以处理求第几大的问题;极快,数据移动少;

    缺点:

    不稳定。

     

    详细解析代码:

    #include<stdio.h>
    
    int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
    //为了方便大家理解,我将数值设为与之前一样的数值。
    
    void quick_sort(int l, int r) {
        if (l < r) {//防止越界
            int i = l - 1, j = r + 1, x = a[l + r >> 1];
            //-1、+1的原因是因为下面我们要用++i、--j。
            //为什么不用i++、j++呢?因为我们要比较数值,要让i、j与我们比较的数值下标同步,提前+1或者-1会导致后面的交换发生错误。
            //前者是先使用i+1再将i=i+1,后者是先使用i再将i=i+1。
            while (i < j) {
                //双指针移动不符合就暂停
                while (a[++i] < x);
                while (a[--j] > x);
    
                //两边都不符合了,交换数值。
                if (i < j) {//学了c++后可直接用swap函数。
                    int t = a[i];
                    a[i] = a[j];
                    a[j] = t;
                }
            }
            //再分治,左右两边递归排序。
            quick_sort(l, j);quick_sort(j + 1, r);
        }
    }
    
    int main() {
        int n=15;
    
        quick_sort(0, n - 1);
        for (int i = 0;i < n;i++) printf("%d ", a[i]);
        return 0;
    }

    寻找第k小的数    代码:

    #include<stdio.h>
    
    int a[] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
    //为了方便大家理解,我将数值设为与之前一样的数值。
    
    int quick_sort(int l, int r, int k) {
        if (l == r) return a[l];
        int i = l - 1, j = r + 1, x = a[l + r >> 1];
        //-1、+1的原因是因为下面我们要用++i、--j。
        //为什么不用i++、j++呢?因为我们要比较数值,要让i、j与我们比较的数值下标同步,提前+1或者-1会导致后面的交换发生错误。
        //前者是先使用i+1再将i=i+1,后者是先使用i再将i=i+1。
        while (i < j) {
            //双指针移动不符合就暂停
            while (a[++i] < x);
            while (a[--j] > x);
    
            //两边都不符合了,交换数值。
            if (i < j) {//学了c++后可直接用swap函数。
                int t = a[i];
                a[i] = a[j];
                a[j] = t;
            }
        }
    
        int s = j - l + 1;//判断左边有多少数,是否包含第k个数
        //分治,不断缩小范围,直到l==r,输出答案。
        if (k <= s) return quick_sort(l, j, k);
        //k>s的话说明第K个值在右边的第k-s 个数中,那么在右边的k=k-s;
        return quick_sort(j + 1, r, k - s);
    }
    
    int main() {
        int n = 15, k = 5;
    
        int ans = quick_sort(0, n - 1, k);
        printf("%d 
    ", ans);
        return 0;
    }
  • 相关阅读:
    前端 HTML CSS
    前端部分1:HTML
    异常处理专区:
    IO模型介绍
    协程专区
    线程专区
    操作系统简介专区
    进程专区
    正则表达式专区:
    题解 P2158 【[SDOI2008]仪仗队】
  • 原文地址:https://www.cnblogs.com/Attacking-vincent/p/12771757.html
Copyright © 2011-2022 走看看