算法学习1_排序

题目一 桶排序
高考结束了,班主任需要计算班里学生的总分和并且从大到小排序?

解答:
最高分 是 750分 所以我们建立的桶需要 大于 750 个 我们这里使用 1000 个
桶排序的核心思想
根据该数组中值的范围,定一个包括所有值的数组,比如一个班的学生的高考总数进行排序那么就确定一个数组int book[1001]
这样
就用book数组的角标值标识分数,从0到1000。然后遍历需要排序的数组,把每个角标所存储的值,作为book数组的角标,然后对book数组
当前角标的值进行++,从而确定了每个分数有多少人得了这个分数,然后再遍历book数组,根据socre所存储的值的大小去确认当前分数出现
的次数,这样就完成了对分数数组进行排序。
这里我们定义的包括所有值的数组socre,其实就是拿了一排桶过来,桶上面有编号0-1000,然后把我们需要排序的小球数组,根据小球的编号(分数),分别放入这个桶里面,然后去数每一个桶里出现了多少个小球,从而完成了排序

int main(int argc, const char * argv[]) {
    int book[1001];
    int i,j,t,n;
    
    for (i = 0; i < 1001; i ++) {
        book[i] = 0;
    }
    printf("输入一个数n,表示之后将要输入n个数:");
    scanf("%d",&n);
    for (i = 0; i < n + 1 ; i ++) {
        // 循环读入n个数,并进行桶排序
        printf("请输入分数:");
        scanf("%d",&t);
        book[t]++;
    }
    
    for (i = 1000; i >= 0; i --) {
        for (j = 1; j <= book[i]; j ++) {
            printf("%d  ",i);
        }
    }
}

空间复杂度:
所谓的算法的空间复杂度,其实简单的理解就是该算法运行时,临时占用存储空间大小的度量。说白了,其实就是这个算法
运行的时候,会消耗多大的内存,那么桶排序的空间复杂度是什么样的呢??从我们写的算法可以看出,其实该算法主要占用内存空
间的是原始数据数组和桶数组,所以随着数据量的增加和数据内容的复杂,所需要的内存
越多。
时间复杂度:
所谓的算法的时间复杂度,其实就是算法运行所需的时间,我们近似的将每一行代码的执行时间看为相同,那么桶排序的
所需要的时间,就是第4行执行次数m,和第7行执行次数n,简单的标示就是O(m+n)。桶排序其实是一种相当快速的排序方式。

桶排序缺点:桶排序的的空间复杂度较高 ,如果计算大量的数据,需要消耗的内存空间是相当大的

题目二 冒泡排序
高考结束了,班主任需要计算班里学生的总分和并且从大到小排序,并且排序要把相应的名字对应起来

冒泡排序核心思想 :冒泡排序的基本思想是:每次比较两个相邻的元素,如果它们的顺序错误就把它们交换 过来。
这里可以使用冒泡排序

// 用于冒泡排序 这里创建了一个结构体用来存储姓名和分数
struct student
{
    char name[21];
    char score;
};
    //下面这些方法在main方法里面 
int main(int argc, const char * argv[]) {
    // 冒泡排序,结构体可以得到分数和分数对应的人 

    struct student a[100], t; // a[100] 和 t 都是结构体对象 学生小于100人
    int i,j,n;
    
    printf("输入一个数n,代表之后要排序的个数为n个:");
    scanf("%d",&n);
    
    for (i = 0; i < n; i ++) {
        printf("请输入一个数");
        scanf("%s %d",a[i].name , &a[i].score);
        
    }
    // 冒泡算法核心比较邻居的大小,来交换位置
    for (i = 0; i < n; i ++) {
        for (j = 0; j < n - i; j ++) {
            if (a[j].score < a[j+1].score) {
                t = a[j];
                a[j] = a[j+1];
                a[j+1] = t;
            }
        }
    }
    
    for (i = 0; i < n; i++) {
        printf("%s, %d ",a[i].name,a[i].score);
    }
}
    

时间复杂度: 冒泡排序的核心部分是双重嵌套循环,时间复杂度是O(N^ 2) 这个时间复杂度是相当高的,所以一般这个算法除了我们听的比较多,一般都不太推荐使用

题目三 最常用的排序-快速排序
高考结束了,某县要求需要计算该县里学生的总分和并且从大到小排序?
分析: 假设计算机每秒计算1亿次一个该县 高三有100000个人,我们需要对着100000个人的分数进行排序 使用 冒泡排序 需要100亿次 ,需要100秒,有没有更快的呢,当然有,快速排序

