各种排序算法的步骤细解

时间:2022-10-07 12:05:18

各种排序算法的步骤细解

选择排序:

//通俗讲解:
//8 6 2 3 1 5 7 4
//从第一个开始往后遍历
//先把第一个8当作最小的元素,他的下标为minIndex
//从第二个开始往后遍历,查找比8小的数
//即6比8小,所以记录6的下标(为1),这时arr[minIndex]为6
//继续遍历,2比6小,所以minIndex=2了,这时arr[minIndex]为2,
//直到遍历结束,这时第一个位置(下标为0的位置)上一定是最小的数了,即为1;

//同上,继续。。。
template<typename T>
void selectionSort(T arr[], int n)
{
    for (int i = 0; i < n; i++)//遍历数组
    {
        int minIndex = i;//记录下标(存放最小值的下标)
        for (int j = i + 1; j < n; j++)//往后寻找比arr[min]小的元素
        {
            if (arr[j]<arr[minIndex])
            {
                minIndex = j;//更新下标
            }
        }
        swap(arr[minIndex], arr[i]);//交换数据
    }
}

插入排序:

//通俗讲解:
//插入排序呢,顾名思义
//8 6 2 3 1 5 7 4
//有两个盒子A,B,一个存放已经排好序的元素,一个存放待排序的元素
//从第一个开始,由于未排序的盒子A为空,所以第一个元素不需要比较,直接就放入了未排序的盒子A 
//第二个元素为6,与第一个元素(8)比较后小于,所以与8交换位置,序列变成了6 8 2 3 1 5 7 4
//第三个元素为2,交换后变成 2 6 8 3 1 5 7 4
//第四个元素为3,依次交换变成 2 3 6 8 1 5 7 4
//继续。。。。
template<typename T>
void  insertionSort(T arr[], int n)
{
    for (int i = 1; i < n; i++)
    {
        //1.
        for (int j = i; j > 0; j--)
        {
            if (arr[j]<arr[j-1])
            {
                swap(arr[j],arr[j-1]);
            }
            else
            {
                break;//这样可以减少运算次数
            }
        }
        //2.改进点:
        for (int j = i; j > 0 && arr[j] < arr[j - 1]; j--)//这样更简便
        {
            swap(arr[j], arr[j - 1]);
        }
    }

**插入排序感觉是减少了运算量,但是给了一个int a[10000]的数组,
发现:
插入排序需要:2.5s左右;
选择排序需要:0.13s左右;
原因就是:插入排序需要交换的次数过多


//8 6 2 3 1 5 7 4
各种排序算法的步骤细解
先复制一个6出来,让6与前面的进行比较
各种排序算法的步骤细解
6小于8,所以,将8赋值给6,6继续往前比较,直到结束
各种排序算法的步骤细解
复制出一个2;
各种排序算法的步骤细解
各种排序算法的步骤细解
各种排序算法的步骤细解

    //改进如下:,通过减少赋值操作来增大效率,每个swap就是3次交换,去除swap

    for (int  i = 1; i < n; i++)
    {
        int e = arr[i];//复制
        int j;//存放要换的位置的下标
        for ( j = i; j > 0&&arr[j-1]>e; j--)
        {
            arr[j] = arr[j-1];
        }
        arr[j - 1] = e;
    }
}

**改进后:
插入排序需要:0.11s左右;
选择排序需要:0.13s左右;
对于接近排好序的数组,则区别更加明显
插入排序需要:0.04s左右;
选择排序需要:0.13s左右;


**

冒泡排序

**

//代码:
template<typename T>
void bubbleSort(T arr[],int n)
{
    //原始写法
    /*for (int i = 0; i < n; i++) { for (int j =0; j < n-i-1; j++)//因为最大肯定在右边了,所以j只需要< n-i-1 { if (arr[j]>arr[j+1]) { swap(arr[j],arr[j+1]); } } }*/
    //优化写法
    bool swapped;
    do  
    {
        swapped = false;
        for (int i = 1; i < n; i++)
        {
            if (arr[i]<arr[i-1])
            {
                swap(arr[i],arr[i-1]);
                swapped = true;//如果将i到n遍历一遍后仍然没有交换,即swapped=false,则这个序列肯定已经排好序了,所以终止
            }
        }
        n--;//因为最大的肯定在最右边,所以n--
    } while (swapped);
}

优化前:
各种排序算法的步骤细解
优化后:
各种排序算法的步骤细解

**

希尔排序

**
各种排序算法的步骤细解
增量依次减小


//代码:
template<typename T>
void shellSort(T arr[], int n){

    int h = 1;
    while( h < n/3 )
        h = 3 * h + 1;
    while( h >= 1 ){

        // h-sort the array
        for( int i = h ; i < n ; i ++ )
        {

            // 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序
            //这下面跟插入排序大同小异,只是元素不同,这里的元素是arr[i], arr[i-h], arr[i-2*h], arr[i-3*h],即在arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]中查找最小值
            T e = arr[i];
            int j;
            for( j = i ; j >= h && e < arr[j-h] ; j -= h )// j >= h 避免出现j<0
               {
                   arr[j] = arr[j-h];
               }
            arr[j] = e;
        }
        h /= 3;//缩小增量因子
    }
}

