【算法基础——第六讲】排序

发布时间:2023-04-23 19:30

ฅ(๑˙o˙๑)ฅ 大家好, 欢迎大家光临我的博客:面向阿尼亚学习
算法学习笔记系列持续更新中~

\"阿\"


文章目录

  • 一、前言
  • 二、排序算法的介绍
  • 三、排序算法的运用
    • 1.快速排序模板
    • 2.冒泡排序模板
    • 3.归并排序模板
    • 4.选择排序模板
    • 5.插入排序模板
    • 6.Shell排序模板
    • 7.堆积排序模板
  • 四、各模板总结
  • 最后


一、前言

排序算法是最基本的算法之一。


二、排序算法的介绍

排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。

常见的内排序算法主要可以分为两类:

基于比较的排序:冒泡排序、选择排序、插入排序、快速排序、Shell排序、归并排序、堆积排序,其最坏情况下时间复杂度的不可能突破 O ( n l o g n ) O(nlogn) O(nlogn)

非基于比较的排序:基数排序、计数排序、桶排序。

\"【算法基础——第六讲】排序_第1张图片\"

图片来自百度,方便大家了解各种排序算法的时间复杂度以及更好的应用.

三、排序算法的运用

推荐看一下动图演示,百度可以搜到,可以更直观理解.
重点在排序算法的思想,常用的主要就几个.

下述所有算法都以升序为例,数组下标范围为A[0,n - 1]。

1.快速排序模板

算法步骤
1.确定分界点

2.调整区间,使x左边的区间都小于等于x(此时区间内不一定是有序的),右边则大于

3.递归处理左右两段

如下:

//  当前排序区间为[l,r]
//  我们选择的基准元素是区间最左边元素,这时候while循环内部先从右往左找
//  结束while循环后,我们将基准元素交换到对应的位置上。
void quickSort(int l,int r)
{
    if(l >= r) return;
    int pivot = A[l];
    int i = l,j = r;
    while(i < j)
    {
        while(i < j && A[j] >= pivot) j --;
        while(i < j && A[i] <= pivot) i ++;
        if(i < j)
            swap(A[i],A[j]);
    }
    swap(A[l],A[i]);
    quickSort(l,i - 1);
    quickSort(i + 1,r);
}

2.冒泡排序模板

算法步骤

1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。

2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

3.针对所有的元素重复以上的步骤,除了最后一个。

4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

如下:

void BubbleSort(int n)
{
    for(int i = 0 ; i< n-1; i ++)
    {
        for(int j = 0 ; j < i ; j ++)
        {       //  比较相邻元素,是否需要交换
            if(A[j] > A[j + 1])
                swap(A[j],A[j + 1]);
        }
    }
}

3.归并排序模板

算法步骤

1.确定分界点为首末中点

2.以中点为界,递归排序中点两侧使其有序

3.归并,合二为一

void mergeSort(int l,int r)
{
    if(l == r) return;
    int mid = (l + r) >> 1;
    mergeSort(l,mid);
    mergeSort(mid + 1,r);
    int temp[r - l + 1],k = 0,i,j;
    for(i = l,j = mid + 1;i <= mid && j <= r;k ++)
    {
        if(A[i] < A[j]) temp[k] = A[i ++];
        else temp[k] = A[j ++];
    }
    while(i <= mid) temp[k ++] = A[i ++];
    while(j <= r) temp[k ++] = A[j ++];
    for(int i = l,k = 0;i <= r ; i ++,k ++)
        A[i] = temp[k];
}

4.选择排序模板

算法步骤
选择排序算法每次在未排序的数字中选择最大的那个数字放在数组末尾。

i指针控制当前未排序数字的右边界,也是当前未排序数字中最大的元素应该放的位置,我们使用idx代表当前最大元素的下标。每次在比较的时候我们只更新下标,避免频繁的数组元素交换。

void selectSort(int n)
{
    for(int i = n - 1; i >= 0 ; i --)
    {
        int idx = 0;
        for(int j = 0 ; j <= i ; j ++)
        {
            if(A[j] > A[idx])
                idx = j;
        }
        swap(A[i],A[idx]);
    }
}

5.插入排序模板

算法步骤
插入排序每次排序第k个元素,此时前k - 1个元素已经从小到大排好序了,我们从已经排序好的元素从后往前遍历,找到第一个小于当前元素的位置,那么当前元素就应该插在当前元素的后面。

