数据结构与算法学习cpp(二):冒泡排序、插入排序、选择排序

1、排序算法简介

1.1 相关概念

稳定性:稳定或不稳定;
待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变为稳定,否则为不稳定;
例如:1,2,2,5,9,4,3
将上述数字从小到大排列,两个2的位置不会发生变化则为稳定。

1.2 基础的排序算法

注:表中时间复杂度忽略了系数,实际应用中应考虑系数。

算法时间复杂度稳定性
冒泡排序O( n 2 n^2 n2)稳定
选择排序O( n 2 n^2 n2)不稳定
插入排序O( n 2 n^2 n2)稳定
希尔排序O( n l o g n nlogn nlogn)不稳定
归并排序O( n l o g n nlogn nlogn)稳定
快速排序O( n l o g n nlogn nlogn)不稳定
计数排序O(n)稳定
桶排序O(n)稳定
基数排序O(n)稳定

后续较为复杂的数据结构目的无非是为了方便数据的增删改查,所以了解基础的排序算法有助于更好的理解和学习后续的内容。

今天先来了解基础中的基础:三种简单排序。


2、冒泡排序

2.1 思路

每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。

举例: 对数组{ 5,16,3,11,8,1,3 } 这几个数冒泡排序,要求从小到大。
第一次冒泡图示:
在这里插入图片描述

图示为数第一次冒泡排序的过程,本次结束后最大的数已经在A[6],则下一次排序只需遍历A[0]~A[5],以此类推,直到剩下A[0],则排序结束。

综上所述,若对大小为n的数组进行冒泡排序,共需要进行n次,每次需要遍历n、n-1、n-2… 1 个元素。

2.2 cpp代码

新建类

class Simplesort
{
public:
	void BubbleSort(int *Arr,int n);//冒泡排序
	void PrintResult(int* Arr, int n);//打印
};

函数定义

void Simplesort::PrintResult(int* Arr, int n)
{
    if (n <= 1 || Arr == nullptr)
    {
        return;
    }
    for (int i = 0; i < n; i++)
    {
        cout << setw(4) << Arr[i];
    }
    cout << endl;
}

void Simplesort::BubbleSort(int* Arr, int n)
{
    if (n <= 1 || Arr == nullptr)
    {
        return;
    }
    bool iHasSwap = true;//当前遍历有数据交换发生,若没有,则表示数据已有序,无需再遍历
    for (int i = 0; i < n; i++)
    {
        iHasSwap = false;
        for (int j = 0; j < n - i - 1; j++)
        {
            if (Arr[j] > Arr[j + 1])//交换位置
            {
                int temp = Arr[j + 1];
                Arr[j + 1] = Arr[j];
                Arr[j] = temp;
                iHasSwap = true;
            }
        }
        if (false == iHasSwap)
        {
            break;
        }
    }
}

测试

int main()
{
    Simplesort mysort;
    int testA[7] = { 5,16,3,11,8,1,3 };

    cout << "before BubbleSort" << endl;
    mysort.PrintResult(testA, 7);

    mysort.BubbleSort(testA,7);

    cout << "after BubbleSort" << endl;
    mysort.PrintResult(testA, 7);
}

结果

2.3 性能分析

2.3.1 空间复杂度:

只需要申请常量级临时空间作交换使用,为O(1);

2.3.2 稳定性:

相同大小元素不会交换位置,稳定;

2.3.3 时间复杂度:

最好:
逆序度为0,遍历一遍就推出,为O(n);

最坏:
有序度为0,每个元素都要遍历,根据2.1节分析,需要遍历 ( n − 1 ) + ( n − 2 ) + . . . . + 1 = n ( n − 1 ) 2 次 (n-1)+(n-2)+....+1= \frac{n(n-1)}{2} 次 (n1)+(n2)+....+1=2n(n1),为 O ( n 2 ) O(n^2) O(n2);

平均:
逆序度、有序度各一半,平均需要 n ( n − 1 ) 4 次 \frac{n(n-1)}{4} 次 4n(n1),为 O ( n 2 ) O(n^2) O(n2)


3、插入排序

3.1 思路