1W的数组测试:
各种排序算法的步骤细解
100W的:
其他的已经需要很长很长时间了,长的都等不及了:
各种排序算法的步骤细解

**

快速排序

**

//这个写法不像其他的写法一样,先从又往左找小于基数的,在从左往右找大于大于基数的,再两者换位置。(见第二种写法)

//这个写法是先遍历,找小于基数的,小于的话,交换位置,让小于的数,放在左边,大于的数不做改变,再稍加调整就会变成想要的效果。


// 对arr[l...r]部分进行partition操作
// 返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int __partition(T arr[], int l, int r){

    T v = arr[l];

    int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
    for (int i = l + 1; i <= r; i++)
        if (arr[i] < v){
            j++;
            swap(arr[j], arr[i]);
        }

    swap(arr[l], arr[j]);

    return j;
}

// 对arr[l...r]部分进行快速排序
template <typename T>
void __quickSort(T arr[], int l, int r){

    if (l >= r)
        return;

    int p = __partition(arr, l, r);
    __quickSort(arr, l, p - 1);
    __quickSort(arr, p + 1, r);
}

template <typename T>
void quickSort(T arr[], int n){
    __quickSort(arr, 0, n - 1);
}

各种排序算法的步骤细解
第二种:

#include <iostream>

using namespace std;

void Qsort(int a[], int low, int high)
{
    if(low >= high)
    {
        return;
    }
    int first = low;
    int last = high;
    int key = a[first];/*用字表的第一个记录作为枢轴*/

    while(first < last)
    {
        while(first < last && a[last] >= key)
        {
            --last;
        }

        a[first] = a[last];/*将比第一个小的移到低端*/// ①

        while(first < last && a[first] <= key)
        {
            ++first;
        }

        a[last] = a[first];    
/*将比第一个大的移到高端*/
    }
    a[first] = key;/*枢轴记录到位*///因为上面①,把a[first]的原值给覆盖了,但是a[first]有副本key,所以需要重新赋值一下
    Qsort(a, low, first-1);
    Qsort(a, first+1, high);
}
int main()
{
    int a[] = {57, 68, 59, 52, 72, 28, 96, 33, 24};

    Qsort(a, 0, sizeof(a) / sizeof(a[0]) - 1);/*这里原文第三个参数要减1否则内存越界*/

    for(int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    {
        cout << a[i] << "";
    }

    return 0;
}

**

//合并排序/归并排序

**

#include<iostream>
using namespace std;
//合并排序/归并排序
template<typename T>
void __mergeSort(T arr[], int l, int mid, int r)
{
    // 经测试,传递aux数组的性能效果并不好
    //T aux[r - l + 1];
    T *aux = new T[r - l + 1];
    for (int i = l; i <= r; i++)
        aux[i - l] = arr[i];//赋值 

    int i = l, j = mid + 1;
    for (int k = l; k <= r; k++){

        if (i > mid)
        {
            arr[k] = aux[j - l]; j++;
        }
        else if (j > r)
        {
            arr[k] = aux[i - l]; i++;
        }
        else if (aux[i - l] < aux[j - l])
        {
            arr[k] = aux[i - l]; i++;
        }
        else
        {
            arr[k] = aux[j - l]; j++;
        }
    }
    delete[]aux;
}
template<typename T>
void MergeSort(T arr[], int l, int r)
{
    if (l >= r)
    {
        return;
    }
    //优化1:
    /*if (r - l <= 15)//由于小数量的排序,插入排序比归并更划算,所以这选择这样,这个数是测试得到的,不一定是15 { insertionSort1(arr, l, r); }*/


    int mid = (r + l) / 2;
    MergeSort(arr, l, mid);//先将这个运行到底 ①
    MergeSort(arr, mid + 1, r);//这个,mid不是那个例如数组大小为6,mid=3那个mid,而是上步①结束时的mid
    __mergeSort(arr, l, mid, r);

    //优化2:
    /* if(arr[mid] > arr[mid + 1])//例如这里第一次是1个和1个比,比完后,两个元素(数组A)和两个元素(数组B)比,这两个数组里的元素是已经排好序的,所以只需要比较arr[mid] 和 arr[mid + 1]就知道还需不需要去比较; { __mergeSort(arr, l, mid, r);//这个排序,排的是第一对(1边1个),在排第二对。。。然后下一轮第一对(1边2个)相排.。。 } */
}
int main()
{
    int n;
    cin >> n;
    int *arr = new int[n];
    for (int i = 0; i < n; i++)
    {
        cin >> arr[i];
    }
    MergeSort(arr, 0, n-1);
    for (int i = 0; i < n; i++)
    {
        cout << arr[i] << " ";
    }
    delete[]arr;
    return 0;
}