排序(三)--交换排序:冒泡排序、快速排序

一、冒泡排序
1.算法思想

冒泡排序(Bubble Sort)是一种简单的排序算法。
为什么会叫做冒泡排序呢?这是由于它的算法思想就类似于鱼儿在河里吐泡泡的场景,例如升序排列一列数,它会两两相邻的数据进行比较,如果前者大于后者就交换,重复此番工作直到交换到最后两个数据,第一趟冒泡排序已经完成,最大的数据被冒到数组的最后一个位置,继而缩小冒泡的区间,又从头开始第二趟冒泡,直到次大数被放在倒数第二个位置,以此类推,直到所有数据被冒到合适位置,冒泡排序就算完成。

2.算法的具体步骤

(1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。
(2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
(3)针对所有的元素重复以上的步骤,除了最后一个。
(4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

3.图解举例

这里写图片描述

4.代码实现
//冒泡排序
void BubbleSort(int* arr, int size)
{
    if (arr == NULL || size <= 0)
        return;

    for (int i = 0; i < size; i++)
    {
        for (int j = 0; j < size - i - 1; j++)
        {
            if (arr[j]>arr[j + 1])
            {
                //swap(arr[j], arr[j + 1]);
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}

优化版本

void BubbleSort(int* arr, int size)
{
    int flag = 0;
    for (int i = 0; i < size; i++)
    {
        flag = 0;
        for (int j = 0; j < size - 1 - i; j++)
        {
            if (arr[j]>arr[j + 1])
            {
                swap(arr[j], arr[j + 1]);
                flag = 1;
            }
        }
    }

    if (flag == 0)
    {
        return;
    }
}
5.其他

(1)时间复杂度:O(n^2)
(2)空间复杂度:O(1)
(3)稳定性:稳定

6.动态图

这里写图片描述

二、快速排序
1.算法思想

本质上,快速排序就是冒泡排序的一种改进,冒泡排序是通过每一趟冒泡将最大值(最小值)放到恰当位置。
而快速排序则是每趟排序从待排序区间选一个基准值,将比它小的数据全放在其左边,将比它大的值放在其右边然后递归其左右子区间对其排序,一层层递归下去,某区直到间只剩一个数据时,停止递归,此子区间已经算是有序,继而向其上层区间返回,一层层向上返回,当首次基准值的左右区间均已有序时,整个排序就算完成。

2.算法具体的步骤

(1)从数列中挑出一个元素,称为 “基准”。
(2)重新排序数列,所有元素比基准值小的摆放在基准左边,所有元素比基准值大的摆在基准的右边(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置,这个称为分区操作。
(3)递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。

3.算法种类

目前比较热门的有三种实现快排的算法:左右指针法、挖坑法、前后指针法。

4.不同算法实现及其优化

(1)左右指针法
1> 算法的思想步骤

  • 用两个指针left和right用来标识区间范围,这里我将key值定义为区间最右边的值,所以要左指针开始走(当然,你也可以将key值定义为区间最左边的值)。
  • 左指针向右找比key值大的数据,找到后停下来,现在右指针向左开始找比key小的数据,找到后将左右指针的值交换。
  • 左指针继续找比key大的值,右指针继续找比key小的值,找到后交换,重复此过程,直到左右指针相遇,然后将左指针所在位置的值赋值为key。
  • 此时比key值小的数据全部在key的左边,比key大的值全在key的右边。
  • 按照上述同样的方法递归以上key值的左右区间,使之有序后排序完成。

2> 算法思想如下图
这里写图片描述

3> 代码实现

//左右指针法
int quicksort1(int* arr, int left, int right)
{
    if(arr==NULL||left>right)
    return NULL;

    int key = right;

    while (left < right)
    {
        while (arr[left] < arr[key])
        {
            ++left;
        }

        while (arr[right]>arr[key])
        {
            --right;
        }

        if (arr[left] != arr[right])
        {
            //swap(arr[left],arr[right]);

            int tmp = arr[left];
            arr[left] = arr[right];
            arr[right] = tmp;
        }
    }
    swap(arr[left], arr[right]);
    return left;
}

(2)挖坑法
1> 算法的思想步骤

  • 用两个指针left和right用来标识区间范围,初始坑设置到key值的地方。我们将key值定义为区间最右边的值。

  • 左指针向右找比key值大的数据,找到后将左指针所指的数据填入坑中,将坑更新为左指针所指位置。现在右指针向左开始找比key小的数据,找到后将右指针所指的数据填入坑中,将坑更新为右指针所指位置。

  • 重复上述过程,左右指针继续走,向中间靠拢直到左右指针相遇,然后将坑处的值值赋值为key。

  • 此时比key值小的数据全部在key的左边,比key大的值全在key的右边。

  • 按照上述同样的方法递归以上key值的左右区间,使之有序后排序完成。

2> 思想展示图
这里写图片描述
3> 代码实现

//挖坑法
int quicksort2(int* arr, int left, int right)
{
    if(arr==NULL||left>right)
    return NULL;

    int key = right;
    int blank = right;

    while (left < right)
    {
        while (arr[left] <= arr[key])
        {
            left++;
        }
        arr[blank] = arr[left];
        blank = left;

        while (arr[right]>=arr[key])
        {
            right--;
        }
        arr[blank] = arr[right];
        blank = right;
    }

    arr[blank] = key;
    return blank;
}

(3)前后指针法
1> 算法具体步骤

  • 定义两个指针prev和cur,最开始cur指向最左边的位置,prev指向cur的前面(即就是空)。
  • 定义key值为最右边的数字。
  • 当cur小于key时,cur和prev同时++,大于等于时cur++,prev不变。如果cur一直找不到比key小的值,cur走到最右边,此时prev++,并且交换prev和cur的值。
  • 重复上述过程,当cur和prev相等时,排序结束。
    2> 算法的具体图形
    这里写图片描述
    3> 代码实现
//前后指针法
int quicksort3(int* arr, int left, int right)
{
    if (arr == NULL || left > right)
        return NULL;

    int prev = left-1;
    int cur = left;
    int key = arr[right];

    while (cur != right)
    {
        if (arr[cur] < key&&arr[cur] != arr[prev])
        {
            //swap(arr[cur], key);

            int tmp = arr[cur];
            arr[cur] = key;
            key = tmp;
        }
        ++cur;
    }

    //swap(arr[++prev], arr[cur]);
    int tmp = arr[prev];
    arr[prev] = arr[cur];
    arr[cur] = tmp;

    return prev;
}
5.快排优化

(1)三数取中法
1> 基本思想:

  • 为了把区间尽可能的分配均匀,在取基准值的时候,选取数组最左边,最中间,最右边的元素三个元素中中间大小的元素作为基准值。

2> 代码实现:

//三数取中法
int GetMidIndex(int* arr, int left, int right)
{
    int mid = left + ((right - left) >> 1);

    if (arr[left] < arr[mid])
    {
        if (arr[mid] < arr[right])
        {
            return mid;
        }
        else if (arr[left]<arr[right])
        {
            return right;
        }
        else
        {
            return left;
        }
    }
    else
    {
        if (arr[mid] > arr[right])
        {
            return mid;
        }
        else if (arr[left] > arr[right])
        {
            return right;
        }
        else
        {
            return left;
        }
    }
}

(2)
1> 算法思想

  • 当区间较小时候,一般认为有13个元素以下,这个时候,快速排序就没有直接插入的性能好了,因为当区间比较小的时候,区间划分的就比较多,快排就像一颗二叉树一样,每一次递归都相当于增加一层高度,当区间比较小的时候,就会快速增加二叉树高度,降低了效率,因此我们在划分到小区间的时候就改为插入排序。

2> 代码实现

//小区间优化
void InsertSort(int* arr, int n)
{
    if (arr == NULL || n <= 0)
        return;

    int end = 0;
    for (int i = 1; i < n; i++)
    {
        int tmp = arr[i];
        end = i - 1;

        while (end >= 0)
        {
            if (arr[end]>tmp)
            {
                arr[i + 1] = arr[end];
                --end;
            }
            else
            {
                break;
            }
        }
        arr[i + 1] = tmp;
    }
}

(3)非递归实现快排
1> 算法思想

  • 快排是基于递归子区间来实现的,而当数据元素比较多的时候,难免递归的次数会非常多,而每次函数的递归都是一个函数的栈帧过程,十分消耗时间,因此可以借助栈这种方式,来实现递归转非递归。

2> 代码实现

#include<stack>
//非递归
void QuickSortNoeR(int* arr, int left, int right)
{
    stack<int> s;
    s.push(left);
    s.push(right);

    while (s != empty())
    {
        int start = s.top();
        s.pop();

        int finish = s.top();
        s.pop();

        int div = quickSort1(array, start, finish);
        if (start < div - 1)
        {
            s.push(div - 1);
            s.push(start);
        }
        if (finish > div + 1)
        {
            s.push(finish);
            s.push(div + 1);
        }
    }
}
6.其他

(1)时间复杂度:O(NlogN)

快速排序的最好情况:
快速排序的最好情况是每次都划分后左右子序列的大小都相等,其运行的时间就为O(N*1ogN)。
快速排序的最坏情况:
快速排序的最坏的情况就是当分组重复生成一个空序列的时候,这时候其运行时间就变为O(N*N)

(2)空间复杂度:O(logN)
(3)稳定性:不稳定

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值