冒泡排序与快速排序

Posted Shemesz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了冒泡排序与快速排序相关的知识,希望对你有一定的参考价值。


   交换排序的基本思想是: 两两比较待排序记录的关键字,一旦发现两个记录不满足次序要求时,则进行交换,直到整个序列满足要求位置。在这里首先介绍基于简单交换思想实现的排序——冒泡排序,然后再此基础上进行改进的排序方法——快速排序。

一、冒泡排序

  冒泡排序(Bubble Sort) 一种最简单的交换排序方法,他通过比较两两比较相邻记录的关键字,如果发生逆序,则进行交换,从而使关键字小的记录如气泡般上浮,或者使关键字大的记录如石块一样逐渐下沉。
在这里插入图片描述
很形象的说明,两两比较,小的上浮,大的下沉,下面通过代码来观察一下具体实现吧!

1. 代码展示

#include <stdio.h>

#define ARR_LEN 255 /*数组长度上限*/
#define elemType int /*元素类型*/

/* 冒泡排序 */
/* 1. 从当前元素起,向后依次比较每一对相邻元素,若逆序则交换 */
/* 2. 对所有元素均重复以上步骤,直至最后一个元素 */
/* elemType arr[]: 排序目标数组; int len: 元素个数 */
void bubbleSort(elemType arr[], int len) {
    elemType temp;
    int i, j;
    int BJ=0, JH=0;      /*观察一下比较了多少次,交换了多少次*/
    for (i = 0; i < len - 1; i++) /* 外循环为排序趟数,len个数进行len-1趟 */
    {
        for (j = 0; j < len - 1 - i; j++)/* 内循环为每趟比较的次数,第j趟比较len-1-i次 */
        { 
            BJ++;
            if (arr[j] > arr[j + 1]) /* 相邻元素比较,若逆序则交换(升序为左大于右,降序反之) */
            {
                JH++;

                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    printf("总共比较了%d次,总共交换了%d次\\n", BJ, JH);
}

int main(void) {
    elemType arr[ARR_LEN] = { 3,5,1,-7,4,9,-6,8,10,4 };
    int len = 10;
    int i;

    bubbleSort(arr, len);
    for (i = 0; i < len; i++)
        printf("%d\\t", arr[i]);
    putchar('\\n');

    return 0;
}

注意:冒泡排序就是把N个数进行N-1轮排序;升序排序中,大的数往下沉,小的往上浮;降序排序中,小的下沉,大的上浮
在这里插入图片描述

2. 代码优化

在代码运行过程当中会出现排序(N-1轮)未结束,但是序列已经不需要再排序,已经是排好序的状态了,但代码还是要按照流程走完比较余下序列的比较,这样计算机只是在做无用功而已,那么如何优化呢?直接上代码

在这里插入图片描述
定义 flag = 1

  • (1)若在比较循环中 flag=0 标志位通过比较不需要进入交换语句当中,则 flag=0不符合外层循环条件,循环终止排序结束;
  • (2)若通过比较发现需要交换,则在交换语句中继续通过flag=1的语句进入下一次循环的判断当中,直到排序结束

未优化之前45次,优化后35次
在这里插入图片描述

3. 时空分析

  • 时间复杂度 O(n^2)
  • 空间复杂度 O(1)
  • 稳定排序

二、快速排序

  快速排序(Quick Sort) 是由冒泡排序改进而得的。在待排序的N个记录中,任意选取一个记录 (通常都为第一个)作为中枢轴(pivotkey)。经过一趟排序后,把所有关键字小于pivotkey的记录交换到左边(前面),把所有大于pivotkey的记录换到右边(后面)结果将排序分成两个子序列,最后将选中的pivotkey放到分界处的位置,然后分别对左、右子表重复上述过程,知道每个子表只有一个记录时排序完成。

简单来说:

  • 采用分治思想处理问题:将问题一直细化,知道解决能解决为止(后面通过展示即可理解)
    在这里插入图片描述
  • 通过递归函数实现快排
    在这里插入图片描述

原理展示: 原序数组(L:左下标 | R:右下标)
在这里插入图片描述

  • 1)选第一个数为pivotkey
    在这里插入图片描述
  • 2)R指向的数与pivotb比较发现小于pivot,则赋值给L的位置
    在这里插入图片描述
    在这里插入图片描述
  • 3)L右移,又开始比较,比pivot大,赋值给R的位置
    在这里插入图片描述
    在这里插入图片描述
  • 4)R开始左移,进行比较,比pivo小,赋值给L所在位置
    在这里插入图片描述
    在这里插入图片描述
  • 5)当L和R重合的时候,即pivot的位置确定,就是第一次排序结束,分出来两个子序列,右子序列只剩一个元素说明排序完成,左子序列继续重复以上步骤继续排序,直到所有子序列都只剩一个元素说明排序完成
    在这里插入图片描述
  • 6)左子序列继续重复以上步骤排序(递归调用)
    在这里插入图片描述
    在这里插入图片描述
  • 7)直到又只剩一个元素
    在这里插入图片描述
  • 8)重复直达每个子序列都只剩一个说明排序完成
    在这里插入图片描述
    这样应该就能感受到快排的精妙指出了吧,通过递归来实现分治思想无线细化,下面就用代码来展示一下!