void insertSort(int n)
{
    for(int i = 0 ; i < n ; i ++)
    {
        for(int j = i - 1 ; j >= 0 && A[j] > A[j + 1] ; j -= 1)
            swap(A[j],A[j + 1]);
    }
}

6.Shell排序模板

算法步骤
Shell排序也称为缩小增量排序。我们首先将所有距离为gap的元素划分为一组,先将每一组内部使用某种算法排好序(这里我们使用插入排序)。

如我们将A[0],A[gap],A[2gap],…,A[kgap]划分为一组,A[1],A[gap + 1],A[2gap + 1],…,A[kgap + 1]划分为一组。然后我们不断的缩小gap,直至gap = 1时,我们就将整个数组排好序了。

常用情况是初始gap = n / 2,每次将gap变为原来的一半。

//  这里需要注意的是,思想上分组排序,实际上是每一组交替进行。
//  第一层循环,更新gap
//  第二层循环,迭代当前需要将哪一个数字按照选择排序的方式在组内排好序
//  第三层循环,A[j]在当前组内(`A[j],A[j - gap],A[j - 2*gap],...,A[j - k*gap]`)进行插入排序
void shellSort(int n)
{
    for(int gap = n / 2; gap > 0 ; gap = gap / 2)
    {
        for(int i = gap ; i < n ; i ++)
        {
            for(int j = i - gap ; j >= 0 && A[j] > A[j + gap] ; j -= gap)
                swap(A[j],A[j + gap]);
        }
    }
}

7.堆积排序模板

算法步骤
堆积排序的思想是首先将 n n n个元素建立成一个大顶堆,然后从大顶堆弹出当前堆顶元素也就是最大的元素,然后将剩余 n − 1 n-1 n1个元素重新更新大顶堆得到次大的元素,直至得到所有的元素。

堆积排序主要分为两个重要的过程,建立大顶堆和更新大顶堆。这里我们使用数组A[0:n-1]来模拟大顶堆,根节点序号为idx,那么其左右孩子的序号分别为2 * idx和2 * idx + 1。

更新大顶堆:假设我们已经得到了初始的大顶堆,我们将大顶堆堆顶元素A[0]和A[n - 1]交换,交换后,我们发现除了根节点,左右两颗子树还是大顶堆的。这时候我们只需要将大顶堆堆顶的元素和两个孩子中较大的那个孩子交换,直至交换到叶节点。

初始建堆:假设堆有d层,那么我们将d - 1层最右边的结点开始,从右到左,从下到上更新以当前节点为根节点的大顶堆进行更新。进行更新完后,我们就可以得到一个大顶堆了。

//  记录根节点的值,`root`是根节点索引,`j`是左孩子节点索引,`n`代表当前堆内的有效元素个数
//  如果当前根节点有右孩子,并且右孩子大于左孩子,更新`j`为左右孩子较大值的那个节点索引
//  如果当前根节点大于较大的孩子节点,那么就不用交换了
//  否则将当前根节点的值变为较大孩子节点的值,同时更新当前根节点为较大孩子节点
void adjust(int root,int n)
{
    int temp = A[root],j = 2 * root + 1;
    while(j < n)
    {
        if(j + 1 < n && A[j] < A[j + 1])
            j ++;
        if(temp >= A[j])
            break;
        A[(j - 1) / 2] = A[j];
        j = 2 * j + 1;
    }
    A[(j - 1) / 2] = temp;
}
//  首先将前`n - 1`个节点为根节点的子树进行调整,建立一个大顶堆
//  将堆顶元素弹出,并更新大顶堆
void heapSort(int n)
{
    for(int i = n / 2;i >= 0 ; i --)
        adjust(i,n);
    for(int i = n - 1 ; i >= 0 ; i --)
    {
        swap(A[0],A[i]);
        adjust(0,i);
    }
}

四、各模板总结

#include
#include
#include
using namespace std;
const int N = 100010;

