哈佛大学公开课:计算机科学cs50 学习笔记及代码练习(第8集:冒泡,选择排序,递归)

0. 前言

这门课讲的排序相当清楚,老师用很容易懂的方式讲原理,代码部分在linux下写,用gdb调试,这才是编程的学习方法。记得以前国内本科也学过,但根本没讲清楚。现在研究生又听这门公开课,发现把原理说清楚之后,代码自己很容易就实现了。

另外gdb调试工具也很好用很重要,但国内学的时候老师甚至没有提过。

1. gdb 调试工具的用法

几个命令:run,break, continue, next, step

https://blog.csdn.net/shaozhenghan/article/details/81370240

2. 冒泡排序。

1 到 8  8个整数,从小到大排序。

最坏的情况,初始排列是:8,7,6,5,4,3,2,1

冒泡排序是相邻元素两两比较,然后交换。在最坏情况下需要交换8*8=64次。

因此时间复杂度是O(n^2)

以上是听课的理论,下面是课后自己写代码练习,从小到大排序。

代码实现1--C语言

#include <stdio.h>

void swap(int *a, int *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void printArray(const int array[], size_t size)
{
    for(size_t i = 0; i != size; ++i)
    {
        printf("%d\n", *(array+i));
    }
}

// From small to large
void bubbleSort(int array[], size_t size)
{
    for(size_t i = 0; i != size-1; ++i)
        for(size_t i = 0; i != size-1; ++i)
        {
            if(array[i] > array[i+1])
                swap(array+i, array+i+1);
        }
}


int main(int argc, char const *argv[])
{
    int a[8] = {8,7,6,5,4,3,2,1};
    size_t size = 8;
    printArray(a, size);
    bubbleSort(a, size);
    printArray(a, size);
    return 0;
}

需注意C语言没有传引用形参!所以C语言版本的必须用指针来写swap函数。

代码实现2--C++

#include <iostream>

void swap(int &a, int &b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

void printArray(const int array[], size_t size)
{
    for(size_t i = 0; i != size; ++i)
    {
        std::cout << array[i] << std::endl;
    }
}

// From small to large
void bubbleSort(int array[], size_t size)
{
    for(size_t i = 0; i != size-1; ++i)
        for(size_t i = 0; i != size-1; ++i)
        {
            if(array[i] > array[i+1])
                swap(array[i], array[i+1]);
        }
}


int main(int argc, char const *argv[])
{
    int a[8] = {8,7,6,5,4,3,2,1};
    size_t size = 8;
    printArray(a, size);
    bubbleSort(a, size);
    printArray(a, size);
    return 0;
}

数组是以指针形式传递给函数的,所以函数一开始并不知道数组的确切尺寸,所以应该提供一些额外的信息。C++中常用方法有两种:1. 显示地传递一个表示数组大小的形参,这也是C语言和C++11标准之前常用的; 2. 在C++11标准中,可以传递指向数组首元素和尾后元素的指针, 用std::begin() 和 std::end()  函数,更简单,更安全,避免下标越界或者访问非法指针造成segmentation error。

我这里只用了第一种,第二种见我这篇博客:https://blog.csdn.net/shaozhenghan/article/details/81437799

代码实现3--C++ 容器vector版本

#include <iostream>
#include <vector>

void swap(int &a, int &b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

void printArray(const std::vector<int> &intvec)
{
    for(auto &i : intvec)
    {
        std::cout << i << " ";
    }
    std::cout << "\n";
}

// From small to large
void bubbleSort(std::vector<int> &intvec)
{
    for(size_t i = 0; i != intvec.size()-1; ++i)
        for(auto it = intvec.begin(); it != intvec.end()-1; ++it)
        {
            if((*it) > *(it+1))
                swap(*it, *(it+1));
        }
    // printArray(intvec);
}


int main(int argc, char const *argv[])
{
    std::vector<int> a = {8,7,6,5,4,3,2,1};
    std::cout << "before sorting" << std::endl;
    printArray(a);
    bubbleSort(a);
    std::cout << "after sorting" << std::endl;
    printArray(a);
    return 0;
}

使用c++的vector容器类型,可以使用迭代器,范围for循环,auto类型等C++11新标准特性,使得代码更安全,不容易出现下标越界缓冲区溢出等严重错误。

需注意,当vector类型做形参时,如果不用引用会发生拷贝构造!所以bubbleSort函数要这样写:void bubbleSort(std::vector<int> &intvec); 不用引用就起不到排序改变vector对象的效果。这一点与数组不同,数组做形参时只能传递数组指针,不会发生拷贝构造,函数内部对数组的改变能起到改变实参的效果。

另外printArray函数形参要写成常量引用,因为函数内部不会改变vector对象,这一点要养成习惯。

3. 选择排序

1 到 8  8个整数,从小到大排序。

假设初始排列是:4,2,6,8,1,3,7,5

那么经过第一次排序后,变为:1,2,6,8,4,3,7,5, 即4和1互换了。

第二次从第二项开始比较,因为2是从第二项开始最小的数,所以不变。

最坏情况下(初始排列为:8,7,6,5,4,3,2,1),需要交换8+7+6+……+1 次,即n+……+1=n(n+1)/2

时间复杂度是O(n^2). (只考虑高次项)

代码实现一下:

#include <iostream>

void swap(int &a, int &b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

void printArray(const int array[], size_t size)
{
    for(size_t i = 0; i != size; ++i)
    {
        std::cout << array[i] << " ";
    }
    std::cout << "\n";
}

// From small to large
void selectSort(int array[], size_t size)
{
    for(size_t j = 0; j != size-1; ++j)
        for(size_t i = j+1; i != size; ++i)
        {
            if(array[j] > array[i])
                swap(array[j], array[i]);
        }
}


int main(int argc, char const *argv[])
{
    int a[8] = {4, 2, 6, 8, 1, 3, 7, 5};
    size_t size = 8;
    printArray(a, size);
    selectSort(a, size);
    printArray(a, size);
    return 0;
}

4. 递归调用

好处:代码简洁,避免了循环。

坏处:有内存溢出的风险。segmentation error 段错误(核心已转储)

例子:求1到整数m的累加和

int sum_one_to_m (int m)
{
    if (m <= 0)
        return (0);
    else
        return (m + sum_one_to_m (m-1));
}

猜你喜欢

转载自blog.csdn.net/shaozhenghan/article/details/81278432
今日推荐