1. 代码展示

#include <iostream>
using namespace std;

#define BUF_SIZE 8


void print(int arr[], int len)  //打印函数
{
    for (int i = 0; i < len; i++)
    {
        cout << arr[i] << "  ";
    }

    cout << endl;
}

void QuickSort(int arr[], int low, int high)
{
    if (low < high)
    {
        int i = low;
        int j = high;
        int pivot = arr[low];

        while (i < j)
        {
            while (i < j && arr[j] >= pivot) //找到比pivot小的数;
            {
                j--; //high左移,继续寻找比pivot小的数
            }
            if (i < j)
            {
                arr[i++] = arr[j];  //找到比pivot小的数,将值赋值给左边,并让low右移
            }

            while (i < j && arr[i] < pivot) //找到比pivot大的数;
            {
                i++;  //low右移,继续寻找比pivot大的数
            }
            if (i < j)
            {
                arr[j--] = arr[i];  //找到比pivot大的数,将值赋值给右边,并让high左移
            }
        }

        arr[i] = pivot; //相遇后,将支点的值赋予此;

        QuickSort(arr, low, i - 1);     //左子序继续递归调用排序,i将序列分为两半,左边最大肯定是i-1个数;
        QuickSort(arr, i + 1, high);    //右子序继续递归调用排序,i将序列分为两半,右边最小肯定是i+1个数;
    }
}


int main()
{
    int arr[BUF_SIZE] = { 0 };

    cout << "请任意输入8个整型数字:" << endl;

    for (int i = 0; i < BUF_SIZE; i++)
    {
        cin >> arr[i];
    }

    cout << "在使用快速排序前数组顺序:" << endl;

    print(arr, BUF_SIZE);

    QuickSort(arr, 0, BUF_SIZE - 1);

    cout << "排序后,数组的顺序是" << endl;

    print(arr, BUF_SIZE);

    return 0;
}

打印结果:
在这里插入图片描述

2. 代码优化

优化递归操作
什么是尾递归呢?如果一个函数中递归形式的调用出现是在函数末尾,我们称这个递归函数是尾递归的。
当编译器检测到一个函数使用尾递归的时候,他就覆盖当前的活跃记录,而不是在栈中去创建一个新的。编译器可以做到这一点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回的时候栈帧中并没有其他事可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得运行效率会变得更高。
因此,只要有可能我们就需要将递归函数写成尾递归的形式。

3.时空分析

  • 时间复杂度:O(nlog₂n)
  • 空间复杂度:O(log₂n)
  • 不稳定排序

以上是关于冒泡排序与快速排序的主要内容,如果未能解决你的问题,请参考以下文章

python 冒泡排序与快速排序 遇到的错误与问题

Java 冒泡排序与快速排序的实现

java冒泡排序和快速排序代码

nodejs实现冒泡排序和快速排序

交换排序(冒泡排序快速排序的算法思想及代码实现)

算法——冒泡排序与快速排序的分析