快速排序:这个有点复杂,我来举个简单例子
假如 要对 6 1 2 7 9 3 4 5 10 8,进行排序,快速排序,首先要在队列里面随便找一个数作为"基准数",为了方便,就让第一个数 6 作为基准数吧。接下来,需要将这个序列中 所有比基准数大的数放在 6 的右边,比基准数小的数放在 6 的左边,类似下面这种排列。
实际上会经过下面的流程

排序1
第一步 首先以6位基准数 ,从右到左(注意:一定是先右后左) 开始找基准数小的数为5 ,从左往右比6大的数是7 ,然后 5 和 7交换位置变为
6 1 2 5 9 3 4 7 10 8

第二步 首先以6位基准数 ,从右到左 开始找基准数小的数为4 ,从左往右比6大的数是4 ,然后 4 和 9交换位置变为
6 1 2 5 4 3 9 7 10 8

第三步 首先以6位基准数 ,从右到左 开始找基准数小的数为3 ,从左往右比6大的数在找到之前和3相遇,然后 6 和 3交换位置变为
3 1 2 5 4 6 9 7 10 8
进过上面排序之后,大于基准数6的有 9,7,10,0,8 , 小于基准数的有 3,1,2,5,4

排序2
3,1,2,5,4 我们让3作为基准数, 再次重复上面操作
得到 2,1,3,5,4 基准数 左边有 2,1 右边有5,4

9,7,10,0,8 我们让9作为基准数, 再次重复上面操作
得到0,7,8,9,10 基准数左边有0,7,8,右边有10

排序n次
重复上面操作,最后可以得到
1 2 3 4 5 6 7 8 9 10

下面是demo实现

int a[100001];//定义全局变量,用于学生存储分数 需要大于人数100000
void quickSort (int left, int right); // left 代表左边分数下标编号 right右边分数下标编号 


int main(int argc, const char * argv[]) {
    int i,n ;
    printf("输入一个数字n,代表之后会有n个人:");
    scanf("%d",&n);
    for (i = 0; i < n; i++) {
        printf("输入一个成绩:");
        scanf("%d",&a[i]); 
    }
    
    quickSort(0, n - 1);
    
    for (i = 0; i < n; i ++) {   // 冒泡排序,和快速排序都是使用该方法去重
         printf("%d ",a[i]); // 我们这里不需要去重
       // if (a[i] != a[i + 1]) { // 去重
           // printf("%d ",a[i]);
        //}
    }
     getchar();
}
/**
 快速排序

 @param left 左边的哨兵下标
 @param right 右边的哨兵下标
 */
void quickSort (int left, int right)
{
    int i ,j ,t ,temp;
    if (left > right) {
        return;
    }
    
    temp = a[left]; // temp 中存的就是基准数
    
    i = left;
    j = right;
    
    while (i != j) {  // 左右两个哨兵没有相遇的时候
        
        // 顺序很重要,首先right哨兵从右往左开始移动
        while (a[j] >= temp && i < j) {
            j --;
        }
        //  然后left哨兵从左往右开始移动
        while (a[i] <= temp && i < j) {
            i ++;
        }
        
        // 两个哨兵都结束的时候,交换位置
        if (i < j) { // 两个哨兵没有相遇得时候
            t = a[i];
            a[i] = a[j];
            a[j] = t;
         }
    }
    //最终将基准数归位
    
    a[left] = a[i]; // 重新定义新的left哨兵
    a[i] = temp;
    
    quickSort(left, i - 1);  // 继续处理左边的,这是一个递归的过程
    quickSort(i + 1, right); // 继续处理右边的,只是一个递归的过程
    
}

输入成绩别较真,还是用上面的测试数据6 1 2 7 9 3 4 5 10 8进行排序,这只是一个演示

优点: 快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候 设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全 部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样只能在相邻的数之间进 行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。当 然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和 冒泡排序是一样的,都是 O(N2),它的平均时间复杂度为 O (NlogN)。其实快速排序是基于一 种叫做“二分”的思想

还有大家别以为这个时间复杂度没变多少,我举个例子
假如我 们的计算机每秒钟可以运行 10 亿次,那么对 1亿个数进行排序
桶排序需要 0.1秒,
冒泡排序则需要 1千万秒,达到 115 天之久,是不是很吓人?
快速排序则需要8 秒


本次学习就到这里了,我们学习了常见的三种排序方式,桶排序、冒泡排序、快速排序,对他们的思想和实现原理也进行了深入分析。最好自己动手,不看相关代码,去实现一遍,看看自己是否已经掌握了这几种排序方式。
算法这个东西,听起来很吓人,但是只要认真学习,还是很有意思的。
本人也是刚刚学习,如有错误,请多多指正。
本文部分内容参考 【啊哈!算法】这本书
代码例子

你可能感兴趣的:(算法学习1_排序)