将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,为数组的第一个元素。未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。
举例: 对数组{ 5,16,3,11,8,1,3 } 这几个数插入排序,要求从小到大。
插入排序图示:
在这里插入图片描述
将当前元素从无序区间插入到有序区间后,从插入位置起,向后的所有元素都要向后移动1位,正好覆盖了移动前的位置。

3.2 cpp代码

函数声明

class Simplesort
{
public:
	void BubbleSort(int *Arr,int n);
	void InsertSort(int* Arr, int n);
	void PrintResult(int* Arr, int n);
};

函数定义

void Simplesort::InsertSort(int* Arr, int n)
{
    if (n <= 1 || Arr == nullptr)
    {
        return;
    }
    for (int i = 1; i < n; i++)//数组第一个元素直接放在有序空间,所以从第二个元素开始
    {
        int value = Arr[i];
        int j = i - 1;
        for (; j>=0; j--)
        {
            if (Arr[j] > value)//j开始的元素依次向后移动
            {
                Arr[j + 1] = Arr[j];
            }
            else 
            {
                break; //如果在已排序区间内找到<=插入值的数,则退出
            }
        }
        Arr[j + 1] = value;//value插在原来j的位置
    }
}

测试

int main()
{
    Simplesort mysort;
    int testA[7] = { 5,16,3,11,8,1,3 };

    cout << "before Sort" << endl;
    mysort.PrintResult(testA, 7);

    mysort.InsertSort(testA,7);

    cout << "after Sort" << endl;
    mysort.PrintResult(testA, 7);
}

3.3 性能分析

3.3.1 空间复杂度:

无需额外申请临时空间作交换使用,为O(1);

3.3.2 稳定性:

相同大小元素不会交换位置,稳定;

3.3.3 时间复杂度:

最好:
逆序度为0,遍历一遍就推出,为O(n);

最坏:
有序度为0,每个元素都要遍历,根据2.1节分析,需要遍历 ( n − 1 ) + ( n − 2 ) + . . . . + 1 = n ( n − 1 ) 2 次 (n-1)+(n-2)+....+1= \frac{n(n-1)}{2} 次 (n1)+(n2)+....+1=2n(n1),为 O ( n 2 ) O(n^2) O(n2);

平均:
逆序度、有序度各一半,平均需要 n ( n − 1 ) 4 次 \frac{n(n-1)}{4} 次 4n(n1),为 O ( n 2 ) O(n^2) O(n2)


4、选择排序

4.1 思路

类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。
举例: 对数组{ 3,16,5,11,8,3,1 } 这几个数插入排序,要求从小到大(数字顺序与上述举例不同)。
选择排序图示:
在这里插入图片描述
选取未排序区中最小值,插入已排序的末尾,实现方式是与未排序区间的第一个元素交换位置。

4.2 cpp代码

函数定义:

void Simplesort::SelectSort(int* Arr, int n)
{
    if (n <= 1 || Arr == nullptr)
    {
        return;
    }
    int min;
    for (int i = 0; i < n-1; i++)//初始已排序区间为0,最后一个数无需排序,所以i范围0~n-1
    { 
        min = Arr[i]; //选取未排序空间的第一个数作比较
        for (int j = i; j < n; j++)//从i开始到n为未排序空间,遍历所有元素找最小值
        {
            if (Arr[j] < min)
            {
                swap(min, Arr[j]);//最小值更新数据
            }
        }
        Arr[i] = min;//最小值放入已排序区间的末尾
    }
}

测试:

int main()
{
    Simplesort mysort;
    int testA[7] = { 3,16,5,11,8,3,1 };

    cout << "before Sort" << endl;
    mysort.PrintResult(testA, 7);

    mysort.SelectSort(testA, 7);

    cout << "after Sort" << endl;
    mysort.PrintResult(testA, 7);
}

4.3 性能分析

4.3.1 空间复杂度:

需申请临时空间作交换使用,同冒泡排序,为O(1);

4.3.2 稳定性:

相同大小元素会交换位置,不稳定(此案例的两个3会变化,图示的步骤a到步骤b);

4.3.3 时间复杂度:

最好、最坏、平均都需要挨个遍历找最小值,因此都为 O ( n 2 ) O(n^2) O(n2)

代码

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值