int A[N];
// 快速排序 O(nlogn)
void quickSort(int l,int r)
{
    if(l >= r) return;
    int pivot = A[l];
    int i = l,j = r;
    while(i < j)
    {
        while(i < j && A[j] >= pivot) j --;
        while(i < j && A[i] <= pivot) i ++;
        if(i < j)
            swap(A[i],A[j]);
    }
    swap(A[l],A[i]);
    quickSort(l,i - 1);
    quickSort(i + 1,r);
}
// 选择排序 O(n^2)
void selectSort(int n)
{
    for(int i = n - 1; i >= 0 ; i --)
    {
        int idx = 0;
        for(int j = 0 ; j <= i ; j ++)
        {
            if(A[j] > A[idx])
                idx = j;
        }
        swap(A[i],A[idx]);
    }
}
// 冒泡排序 O(n^2) 稳定
void BubbleSort(int n)
{
    for(int i = n - 1 ; i >= 0; i --)
    {
        for(int j = 0 ; j < i ; j ++)
        {
            if(A[j] > A[j + 1])
                swap(A[j],A[j + 1]);
        }
    }
}
// 插入排序 O(n^2) 稳定
void insertSort(int n)
{
    for(int i = 0 ; i < n ; i ++)
    {
        int j = i - 1,temp = A[i];
        while(j >= 0 && A[j] > temp)
        {
            A[j + 1] = A[j];
            j --;
        }
        A[j + 1] = temp;
    }
}
void insertSortV2(int n)
{
    for(int i = 0 ; i < n ; i ++)
    {
        for(int j = i - 1 ; j >= 0 && A[j] > A[j + 1] ; j -= 1)
            swap(A[j],A[j + 1]);
    }
}
void quickInsertSort(int n)
{
    for(int i = 0 ; i < n ; i ++)
    {
        int l = 0,r = i - 1,temp = A[i];
        while(l < r)
        {
            int mid = (l + r) >> 1;
            if(A[mid] > temp) r = mid;
            else l = mid + 1;
        }
        if(A[l] < temp)
            continue;
        for(int j = i - 1 ; j >= l ; j --)
            A[j + 1] = A[j];
        A[l] = temp;
    }
}
// 归并排序 O(nlogn) 稳定
void mergeSort(int l,int r)
{
    if(l == r) return;
    int mid = (l + r) >> 1;
    mergeSort(l,mid);
    mergeSort(mid + 1,r);
    int temp[r - l + 1],k = 0,i,j;
    for(i = l,j = mid + 1;i <= mid && j <= r;k ++)
    {
        if(A[i] < A[j]) temp[k] = A[i ++];
        else temp[k] = A[j ++];
    }
    while(i <= mid) temp[k ++] = A[i ++];
    while(j <= r) temp[k ++] = A[j ++];
    for(int i = l,k = 0;i <= r ; i ++,k ++)
        A[i] = temp[k];
}
//Shell排序
void shellSort(int n)
{
    for(int gap = n / 2; gap > 0 ; gap = gap / 2)
    {
        for(int i = gap ; i < n ; i ++)
        {
            for(int j = i - gap ; j >= 0 && A[j] > A[j + gap] ; j -= gap)
                swap(A[j],A[j + gap]);
        }
    }
}
// 堆积排序 O(nlogn)
void adjust(int root,int n)
{
    int temp = A[root],j = 2 * root + 1;
    while(j < n)
    {
        if(j + 1 < n && A[j] < A[j + 1])
            j ++;
        if(temp >= A[j])
            break;
        A[(j - 1) / 2] = A[j];
        j = 2 * j + 1;
    }
    A[(j - 1) / 2] = temp;
}
void heapSort(int n)
{
    for(int i = n / 2;i >= 0 ; i --)
        adjust(i,n);
    for(int i = n - 1 ; i >= 0 ; i --)
    {
        swap(A[0],A[i]);
        adjust(0,i);
    }
}


int main()
{
    int n;
    cin>>n;
    for(int i = 0; i < n ; i ++)
    A[i]=rand()%100;
    quickSort(0,n - 1);
	//BubbleSort(n);
    //insertSort(n);
    //insertSortV2(n);
    //quickInsertSort(n);
    //selectSort(n);
    //mergeSort(0,n - 1);
    //shellSort(n);
    //heapSort(n);
    for(int i = 0 ; i < n ; i ++)
        cout<<A[i]<<\" \";
    return 0;
}

最后

莫言真理无穷尽,寸进自有寸进欢
\"【算法基础——第六讲】排序_第2张图片\"

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号